feat(API): 新增按单位代码获取摄像头列表接口

新增/cameras/unit/:unitcode接口,支持根据单位代码过滤摄像头列表
适配qsc_camera表结构调整数据库模型和API响应格式
添加API使用文档说明各接口调用方式
ziyun-rtsp-web
karlkyo 8 months ago
parent 2a37fbf7be
commit bbccd98711

@ -0,0 +1,371 @@
# 摄像头管理 API 使用说明
本文档说明如何使用摄像头管理相关的 REST API 接口。
## 基础配置
### 服务器地址
```
http://localhost:8083 # 默认地址,根据实际配置调整
```
### 认证
如果启用了 HTTP 认证,需要在请求头中添加 Basic Auth
```
Authorization: Basic <base64(username:password)>
```
## 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
<template>
<div>
<h2>摄像头管理</h2>
<!-- 单位代码选择 -->
<div>
<label>选择单位代码:</label>
<select v-model="selectedUnitCode" @change="loadCamerasByUnit">
<option value="">全部</option>
<option value="UNIT001">UNIT001</option>
<option value="UNIT002">UNIT002</option>
</select>
</div>
<!-- 摄像头列表 -->
<div v-if="loading">加载中...</div>
<div v-else>
<h3>摄像头列表 (共 {{ cameras.length }} 个)</h3>
<div v-for="camera in cameras" :key="camera.camera_id" class="camera-item">
<h4>{{ camera.camera_name }}</h4>
<p>IP: {{ camera.ip }}:{{ camera.port }}</p>
<p>单位代码: {{ camera.unit_code }}</p>
<p>状态: <span :class="camera.stream_status">{{ camera.stream_status }}</span></p>
</div>
</div>
</div>
</template>
<script>
import { getAllCameras, getCamerasByUnitCode } from './api';
export default {
name: 'CameraManager',
data() {
return {
cameras: [],
selectedUnitCode: '',
loading: false
};
},
mounted() {
this.loadAllCameras();
},
methods: {
async loadAllCameras() {
this.loading = true;
try {
const data = await getAllCameras();
this.cameras = data.cameras;
} catch (error) {
console.error('加载摄像头失败:', error);
} finally {
this.loading = false;
}
},
async loadCamerasByUnit() {
if (!this.selectedUnitCode) {
this.loadAllCameras();
return;
}
this.loading = true;
try {
const data = await getCamerasByUnitCode(this.selectedUnitCode);
this.cameras = data.cameras;
} catch (error) {
console.error('加载摄像头失败:', error);
} finally {
this.loading = false;
}
}
}
};
</script>
<style scoped>
.camera-item {
border: 1px solid #ddd;
padding: 10px;
margin: 10px 0;
border-radius: 5px;
}
.online {
color: green;
font-weight: bold;
}
.offline {
color: red;
font-weight: bold;
}
</style>
```
## 错误处理
### 常见错误码
- `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'` 的摄像头(未删除的摄像头)

Binary file not shown.

