videoplayer/handler/tool.go

560 lines
17 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package handler
import (
"fmt"
"github.com/gin-gonic/gin"
"io"
"log"
"net/http"
"net/url"
"os"
"path/filepath"
"strconv"
"time"
"videoplayer/dao"
"videoplayer/proto"
"videoplayer/service"
"videoplayer/worker"
)
type SetRedisReq struct {
Option string `json:"option" form:"option"`
Key string `json:"key" form:"key"`
Value string `json:"value" form:"value"`
Expire int `json:"expire" form:"expire"`
}
type SetDeviceStatusReq struct {
ID string `json:"id" form:"id"` //设备编码
Status string `json:"status" form:"status"` //设备状态
}
type GetFileListReq struct {
Type string `json:"type" form:"type"` //请求类型1按md5查询2按文件名查询;3:查询待删除文件
Md5 string `json:"md5" form:"md5"`
}
type SendMailReq struct {
Title string `json:"title" form:"title"`
Content string `json:"content" form:"content"`
To string `json:"to" form:"to"`
}
func SetUpToolGroup(router *gin.Engine) {
toolGroup := router.Group("/tool")
toolGroup.POST("/set_redis", SetRedis)
toolGroup.POST("/get_redis", GetRedis)
//文件上传、下载
toolGroup.POST("/upload", UploadFile)
toolGroup.GET("/download", DownloadFile)
toolGroup.GET("/file/:filename", GetFile)
toolGroup.POST("/file_list", GetFileList)
//文件管理
toolGroup.POST("/file_del", DelFile)
//服务器、设备状态接口
toolGroup.POST("/monitor", SetDeviceStatusV2)
toolGroup.GET("/get_monitor_list", GetMonitorList) //获取设备监控列表
toolGroup.POST("/update_monitor", UpdateMonitor) //设置设备状态
toolGroup.POST("/del_monitor", DelMonitor) //删除设备监控
//发送邮件
toolGroup.POST("/send_mail", SendMailTool)
//下载代理
toolGroup.GET("/dl/:url", DownloadProxyHandle)
}
func GetMonitorList(c *gin.Context) {
id, _ := c.Get("id")
userId := int(id.(float64))
var resp proto.GeneralResp
monitorDeviceList, err := service.GetMonitorDeviceListWithStatus(userId)
if err != nil {
resp.Code = proto.OperationFailed
resp.Message = err.Error()
} else {
resp.Code = proto.SuccessCode
resp.Message = "success"
resp.Data = monitorDeviceList
}
c.JSON(http.StatusOK, resp)
}
type UpdateMonitorDeviceStatusReq struct {
Devices []proto.GetMonitorDeviceStatus `json:"devices" form:"devices"` //设备状态列表
}
func UpdateMonitor(c *gin.Context) {
id, _ := c.Get("id")
id1 := int(id.(float64))
var req UpdateMonitorDeviceStatusReq
var resp proto.GeneralResp
if err := c.ShouldBind(&req); err == nil {
err2 := service.UpdateMonitorDeviceListWithStatus(id1, req.Devices)
if err2 != nil {
resp.Code = proto.OperationFailed
resp.Message = "更新设备状态失败:" + err2.Error()
} else {
resp.Code = proto.SuccessCode
resp.Message = "更新设备状态成功"
}
} else {
resp.Code = proto.ParameterError
resp.Message = "参数解析失败:" + err.Error()
}
c.JSON(http.StatusOK, resp)
}
func DelMonitor(c *gin.Context) {
id, _ := c.Get("id")
id1 := int(id.(float64))
var req UpdateMonitorDeviceStatusReq
var resp proto.GeneralResp
if err := c.ShouldBind(&req); err == nil {
delSuccess, err2 := service.DelMonitorDeviceListWithStatus(id1, req.Devices)
if err2 != nil {
resp.Code = proto.OperationFailed
resp.Message = "更新设备状态失败:" + err2.Error()
resp.Data = delSuccess
} else {
resp.Code = proto.SuccessCode
resp.Message = "更新设备状态成功"
resp.Data = delSuccess
}
} else {
resp.Code = proto.ParameterError
resp.Message = "参数解析失败:" + err.Error()
}
c.JSON(http.StatusOK, resp)
}
func SetDeviceStatusV2(c *gin.Context) {
// TODO
var req SetDeviceStatusReq
var resp proto.GeneralResp
if err := c.ShouldBind(&req); err != nil {
resp.Code = proto.ParameterError
resp.Message = "参数解析失败:" + err.Error()
} else {
token := c.Request.Header.Get("token")
if token == "" {
resp.Code = proto.TokenIsNull //token为空
} else {
devices := worker.GetRedisSetMembers(token)
if len(devices) == 0 {
resp.Code = proto.MonitorServerIDIsNull
resp.Message = "服务器设备监控为空!"
} else {
isExit := false
for _, v := range devices {
if v == req.ID {
// 继续处理请求
//是否是暂停之后第一次上线,如果是则发送邮件通知
deviceStatus := worker.GetRedis("monitor_" + req.ID)
isExist := worker.IsContainKey("monitor_" + req.ID)
if deviceStatus == "2" || !isExist {
//发送邮件通知
title := "设备上线"
content := "设备上线\n设备:" + req.ID + "\t状态:" + req.Status + "\t时间" + time.Now().String()
go SendMail(title, content)
}
worker.SetRedisWithExpire("monitor_"+req.ID, "1", time.Second*300)
resp.Code = proto.SuccessCode
resp.Message = "success"
isExit = true
}
}
if isExit == false {
resp.Code = proto.MonitorServerIDNotFound
resp.Message = "设备不存在!"
log.Println("设备不存在,id:", req.ID, "\ttoken:", token, "\tdevices:", devices)
}
}
}
}
c.JSON(http.StatusOK, resp)
}
func GetFileList(c *gin.Context) {
//解析请求参数
var req GetFileListReq
if err := c.ShouldBind(&req); err == nil {
if req.Type == "1" {
//按md5查询
if req.Md5 == "" {
c.JSON(http.StatusOK, gin.H{"error": "md5 is empty", "code": proto.ParameterError, "message": "failed"})
return
}
file := dao.FindFileByMd5(req.Md5)
if file.ID == 0 {
c.JSON(http.StatusOK, gin.H{"error": "file not found", "code": proto.FileNotFound, "message": "failed"})
return
}
c.JSON(http.StatusOK, gin.H{"code": proto.SuccessCode, "message": "success", "data": file})
}
} else {
c.JSON(http.StatusOK, gin.H{"error": "parameter error", "code": proto.ParameterError, "message": "failed"})
return
}
}
func DelFile(c *gin.Context) {
//先查看是否有权限
id, _ := c.Get("id")
id1 := int(id.(float64))
file_id, _ := strconv.Atoi(c.PostForm("id"))
file_ := dao.FindFileByID(file_id, id1)
if file_.ID == 0 {
c.JSON(http.StatusOK, gin.H{"error": "file not found", "code": proto.FileNotFound, "message": "failed"})
return
}
//删除文件
err := os.Remove(file_.FilePath + "/" + file_.FileStoreName)
if err != nil {
c.JSON(http.StatusOK, gin.H{"error": "delete file failed", "code": proto.DeleteFileFailed, "message": "failed"})
return
}
//删除文件信息
if res := dao.DeleteFileById(file_id); !res {
c.JSON(http.StatusOK, gin.H{"error": "delete file info failed", "code": proto.DeleteFileInfoFailed, "message": "failed"})
return
}
c.JSON(http.StatusOK, gin.H{"code": proto.SuccessCode, "message": "success"})
}
func GetFile(c *gin.Context) {
//先查看是否有权限
filename := c.Param("filename")
if filename == "" {
c.JSON(http.StatusOK, gin.H{"error": "filename is empty", "code": proto.ParameterError, "message": "failed"})
return
}
//查询文件信息
file := dao.FindFileByName(filename)
if file.ID == 0 {
c.JSON(http.StatusOK, gin.H{"error": "file not found", "code": proto.FileNotFound, "message": "failed"})
return
}
//下载文件
if file.NeedAuth == false {
c.File(file.FilePath + "/" + file.FileStoreName)
} else {
c.JSON(http.StatusOK, gin.H{"code": proto.SuccessCode, "message": "file must auth", "data": "file must auth"})
}
}
func UploadFile(c *gin.Context) {
//先查看是否有权限
id, _ := c.Get("id")
id1 := int(id.(float64))
//从请求头获取upload_type
uploadType := c.PostForm("upload_type")
authType := c.PostForm("auth_type")
md5_ := c.PostForm("md5")
if uploadType == "" {
c.JSON(http.StatusOK, gin.H{"error": "upload_type is empty", "code": proto.ParameterError, "message": "failed"})
return
}
user := dao.FindUserByUserID(id1)
if user.Upload == false {
c.JSON(http.StatusOK, gin.H{"error": "no upload Permissions", "code": proto.NoUploadPermissions, "message": "failed"})
return
}
//上传文件
file, err := c.FormFile("file")
if err != nil {
c.JSON(http.StatusOK, gin.H{"error": "upload file failed", "code": proto.UploadFileFailed, "message": "failed"})
return
}
//计算文件md5值
if md5_ == "" {
file_, _ := file.Open()
md5_ = service.CalculateFileMd5(file_)
if md5_ == "" {
c.JSON(http.StatusOK, gin.H{"error": "计算文件MD5值失败", "code": proto.UploadFileFailed, "message": "failed"})
return
}
}
//查询文件是否已存在
fileExist := dao.FindFileByMd5(md5_)
if fileExist.ID != 0 {
fileExist.FilePath = ""
c.JSON(http.StatusOK, gin.H{"code": proto.SuccessCode, "message": "success", "data": fileExist})
return
}
//保存文件
filePath, fileStoreName, err := service.SaveFile(c, file, uploadType)
if err != nil {
c.JSON(http.StatusOK, gin.H{"error": "save file failed", "code": proto.SaveFileFailed, "message": "failed"})
return
}
//保存文件信息
fileSize := int(file.Size)
fileName := file.Filename
fileType := file.Header.Get("file_type")
var auth_type_ bool
if authType == "public" || authType == "" {
auth_type_ = false
} else if authType == "private" {
auth_type_ = true
}
file_record := dao.CreateFile(fileStoreName, fileName, fileType, filePath, md5_, fileSize, id1, auth_type_)
if file_record.ID == 0 {
c.JSON(http.StatusOK, gin.H{"error": "save file info failed", "code": proto.SaveFileInfoFailed, "message": "failed"})
return
}
file_record.FilePath = ""
c.JSON(http.StatusOK, gin.H{"code": proto.SuccessCode, "message": "success", "data": file_record})
}
func DownloadFile(c *gin.Context) {
//参数
//filename := c.Param("filename")
file_id, _ := strconv.Atoi(c.Query("id"))
id, _ := c.Get("id")
//查询文件信息
//file := dao.FindFileByNames(file_id, int(id.(float64)))
file_ := dao.FindFileByID(file_id, int(id.(float64)))
if file_.ID == 0 {
c.JSON(http.StatusOK, gin.H{"error": "file not found", "code": proto.FileNotFound, "message": "failed"})
return
}
//下载文件
// 打开文件
file, err := os.Open(file_.FilePath + "/" + file_.FileStoreName)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Internal Server Error", "message": "Failed to open file"})
return
}
defer file.Close()
// 设置响应头
c.Writer.Header().Set("Content-Type", "application/octet-stream")
c.Writer.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", file_.FileName))
// 发送文件内容
_, err = io.Copy(c.Writer, file)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Internal Server Error", "message": "Failed to send file"})
return
}
c.Status(http.StatusOK)
}
func SetRedis(c *gin.Context) {
//先查看是否有权限
id, _ := c.Get("id")
id1 := int(id.(float64))
user := dao.FindUserByUserID(id1)
if user.Redis == false {
c.JSON(http.StatusOK, gin.H{"error": "no redis Permissions", "code": proto.NoRedisPermissions, "message": "failed"})
return
}
//解析请求参数
var req SetRedisReq
if err := c.ShouldBind(&req); err == nil {
var code int
var message string
if req.Option == "list" {
code, message = service.SetToolRedisList(req.Key, req.Value, req.Expire)
} else if req.Option == "set" {
code, message = service.SetToolRedisSet(req.Key, req.Value, req.Expire)
} else if req.Option == "kv" {
code, message = service.SetToolRedisKV(req.Key, req.Value, req.Expire)
}
c.JSON(http.StatusOK, gin.H{"code": code, "message": message})
} else {
c.JSON(http.StatusOK, gin.H{"error": "parameter error", "code": proto.ParameterError, "message": "failed"})
return
}
}
func GetRedis(c *gin.Context) {
//先查看是否有权限
id, _ := c.Get("id")
id1 := int(id.(float64))
user := dao.FindUserByUserID(id1)
if user.Redis == false {
c.JSON(http.StatusOK, gin.H{"error": "no redis Permissions", "code": proto.NoRedisPermissions, "message": "failed"})
return
}
//解析请求参数
var req SetRedisReq
if err := c.ShouldBind(&req); err == nil {
if req.Option == "one" {
code, message := service.GetToolRedis(req.Key)
req.Value = message
c.JSON(http.StatusOK, gin.H{"code": code, "message": message, "data": req})
} else if req.Option == "all" {
code, message, data := service.GetAllRedis()
c.JSON(http.StatusOK, gin.H{"code": code, "message": message, "data": data})
}
} else {
c.JSON(http.StatusOK, gin.H{"error": "parameter error", "code": proto.ParameterError, "message": "failed"})
return
}
}
// 服务器、设备状态扫描
func ScanDeviceStatus() {
// TODO
// 检查设备状态
// 如果设备状态异常, 则发送邮件通知
devices := worker.GetRedisSetMembers("627gyf3488h")
offline := ""
for _, v := range devices {
c := worker.IsContainKey("monitor_" + v)
if c == false {
worker.SetRedisWithExpire("monitor_"+v, "2", time.Hour*24)
offline += v + ","
}
}
if offline != "" {
title := "设备状态异常"
content := "设备状态异常\n设备: " + offline + "\t时间" + time.Now().String()
go SendMail(title, content)
}
}
func SendMail(title, content string) {
//捕获异常
defer func() {
if err := recover(); err != nil {
fmt.Errorf("tool send mail error: %s", err)
}
}()
// TODO
// 发送邮件
// 邮件内容
// 邮件标题
// 收件人
// 发送邮件
// 发送邮件通知
// 发送邮件通知
var em worker.MyEmail
em.SmtpPassword = "nihzazdkmucnbhid"
em.SmtpHost = "pop.qq.com:587"
em.SmtpUserName = "354425203@qq.com"
em.SmtpPort = 587
em.ImapPort = 993
var targetMails []string
for _, v := range proto.Config.MONITOR_MAIL {
targetMails = append(targetMails, v.Value)
}
if targetMails == nil || len(targetMails) == 0 {
return
}
err := em.Send(title, content, targetMails)
if err != nil {
fmt.Println(err)
}
}
func SendMailTool(c *gin.Context) {
id, _ := c.Get("id")
id1 := int(id.(float64))
var req SendMailReq
if err := c.ShouldBind(&req); err == nil {
user := dao.FindUserByUserID(id1)
if user.ID == 0 {
c.JSON(http.StatusOK, gin.H{"error": "user not found", "code": proto.ParameterError, "message": "failed"})
return
}
//目标邮箱地址是否合法
if !service.CheckEmail(req.To) {
c.JSON(http.StatusOK, gin.H{"error": "email address is invalid", "code": proto.ParameterError, "message": "failed"})
return
}
if req.Title == "" || req.Content == "" {
c.JSON(http.StatusOK, gin.H{"error": "title or content is empty", "code": proto.ParameterError, "message": "failed"})
return
}
//发送邮件
if user.Role == "admin" {
go service.SendEmail(req.To, req.Title, req.Content)
c.JSON(http.StatusOK, gin.H{"code": proto.SuccessCode, "message": "success", "data": "mail will be sent"})
} else {
c.JSON(http.StatusOK, gin.H{"error": "no send mail permission", "code": proto.PermissionDenied, "message": "failed"})
}
} else {
c.JSON(http.StatusOK, gin.H{"error": err.Error(), "code": proto.ParameterError, "message": "failed"})
}
}
// DownloadProxyHandle 处理下载代理请求
func DownloadProxyHandle(c *gin.Context) {
key := c.Query("key")
if key == "" || key != proto.Config.DOWNLOAD_PROXY_KEY {
c.JSON(http.StatusOK, gin.H{"code": proto.ParameterError, "message": "failed, key is null or error"})
}
// 获取URL参数
encodedURL := c.Param("url")
if encodedURL == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "URL参数不能为空", "code": proto.ParameterError, "message": "failed"})
return
}
// URL解码
decodedURL, err := url.QueryUnescape(encodedURL)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "URL解码失败: " + err.Error(), "code": proto.ParameterError, "message": "failed"})
return
}
// 验证URL格式
parsedURL, err := url.Parse(decodedURL)
if err != nil || parsedURL.Scheme == "" || parsedURL.Host == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的URL格式", "code": proto.ParameterError, "message": "failed"})
return
}
// 发起请求获取目标资源
resp, err := http.Get(decodedURL)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "获取资源失败: " + err.Error(), "code": proto.InternalServerError})
return
}
defer resp.Body.Close()
// 检查响应状态
if resp.StatusCode != http.StatusOK {
c.JSON(http.StatusBadGateway, gin.H{"error": "目标资源请求失败,状态码: " + resp.Status, "code": proto.InternalServerError})
return
}
// 获取文件名
filename := getFilenameFromURL(parsedURL)
// 设置响应头,告诉浏览器这是一个文件下载
c.Header("Content-Disposition", "attachment; filename="+url.QueryEscape(filename))
c.Header("Content-Type", resp.Header.Get("Content-Type"))
c.Header("Content-Length", resp.Header.Get("Content-Length"))
// 将响应体流式传输到客户端
_, err = io.Copy(c.Writer, resp.Body)
if err != nil {
// 已经开始传输数据后发生错误无法返回JSON只能记录日志
log.Println("tool download proxy handle error:", err)
}
}
// 从URL中提取文件名
func getFilenameFromURL(u *url.URL) string {
// 从路径中提取文件名
filename := filepath.Base(u.Path)
// 如果路径中没有文件名,尝试从查询参数中获取
if filename == "" || filename == "/" {
filename = "download_file"
}
return filename
}