首页 > 其他分享 >基于gin和gorm框架的流媒体视频网站(1)

基于gin和gorm框架的流媒体视频网站(1)

时间:2024-11-27 23:05:48浏览次数:6  
标签:流媒体 return string err ctx http gin gorm

一、基本配置

1、配置

config.go   使用viper读取config.yml的配置

package config

import (
	"log"

	"github.com/spf13/viper"
)

type Config struct {
	App struct {
		Name string
		Port string
	}
	Streamer struct {
		Name string
		Port string
	}
	Scheduler struct {
		Name string
		Port string
	}
	Web struct {
		Name string
		Port string
	}

	Database struct {
		Host     string
		Port     string
		User     string
		Password string
		Name     string
	}
}

var AppConfig *Config

func InitConfig() {

	viper.SetConfigName("config")
	viper.SetConfigType("yml")
	viper.AddConfigPath("E:/goproject/src/video_server/config")
	if err := viper.ReadInConfig(); err != nil {

		log.Fatalf("Error reading config file :%v", err)
	}//根据设置的配置路径查找文件

	AppConfig = &Config{}  

	if err := viper.Unmarshal(AppConfig); err != nil {
		log.Fatalf("Unable to decode into struct:%v", err)

	}//将配置文件中的内容解码到指定的结构体中将配置文件的内容解码到 AppConfig 结构体指针中。
	InitDB()
	InitRedis()

}

config.yml

App:
  name: video_server
  port: :3001


Streamer:
    name: stream_server
    port: :3007

Scheduler:
    name: scheduler
    port: :3009

Web:
    name: web
    port: :3010

Database:
  host: 192.168.1.177
  port: 3306
  user: root
  password: 1234
  name: video_server


datebase.go  用于初始化数据库

package config

import (
	"fmt"
	"log"
	"time"
	"video_server/global"

	"gorm.io/driver/mysql"
	"gorm.io/gorm"
)

// InitDB 初始化数据库连接
func InitDB() {
	// 如果已经存在数据库连接,直接返回,避免重复连接
	if global.DB != nil {
		fmt.Println("Database connection already initialized.")
		return
	}

	// 从配置中获取数据库连接参数
	user := AppConfig.Database.User
	pwd := AppConfig.Database.Password
	ip := AppConfig.Database.Host
	port := AppConfig.Database.Port
	database := AppConfig.Database.Name

	// 格式化连接字符串
	dsn := fmt.Sprintf("%v:%v@tcp(%v:%v)/%v?charset=utf8mb4&parseTime=True&loc=Local", user, pwd, ip, port, database)

	// 打印连接字符串(可以去掉生产环境中)
	fmt.Println(dsn)

	// 尝试连接数据库
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
		//SkipDefaultTransaction: true, // 禁用事务(根据需要)
	})
	if err != nil {
		log.Fatal("Database connection failed:", err)
	}

	// 获取底层的 sql.DB 实例
	sqlDB, err := db.DB()
	if err != nil {
		log.Fatal("Failed to get SQL DB instance:", err)
	}

	// 配置数据库连接池
	sqlDB.SetMaxIdleConns(10)           // 设置最大空闲连接数
	sqlDB.SetMaxOpenConns(100)          // 设置最大打开连接数
	sqlDB.SetConnMaxLifetime(time.Hour) // 设置连接最大生命周期

	// 将数据库连接赋值给 global.DB
	global.DB = db

	fmt.Println("Database connected successfully.")
}

 全局函数   

package global

import (
	"github.com/go-redis/redis"
	"gorm.io/gorm"
)

var (
	DB      *gorm.DB
	RedisDB *redis.Client
)

2、数据库内容和模型

package models

type VideoInfo struct {
	Id           string `json:"id"`
	AuthorId     int    `json:"author_id"`
	Name         string `json:"name"`
	DisplayCtime string
}


type VideoDelRec struct {
	VideoId string `json:"video_id"`
}