@ -1,11 +1,11 @@
package main package main
import ( import (
"fmt"
"net/http" "net/http"
"time" "time"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
@ -19,21 +19,32 @@ type CameraRequest struct {
Enabled bool `json:"enabled"` Enabled bool `json:"enabled"`
} }
// CameraResponse 摄像头响应结构 // CameraResponse 摄像头响应结构 - 适配qsc_camera表
type CameraResponse struct { type CameraResponse struct {
ID string `json:"id"` CameraID int `json:"camera_id"`
Name string `json:"name"` IP string `json:"ip"`
IP string `json:"ip"` Port int `json:"port"`
Username string `json:"username"` Username string `json:"username"`
Password string `json:"password,omitempty"` // 密码在响应中可选 Password string `json:"password,omitempty"`
RTSPURL string `json:"rtsp_url"` URL string `json:"url"`
Enabled bool `json:"enabled"` CameraProduce string `json:"camera_produce"`
Status string `json:"status"` CameraName string `json:"camera_name"`
CreatedAt time.Time `json:"created_at"` DeviceType string `json:"device_type"`
UpdatedAt time.Time `json:"updated_at"` 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) { func HTTPAPIServerCameras(c *gin.Context) {
if !Storage.Server.DatabaseEnabled { if !Storage.Server.DatabaseEnabled {
c.JSON(http.StatusServiceUnavailable, gin.H{ c.JSON(http.StatusServiceUnavailable, gin.H{
@ -42,19 +53,86 @@ func HTTPAPIServerCameras(c *gin.Context) {
return 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{ c.JSON(http.StatusInternalServerError, gin.H{
"error": "Database manager not initialized", "error": err.Error(),
}) })
return 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 { if err != nil {
log.WithFields(logrus.Fields{ log.WithFields(logrus.Fields{
"module": "api", "module": "api",
"func": "HTTPAPIServerCameras", "func": "HTTPAPIServerCamerasByUnitCode",
"call": "GetAllCameras", "call": "GetCamerasByUnitCode",
}).Errorln(err.Error()) }).Errorln(err.Error())
c.JSON(http.StatusInternalServerError, gin.H{ c.JSON(http.StatusInternalServerError, gin.H{
"error": err.Error(), "error": err.Error(),
@ -63,37 +141,47 @@ func HTTPAPIServerCameras(c *gin.Context) {
} }
// 转换为响应格式 // 转换为响应格式
var response []CameraResponse var responses []CameraResponse
for _, camera := range cameras { for _, camera := range cameras {
status := "offline" // 检查流状态
if stream, exists := Storage.Streams[camera.ID]; exists { streamID := fmt.Sprintf("%d", camera.CameraID)
if len(stream.Channels) > 0 { streamStatus := "offline"
for _, channel := range stream.Channels { if _, exists := Storage.Streams[streamID]; exists {
if channel.runLock { if Storage.StreamChannelExist(streamID, "0") {
status = "online" streamStatus = "online"
break
}
}
} }
} }
response = append(response, CameraResponse{ response := CameraResponse{
ID: camera.ID, CameraID: camera.CameraID,
Name: camera.Name, IP: camera.IP,
IP: camera.IP, Port: camera.Port,
Username: camera.Username, Username: camera.Username,
// Password: camera.Password, // 不返回密码 Password: camera.Password,
RTSPURL: camera.RTSPURL, URL: camera.URL,
Enabled: camera.Enabled, CameraProduce: camera.CameraProduce,
Status: status, CameraName: camera.CameraName,
CreatedAt: camera.CreatedAt, DeviceType: camera.DeviceType,
UpdatedAt: camera.UpdatedAt, 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{ c.JSON(http.StatusOK, gin.H{
"cameras": response, "cameras": responses,
"count": len(response), "total": len(responses),
"unit_code": unitCode,
}) })
} }
@ -114,20 +202,23 @@ func HTTPAPIServerCameraAdd(c *gin.Context) {
return return
} }
// 生成新的UUID // 创建摄像头记录 - 适配qsc_camera表
cameraID := uuid.New().String()
// 创建摄像头记录
camera := Camera{ camera := Camera{
ID: cameraID, IP: req.IP,
Name: req.Name, Port: 554, // 默认RTSP端口
IP: req.IP, Username: req.Username,
Username: req.Username, Password: req.Password,
Password: req.Password, URL: req.RTSPURL,
RTSPURL: req.RTSPURL, CameraName: req.Name,
Enabled: req.Enabled, DeviceType: "网络摄像头",
CreatedAt: time.Now(), UnitCode: "DEFAULT", // 默认单位代码
UpdatedAt: time.Now(), 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 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) stream := CameraToStream(camera)
err = Storage.StreamAdd(cameraID, stream) err = Storage.StreamAdd(streamID, stream)
if err != nil { if err != nil {
log.WithFields(logrus.Fields{ log.WithFields(logrus.Fields{
"module": "api", "module": "api",
@ -161,7 +261,7 @@ func HTTPAPIServerCameraAdd(c *gin.Context) {
c.JSON(http.StatusCreated, gin.H{ c.JSON(http.StatusCreated, gin.H{
"message": "Camera added successfully", "message": "Camera added successfully",
"camera_id": cameraID, "camera_id": camera.CameraID,
}) })
} }
@ -211,18 +311,15 @@ func HTTPAPIServerCameraUpdate(c *gin.Context) {
return return
} }
// 更新摄像头信息 // 更新摄像头信息 - 适配qsc_camera表
updatedCamera := Camera{ updatedCamera := *existingCamera // 复制现有摄像头信息
ID: cameraID, updatedCamera.IP = req.IP
Name: req.Name, updatedCamera.Username = req.Username
IP: req.IP, updatedCamera.Password = req.Password
Username: req.Username, updatedCamera.URL = req.RTSPURL
Password: req.Password, updatedCamera.CameraName = req.Name
RTSPURL: req.RTSPURL, updatedCamera.UpdateBy = "admin"
Enabled: req.Enabled, updatedCamera.UpdateTime = time.Now()
CreatedAt: existingCamera.CreatedAt,
UpdatedAt: time.Now(),
}
// 更新数据库 // 更新数据库
err = Storage.dbManager.UpdateCamera(&updatedCamera) err = Storage.dbManager.UpdateCamera(&updatedCamera)
@ -354,7 +451,8 @@ func HTTPAPIServerCameraGet(c *gin.Context) {
// 检查流状态 // 检查流状态
status := "offline" 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 { if len(stream.Channels) > 0 {
for _, channel := range stream.Channels { for _, channel := range stream.Channels {
if channel.runLock { if channel.runLock {
@ -366,16 +464,20 @@ func HTTPAPIServerCameraGet(c *gin.Context) {
} }
response := CameraResponse{ response := CameraResponse{
ID: camera.ID, CameraID: camera.CameraID,
Name: camera.Name, CameraName: camera.CameraName,
IP: camera.IP, IP: camera.IP,
Username: camera.Username, Port: camera.Port,
Username: camera.Username,
// Password: camera.Password, // 不返回密码 // Password: camera.Password, // 不返回密码
RTSPURL: camera.RTSPURL, URL: camera.URL,
Enabled: camera.Enabled, CameraProduce: camera.CameraProduce,
Status: status, DeviceType: camera.DeviceType,
CreatedAt: camera.CreatedAt, UnitCode: camera.UnitCode,
UpdatedAt: camera.UpdatedAt, NvrProduce: camera.NvrProduce,
StreamStatus: status,
CreateTime: camera.CreateTime,
UpdateTime: camera.UpdateTime,
} }
c.JSON(http.StatusOK, response) c.JSON(http.StatusOK, response)

@ -91,6 +91,7 @@ func HTTPAPIServer() {
*/ */
privat.GET("/cameras", HTTPAPIServerCameras) privat.GET("/cameras", HTTPAPIServerCameras)
privat.GET("/cameras/unit/:unitcode", HTTPAPIServerCamerasByUnitCode)
privat.POST("/camera/add", HTTPAPIServerCameraAdd) privat.POST("/camera/add", HTTPAPIServerCameraAdd)
privat.GET("/camera/:uuid", HTTPAPIServerCameraGet) privat.GET("/camera/:uuid", HTTPAPIServerCameraGet)
privat.PUT("/camera/:uuid", HTTPAPIServerCameraUpdate) privat.PUT("/camera/:uuid", HTTPAPIServerCameraUpdate)

@ -1,5 +1,6 @@
{ {
"server": { "server": {
"database_enabled": true,
"debug": true, "debug": true,
"http_debug": false, "http_debug": false,
"http_demo": true, "http_demo": true,
@ -24,25 +25,13 @@
} }
}, },
"database": { "database": {
"enabled": false, "enabled": true,
"type": "mysql", "type": "mysql",
"host": "localhost", "host": "localhost",
"port": 3306, "port": 3306,
"username": "root", "username": "root",
"password": "password", "password": "123456",
"database": "rtsp_cameras", "database": "ziyun",
"table_name": "cameras" "table_name": "qsc_camera"
},
"streams": {
"27aec28e-6181-4753-9acd-0456a75f0289": {
"channels": {
"0": {
"url": "rtmp://171.25.232.10/12d525bc9f014e209c1280bc0d46a87e",
"debug": false,
"audio": true
}
},
"name": "111111111"
}
} }
} }

@ -3,11 +3,11 @@ package main
import ( import (
"database/sql" "database/sql"
"fmt" "fmt"
"strconv"
"time" "time"
_ "github.com/denisenkom/go-mssqldb" _ "github.com/denisenkom/go-mssqldb"
_ "github.com/go-sql-driver/mysql" _ "github.com/go-sql-driver/mysql"
"github.com/google/uuid"
) )
// DatabaseConfig 数据库配置结构 // DatabaseConfig 数据库配置结构
@ -22,20 +22,28 @@ type DatabaseConfig struct {
SSLMode string `json:"ssl_mode,omitempty"` // for mysql SSLMode string `json:"ssl_mode,omitempty"` // for mysql
} }
// Camera 摄像头数据库模型 // Camera 摄像头数据库模型 - 适配qsc_camera表
type Camera struct { type Camera struct {
ID string `json:"id" db:"id"` CameraID int `json:"camera_id" db:"camera_id"`
Name string `json:"name" db:"name"` IP string `json:"ip" db:"ip"`
IP string `json:"ip" db:"ip"` Port int `json:"port" db:"port"`
Username string `json:"username" db:"username"` Username string `json:"username" db:"username"`
Password string `json:"password" db:"password"` Password string `json:"password" db:"password"`
RTSPURL string `json:"rtsp_url" db:"rtsp_url"` URL string `json:"url" db:"url"`
Enabled bool `json:"enabled" db:"enabled"` CameraProduce string `json:"camera_produce" db:"camera_produce"`
OnDemand bool `json:"on_demand" db:"on_demand"` CameraName string `json:"camera_name" db:"camera_name"`
Audio bool `json:"audio" db:"audio"` DeviceType string `json:"device_type" db:"device_type"`
Debug bool `json:"debug" db:"debug"` UnitCode string `json:"unit_code" db:"unit_code"`
CreatedAt time.Time `json:"created_at" db:"created_at"` NvrProduce string `json:"nvr_produce" db:"nvr_produce"`
UpdatedAt time.Time `json:"updated_at" db:"updated_at"` 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 数据库管理器 // DatabaseManager 数据库管理器
@ -139,11 +147,14 @@ func (dm *DatabaseManager) initTables() error {
return err return err
} }
// GetAllCameras 获取所有摄像头 // GetAllCameras 获取所有摄像头 - 适配qsc_camera表
func (dm *DatabaseManager) GetAllCameras() ([]Camera, error) { 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 { if err != nil {
return nil, err return nil, err
} }
@ -152,9 +163,11 @@ func (dm *DatabaseManager) GetAllCameras() ([]Camera, error) {
var cameras []Camera var cameras []Camera
for rows.Next() { for rows.Next() {
var camera Camera var camera Camera
err := rows.Scan(&camera.ID, &camera.Name, &camera.IP, &camera.Username, err := rows.Scan(&camera.CameraID, &camera.IP, &camera.Port, &camera.Username,
&camera.Password, &camera.RTSPURL, &camera.Enabled, &camera.OnDemand, &camera.Password, &camera.URL, &camera.CameraProduce, &camera.CameraName,
&camera.Audio, &camera.Debug, &camera.CreatedAt, &camera.UpdatedAt) &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 != nil {
return nil, err return nil, err
} }
@ -164,14 +177,49 @@ func (dm *DatabaseManager) GetAllCameras() ([]Camera, error) {
return cameras, nil 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) { 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 var camera Camera
err := dm.db.QueryRow(query, id).Scan(&camera.ID, &camera.Name, &camera.IP, err := dm.db.QueryRow(query, id).Scan(&camera.CameraID, &camera.IP, &camera.Port,
&camera.Username, &camera.Password, &camera.RTSPURL, &camera.Enabled, &camera.Username, &camera.Password, &camera.URL, &camera.CameraProduce,
&camera.OnDemand, &camera.Audio, &camera.Debug, &camera.CreatedAt, &camera.UpdatedAt) &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 != nil {
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
@ -183,41 +231,44 @@ func (dm *DatabaseManager) GetCameraByID(id string) (*Camera, error) {
return &camera, nil return &camera, nil
} }
// CreateCamera 创建摄像头 // CreateCamera 创建摄像头 - 适配qsc_camera表
func (dm *DatabaseManager) CreateCamera(camera *Camera) error { func (dm *DatabaseManager) CreateCamera(camera *Camera) error {
if camera.ID == "" { camera.CreateTime = time.Now()
camera.ID = uuid.New().String() camera.UpdateTime = time.Now()
}
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) query := `INSERT INTO qsc_camera (ip, port, username, password, url, camera_produce,
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` 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, _, err := dm.db.Exec(query, camera.IP, camera.Port, camera.Username, camera.Password,
camera.Password, camera.RTSPURL, camera.Enabled, camera.OnDemand, camera.URL, camera.CameraProduce, camera.CameraName, camera.DeviceType,
camera.Audio, camera.Debug, camera.CreatedAt, camera.UpdatedAt) camera.UnitCode, camera.NvrProduce, camera.NvrPath, camera.PlayBack,
camera.DelFlag, camera.CreateBy, camera.CreateTime, camera.UpdateBy,
camera.UpdateTime, camera.UserID, camera.DeptID)
return err return err
} }
// UpdateCamera 更新摄像头 // UpdateCamera 更新摄像头 - 适配qsc_camera表
func (dm *DatabaseManager) UpdateCamera(camera *Camera) error { 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=?, query := `UPDATE qsc_camera SET ip=?, port=?, username=?, password=?, url=?,
enabled=?, on_demand=?, audio=?, debug=?, updated_at=? WHERE id=?` 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, _, err := dm.db.Exec(query, camera.IP, camera.Port, camera.Username, camera.Password,
camera.Password, camera.RTSPURL, camera.Enabled, camera.OnDemand, camera.URL, camera.CameraProduce, camera.CameraName, camera.DeviceType,
camera.Audio, camera.Debug, camera.UpdatedAt, camera.ID) camera.UnitCode, camera.NvrProduce, camera.NvrPath, camera.PlayBack,
camera.UpdateBy, camera.UpdateTime, camera.CameraID)
return err return err
} }
// DeleteCamera 删除摄像头 // DeleteCamera 删除摄像头 - 适配qsc_camera表
func (dm *DatabaseManager) DeleteCamera(id string) error { 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) _, err := dm.db.Exec(query, id)
return err return err
} }
@ -230,39 +281,61 @@ func (dm *DatabaseManager) Close() error {
return nil return nil
} }
// CameraToStream 将摄像头转换为流配置 // CameraToStream 将摄像头转换为流配置 - 适配qsc_camera表
func CameraToStream(camera Camera) StreamST { 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{ return StreamST{
Name: camera.Name, Name: camera.CameraName,
Channels: map[string]ChannelST{ Channels: map[string]ChannelST{
"0": { "0": {
Name: camera.Name, Name: camera.CameraName,
URL: camera.RTSPURL, URL: rtspURL,
OnDemand: camera.OnDemand, OnDemand: false, // 根据需要设置
Debug: camera.Debug, Debug: false, // 根据需要设置
Audio: camera.Audio, Audio: true, // 根据需要设置
}, },
}, },
} }
} }
// StreamToCamera 将流配置转换为摄像头 // StreamToCamera 将流配置转换为摄像头 - 适配qsc_camera表
func StreamToCamera(id string, stream StreamST) Camera { func StreamToCamera(id string, stream StreamST) Camera {
// 创建基础摄像头对象 - 适配qsc_camera表
cameraID, _ := strconv.Atoi(id)
camera := Camera{ camera := Camera{
ID: id, CameraID: cameraID,
Name: stream.Name, CameraName: stream.Name,
Enabled: true, IP: "", // 从URL中解析
CreatedAt: time.Now(), Port: 554, // 默认RTSP端口
UpdatedAt: time.Now(), 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 { if len(stream.Channels) > 0 {
for _, channel := range stream.Channels { for _, channel := range stream.Channels {
camera.RTSPURL = channel.URL camera.URL = channel.URL
camera.OnDemand = channel.OnDemand
camera.Debug = channel.Debug
camera.Audio = channel.Audio
break break
} }
} }

@ -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
}

@ -66,12 +66,13 @@ func (obj *StorageST) LoadStreamsFromDatabase() error {
// 将摄像头转换为流配置 // 将摄像头转换为流配置
for _, camera := range cameras { for _, camera := range cameras {
stream := CameraToStream(camera) stream := CameraToStream(camera)
obj.Streams[camera.ID] = stream streamID := fmt.Sprintf("%d", camera.CameraID)
obj.Streams[streamID] = stream
log.WithFields(logrus.Fields{ log.WithFields(logrus.Fields{
"module": "database", "module": "database",
"func": "LoadStreamsFromDatabase", "func": "LoadStreamsFromDatabase",
"camera": camera.Name, "camera": camera.CameraName,
"id": camera.ID, "id": camera.CameraID,
}).Infoln("Loaded camera from database") }).Infoln("Loaded camera from database")
} }
@ -114,11 +115,11 @@ func (obj *StorageST) SyncStreamToDatabase(streamID string, stream StreamST) err
"module": "database", "module": "database",
"func": "SyncStreamToDatabase", "func": "SyncStreamToDatabase",
"id": streamID, "id": streamID,
"name": camera.Name, "name": camera.CameraName,
}).Infoln("Created camera in database") }).Infoln("Created camera in database")
} else { } else {
// 更新现有摄像头 // 更新现有摄像头
camera.CreatedAt = existingCamera.CreatedAt // 保持创建时间 camera.CreateTime = existingCamera.CreateTime // 保持创建时间
err = obj.dbManager.UpdateCamera(&camera) err = obj.dbManager.UpdateCamera(&camera)
if err != nil { if err != nil {
log.WithFields(logrus.Fields{ log.WithFields(logrus.Fields{
@ -133,7 +134,7 @@ func (obj *StorageST) SyncStreamToDatabase(streamID string, stream StreamST) err
"module": "database", "module": "database",
"func": "SyncStreamToDatabase", "func": "SyncStreamToDatabase",
"id": streamID, "id": streamID,
"name": camera.Name, "name": camera.CameraName,
}).Infoln("Updated camera in database") }).Infoln("Updated camera in database")
} }

Loading…
Cancel
Save