type User struct {
	gorm.Model // 自动包含 ID、CreatedAt、UpdatedAt、DeletedAt

	Pwd       string `json:"pwd"`
	Id        int    `json:"id"`
	LoginName string `json:"login_name" gorm:"unique;not null"` // 唯一且不为空
}
type Comment struct {
	Id      string
	VideoId string
	Author  string
	Content string
}

二、api设置     

实现注册,登录,注销,删除视频,添加视频,添加评论功能

1、路由设置

这里面的路由都上传和删除视频的信息,并不是上传本身

api中的main函数    设置了api的路由

package main

import (
	"video_server/config"

	"video_server/api/router"
)

func main() {

	config.InitConfig()

	r := router.SetupRouter()

	port := config.AppConfig.App.Port

	r.Run(port)

}

router.go   api中的路由,使用了中间件来实现验证是否登录

package router

import (
	"video_server/controllers"
	"video_server/middlewares"

	"github.com/gin-gonic/gin"
)

func SetupRouter() *gin.Engine {

	r := gin.Default()

	auth := r.Group("/api/auth")
	{
		auth.POST("/register", controllers.Register)
		auth.POST("/login", controllers.Login)
		auth.DELETE("/delete/:id", controllers.DeleteUser)

	}

	api := r.Group("/api")
	{
		// 将需要保护的路由放在 api 路由组中,并使用中间件
		api.Use(middlewares.AuthMiddleWare()) //配置中间件
		{
			api.POST("/addnewvideo", controllers.AddNewVideo)
			auth.DELETE("/deletevideo/:id", controllers.DeleteVideo)
			auth.DELETE("/comment/:id", controllers.Deletecomment)
			auth.POST("/addcoment", controllers.Addcomnet)

		}

	}

	return r

}

2、中间件配置

配置中间件 

新建一个utils文件  实现一些基本的功能

产生jwt用于验证

func GenerateJWT(username string) (string, error) {
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
		"username": username,
		"exp":      time.Now().Add(time.Hour * 144).Unix(),
	})

	signedToken, err := token.SignedString([]byte("secret"))
	return "Bearer " + signedToken, err
}

解析jwt   获得username  

func ParseJWT(tokenString string) (string, error) {
	if len(tokenString) > 7 && tokenString[:7] == "Bearer " {
		tokenString = tokenString[7:]
	}

	token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, errors.New("unexpected Signing Method")
		}
		return []byte("secret"), nil
	})

	if err != nil {
		return "", err
	}

	if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
		username, ok := claims["username"].(string)
		if !ok {
			return "", errors.New("username claim is not a string")
		}
		return username, nil
	}

	return "", err
}

生成hash密码 和验证密码函数   

func HashPassword(pwd string) (string, error) {

	hash, err := bcrypt.GenerateFromPassword([]byte(pwd), 12)

	return string(hash), err

}
func CheckPassword(password string, hash string) bool {
	err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
	return err == nil
}

建立控制器,实现路由的功能

3、路由配置

(1)注册

func Register(ctx *gin.Context) { //POST
	var user models.User

	if err := ctx.ShouldBindJSON(&user); err != nil {
		ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	hashedPwd, err := utils.HashPassword(user.Pwd)

	if err != nil {
		ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	user.Pwd = hashedPwd

	token, err := utils.GenerateJWT(user.LoginName)

	if err != nil {
		ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	if err := global.DB.AutoMigrate(&user); err != nil {
		ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	if err := global.DB.Create(&user).Error; err != nil {
		ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	ctx.JSON(http.StatusOK, gin.H{"token": token})
}

(2)登录

func Login(ctx *gin.Context) {
	var input struct {
		Id  int    `json:"id"`
		Pwd string `json:"pwd"`
	}

	if err := ctx.ShouldBindJSON(&input); err != nil {
		ctx.JSON(http.StatusBadRequest, gin.H{
			"error7": err.Error(),
		})
		return

	}

	var user models.User
	if err := global.DB.Where("id=?", input.Id).First(&user).Error; err != nil {
		ctx.JSON(http.StatusUnauthorized, gin.H{"error8": "wrong creditinals"})
		return
	}
	if !utils.CheckPassword(input.Pwd, user.Pwd) {
		ctx.JSON(http.StatusUnauthorized, gin.H{"error9": "wrong creditinals"})
		return
	}

	token, err := utils.GenerateJWT(user.LoginName)
	if err != nil {
		ctx.JSON(http.StatusInternalServerError, gin.H{

			"error8": err.Error(),
		})
		return
	}

	ctx.JSON(http.StatusOK, gin.H{

		"token": token,
	})

}

(3)注销


func DeleteUser(ctx *gin.Context) {

	id := ctx.Param("id")

	global.DB.Exec("delete from users where id=?", id)

	// 成功删除时返回消息
	ctx.JSON(http.StatusOK, gin.H{"message": "User deleted", "id": id})
}

(4)添加视频

func AddNewVideo(ctx *gin.Context) {
	var videoinfo models.VideoInfo

	// 绑定请求体中的 JSON 数据
	if err := ctx.ShouldBindJSON(&videoinfo); err != nil {
		ctx.JSON(http.StatusBadRequest, gin.H{"error": "Invalid JSON"})
		return
	}
	if err := global.DB.AutoMigrate(&videoinfo); err != nil {
		ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	if err := global.DB.Create(&videoinfo).Error; err != nil {
		ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	ctx.JSON(200, "success")
}

(5)删除视频

func DeleteVideo(ctx *gin.Context) {
	id := ctx.Param("id")

	global.DB.Exec("delete from video_infos where id=?", id)

	// 成功删除时返回消息
	ctx.JSON(http.StatusOK, gin.H{"message": "Video deleted", "id": id})
}

(6)添加评论

func Addcomnet(ctx *gin.Context) {

	var comment models.Comment
	if err := ctx.ShouldBindJSON(&comment); err != nil {
		ctx.JSON(http.StatusBadRequest, gin.H{"error": "Invalid JSON"})
		return
	}

	if err := global.DB.AutoMigrate(&comment); err != nil {
		ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	if err := global.DB.Create(&comment).Error; err != nil {
		ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
}

(6)删除评论

func Deletecomment(ctx *gin.Context) {

	id := ctx.Param("id")
	global.DB.Exec("delete from comments where id=?", id)

}

三、stream设置

main函数都是一样的,把使用的端口改一下就可以,

1、路由设置   

实现更新视频和上传视频的功能

package router

import (
	"time"
	"video_server/controllers"
	"video_server/middlewares"

	"github.com/gin-contrib/cors"

	"github.com/gin-gonic/gin"
)

func SetupRouter() *gin.Engine {

	r := gin.Default()

	r.Use(cors.New(cors.Config{
		AllowOrigins:     []string{"http://localhost:5175"},
		AllowMethods:     []string{"GET", "POST", "OPTIONS", "DELETE"},
		AllowHeaders:     []string{"Origin", "Content-Type", "Authorization"},
		ExposeHeaders:    []string{"Content-Length"},
		AllowCredentials: true,
		MaxAge:           12 * time.Hour,
	}))

	connliomiter := middlewares.NewCoonLimiter(10)

	api := r.Group("/stream")
	{
		// 将需要保护的路由放在 api 路由组中,并使用中间件

		api.Use(connliomiter.Middleware()) //配置中间件
		{

			api.GET("/videos/:vid-id", controllers.StreamHandler)

			api.POST("/update/:vid-id", controllers.UploadVideoHandler)

		}

	}

	return r

}

2、中间件配置

实现一个连接限制中间件 ConnLimiter,用于限制并发连接数。

package middlewares

import (
	"log"
	"net/http"

	"github.com/gin-gonic/gin"
)

type ConnLimiter struct {
	concurrentCoon int
	bucket         chan int
}

func NewCoonLimiter(cc int) *ConnLimiter {

	return &ConnLimiter{
		concurrentCoon: cc,
		bucket:         make(chan int, cc), // 初始化缓冲通道,容量为并发连接数 cc
	}
}
func (cl *ConnLimiter) GetCoon() bool {
	if len(cl.bucket) >= cl.concurrentCoon {
		log.Printf("reached the rate limitation")
		return false

	}
	cl.bucket <- 1 //给通道发放如一个值
	return true

}
func (cl *ConnLimiter) ReleaseCoon() {

	c := <-cl.bucket
	log.Printf("new connction coming:%d", c)

}

func (cl *ConnLimiter) Middleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		// 尝试获取一个连接
		if !cl.GetCoon() {
			// 如果获取连接失败,返回 429 错误
			c.JSON(http.StatusTooManyRequests, gin.H{
				"message": "Too many requests. Please try again later.",
			})
			c.Abort() // 阻止进一步处理请求
			return
		}

		// 如果成功获取连接,继续处理请求
		c.Next()

		// 请求处理完后释放连接
		cl.ReleaseCoon()
	}
}

3、路由配置

(1)获取视频

func StreamHandler(ctx *gin.Context) {
	// 获取视频ID参数
	vid := ctx.Param("vid-id")
	vl := "E:/goproject/src/video_server/videos/" + vid + ".mp4"

	// 打开视频文件
	video, err := os.Open(vl)
	if err != nil {
		// 如果文件打开失败,返回 500 错误
		ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	// 获取文件信息
	fileInfo, err := video.Stat()
	if err != nil {
		// 如果无法获取文件信息,返回 500 错误
		ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	// 获取文件的最后修改时间
	lastModified := fileInfo.ModTime()

	// 设置响应头,表明返回的是 MP4 格式的视频
	ctx.Header("Content-Type", "video/mp4")

	// 使用 http.ServeContent 来流式传输视频文件
	http.ServeContent(ctx.Writer, ctx.Request, vid, lastModified, video)

	//http.ServeContent(ctx.Writer, ctx.Request, vid, lastModified, video)
	defer video.Close()

}

(2)更新视频


func UploadVideoHandler(ctx *gin.Context) {
	// 限制上传文件的最大大小为 100 MB
	const maxSize = 100 * 1024 * 1024 // 100 MB

	// 使用 http.MaxBytesReader 限制上传请求体的大小
	ctx.Request.Body = http.MaxBytesReader(ctx.Writer, ctx.Request.Body, maxSize)

	// 获取上传的文件
	file, err := ctx.FormFile("file")
	if err != nil {
		// 处理上传错误
		if err.Error() == "http: request body too large" {
			ctx.JSON(http.StatusRequestEntityTooLarge, gin.H{"error": "文件过大,最大支持 100MB"})
		} else {
			ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		}
		return
	}

	// 限制文件类型为 .mp4 和 .avi
	ext := filepath.Ext(file.Filename)
	if ext != ".mp4" && ext != ".avi" {
		ctx.JSON(http.StatusBadRequest, gin.H{"error": "只支持 .mp4 和 .avi 格式的文件"})
		return
	}

	// 指定视频保存的路径
	videoDir := "E:/goproject/src/video_server/videos"

	// 确保目录存在,如果不存在则创建它
	if err := os.MkdirAll(videoDir, os.ModePerm); err != nil {
		ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}
	vid := ctx.Param("vid-id")
	if vid == "" {
		ctx.JSON(http.StatusInternalServerError, "Missing video ID")
		return
	}

	// 生成保存文件的路径,使用文件的原始名称
	dst := filepath.Join(videoDir, vid)

	// 保存文件到服务器
	if err := ctx.SaveUploadedFile(file, dst); err != nil {
		ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
		return
	}

	// 返回上传成功的响应
	ctx.JSON(http.StatusOK, gin.H{
		"message": "文件上传成功",
		"file":    dst,
	})
}

标签:流媒体,return,string,err,ctx,http,gin,gorm
From: https://blog.csdn.net/2302_77013343/article/details/144078587

相关文章

  • RK3568平台开发系列讲解(DMA篇)DMA engine使用
    ......
  • PyQt5控件QWebEngineView(WebView)
    PyQt5控件QWebEngineView(WebView)下载依赖PyQt5、PyQtWebEnginepipinstall--index-url=https://mirrors.aliyun.com/pypi/simple/PyQt5pipinstall--index-url=https://mirrors.aliyun.com/pypi/simple/PyQtWebEngine加载外部网页importsysfromPyQt5.QtCore......
  • vue3+vite打包配置,并部署nginx,解决部署之后axios请求跨域
    配置base配置base避免打包部署到服务器上后可能会报404,无法正确的获取的资源。//vite.config.tsexportdefaultdefineConfig({ base:"./"})配置路径别名//vite.config.tsimport{defineConfig}from"vite";importvuefrom"@vitejs/plugin-vue";//配置组件路......
  • Prometheus Operator自定义监控对象 -- Ingress-Nginx
    PrometheusOperator自定义监控对象--Ingress-Nginx一、自定义资源Prometheus-operator通过定期循环watchapiserver,获取到CRD资源(比如servicemonitor)的创建或者更新,将配置更新及时应用到运行中的prometheuspod中转换成标准promethesu配置文件供prometheusserver使用。各......
  • 通过并行nologging等快速创建大表备份
    redhat6.5+oracle11.2.0.4rac+96cpu,256g内存[root@dbjyc]#cat20200527.sh #!/bin/shsu-oracle-c"sqlplus/nolog<<EOF@/home/oracle/jyc/20200527.sql;exit;EOF"[root@dbjyc]#cat20200527.sqlsettimeonsettimingonconnuser/passwor......
  • Linux搭建nginx+keepalived 高可用(主备+双主模式)
    keepalived简介反向代理及负载均衡参考:nginx反向代理与负载均衡当你了解会搭建nginx负载均衡后,需要考虑nginx这台服务器的安全性啦,如果只有一台,这台nginx一出问题,web就会无法访问的情况,所以为了应对这种情况,就需要两台nginx做主备服务器。nginx+keepalived如下图所示 在nginx......
  • H.265流媒体播放器EasyPlayer.js播放器关于播放内网https地址报错(ERR_CERT_COMMON_NA
    随着技术的发展,越来越多的H5流媒体播放器开始支持H.265编码格式。例如,EasyPlayer.jsH5播放器能够支持H.264、H.265等多种音视频编码格式,这使得播放器能够适应不同的视频内容和网络环境。那么播放内网https地址报错(ERR_CERT_COMMON_NAME_INVALID错误)怎么处理?一般这种情况是浏览......
  • nginx安装及负载均衡配置
    下载http://nginx.org/en/download.html nginx的负载均衡策略轮询(默认)每个请求按照请求时间顺序分配到不同的后端服务器,如果后端服务器挂了,则自动剔除。此策略还可以设置:权重,指定轮询的频率,weight和访问率成正比,用于后端服务器性能不均匀的情况。ip_hash客户端ip地址被用......
  • H5流媒体播放器EasyPlayer.js播放器关于苹果iOS系统webglcontextlost的问题(ios内核的b
    随着流媒体技术的迅速发展,H5流媒体播放器已成为现代网络视频播放的重要工具。其中,EasyPlayer.js视频流媒体播放器作为一款功能强大的H5播放器,凭借其全面的协议支持、多种解码方式以及跨平台兼容性,赢得了广泛的关注和应用。有时苹果iOS系统会出现webglcontextlost的问题(ios内核的......
  • 时序数据库tdengine部署说明
    TDengine是一款开源、高性能、云原生的时序数据库(TimeSeriesDatabase,TSDB)。参考文档:https://docs.taosdata.com/目录单节点部署docker-compose启动连接测试集群部署集群规划部署过程初始化配置文件设置firstEp启动集群验证添加管理节点冗余nginx负载均衡部署单节点部......