Go_

  • content {:toc}

我的工作—发布视频

简介

我的工作为发布视频, 将这个任务分解为几个小任务, 先看哪些任务能够自己独立完成,先完成这些任务, 之后再根据小组成员的接口, 实现完整的功能

环境

  • 需要提前安装ffmpeg

发布视频的流程

  • 鉴权 —– 使用别人的接口
  • 存储视频 —- ( demo中 有 )
  • 抽取封面并存储
  • 视频 和 封面 上传到 cdn ( 没有, 因此没有做 )
  • 将收到的视频信息( 视频名称, 用户名, 播放地址, 封面的地址 )存储到数据库中 —- 使用数据库的接口
  • 返回消息 ( 发布成功)

因此最先做的工作就是

  • 确定资源存储的位置, 通过查找路由得知, 有这样一个映射r.Static("/static", "./public"), 我们访问静态资源的时候, 网址为xxx/static/fileName.txt, 通过 c.Request.Host字段, 来获得域名 : 2f781ee3592dd7a9ff0bbd0007fe40ce-app.1024paas.com, 加头(协议)加尾(路径 + 文件名), 进行字符串拼接
  • 使用ffmpeg来抽取某一帧来做封面, 需要设置 其 环境
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// controller/ publish.go
// 没有其他合作者的提供的接口
func Publish(c *gin.Context) {
    // token, 鉴权
 	
    // 存储
    err := c.SaveUploadedFile(data, saveFile);
    
    // 抽取并存储图片
    Vedio2Jpeg(saveFile, 6)
    
    // 拼接网址
    vedio_url := "https://"+ domain + "/static/"+ finalName
    
    // 放入数据库
    db.creat().....
    
}

抽取图片 demo

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//  将视频抽取一帧, 转化为流
package examples

import (
	"bytes"
	"fmt"
	"io"
	"os"

	ffmpeg "github.com/u2takey/ffmpeg-go"
)

func ExampleReadFrameAsJpeg(inFileName string, frameNum int) io.Reader {
	buf := bytes.NewBuffer(nil)
	err := ffmpeg.Input(inFileName).
		Filter("select", ffmpeg.Args{fmt.Sprintf("gte(n,%d)", frameNum)}).
		Output("pipe:", ffmpeg.KwArgs{"vframes": 1, "format": "image2", "vcodec": "mjpeg"}).
		WithOutput(buf, os.Stdout).
		Run()
	if err != nil {
		panic(err)
	}
	return buf
}

队友的接口

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// 鉴权接口
// 参数: token, 返回: username
func ParseToken(token string) (string, error)


// 数据库接口---用户
func (*UserDao) FindUserByName(username string) (*User, error)
func (d *UserDao) FindUserById(id int64) (*User, error) 
_, err := models.NewUserDaoInstance().FindUserByName("qong");

// 数据库接口--- 视频
func (*VideoDao) CreateVideo(video *Video) (*Video, error) 

使用了接口之后的伪代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
func Publish(c *gin.Context) {
    // token, 鉴权
 	token := c.PostForm("token")
    user_name, err := ParseToken(token);
    user, err := models.NewUserDaoInstance().FindUserByName(user_name)
    var user_id = user.UserId
    
    // 存储
    err := c.SaveUploadedFile(data, saveFile);
    
    // 抽取并存储图片
    Vedio2Jpeg(saveFile, 6)
    
    // 拼接网址
    vedio_url := "https://"+ domain + "/static/"+ finalName
    
    // 放入数据库
    video1 := models.Video{UserId : user_id, PlayUrl : vedio_url, CoverUrl : img_url , }
    _, err = models.NewVideoDaoInstance().CreateVideo(&video1)
    
}
主题 描述
计算机基础 数据结构、算法/ 计算机组成原理、计算机网络、操作系统、编译原理
Linux linux基础操作、unix环境编程、网络编程
数据库 基础理论ACID、MySQL、NoSQL
编程语言 Java、Go、C/C++、Rust
设计模式 23种设计模式
中间件 API网关、Web/RPC框架、消息队列、缓存、定时调度、数据库中间件ORM、日志系统、配置中型
分布式 一致性/CAP、事务、等、微服务、拓展性
高并发、高性能、高可用 C10k、进程、线程、线程、异步、池化、缓存、CDN、集群、负载均衡、限流、容灾、多活

linux 命令

  • 查看目录
  • 打开文件
  • 两个命令 awk, sed

SOA(Service-Oriented Architecture) 1.将应用的不同功能单元抽象为服务 2.定义服务之间的通信标准微服务架构:SOA的去中心化演进方向

user

1
2
3
4
5

type Response struct {
	StatusCode int32  `json:"status_code"`
	StatusMsg  string `json:"status_msg,omitempty"`
}

项目分析

service/ message.go

  • tcp 客户端,
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
package service

import .....

// 并发安全的 映射类型
// event.UserId_event.ToUserId
var chatConnMap = sync.Map{}

func RunMessageServer() {
	listen, err := net.Listen("tcp", "127.0.0.1:9090")


	for {
		conn, err := listen.Accept()
		if err != nil {
			fmt.Printf("Accept conn failed: %v\n", err)
			continue
		}

		go process(conn)
	}

}

func process(conn net.Conn) {
	defer conn.Close()

	var buf [256]byte
	for {
		n, err := conn.Read(buf[:])
		if n == 0 {
			if err == io.EOF {
				break
			}
			fmt.Printf("Read message failed: %v\n", err)
			continue
		}
		
        // 在 common.go #L39
        // 请求事件是什么
		var event = controller.MessageSendEvent{}
		_ = json.Unmarshal(buf[:n], &event)
		fmt.Printf("Receive Message:%+v\n", event)

		fromChatKey := fmt.Sprintf("%d_%d", event.UserId, event.ToUserId)
		if len(event.MsgContent) == 0 {
			chatConnMap.Store(fromChatKey, conn)
			continue
		}

		toChatKey := fmt.Sprintf("%d_%d", event.ToUserId, event.UserId)
		writeConn, exist := chatConnMap.Load(toChatKey)
		if !exist {
			fmt.Printf("User %d offline\n", event.ToUserId)
			continue
		}

		pushEvent := controller.MessagePushEvent{
			FromUserId: event.UserId,
			MsgContent: event.MsgContent,
		}
        // 转化为 json 字符串
		pushData, _ := json.Marshal(pushEvent)
		_, err = writeConn.(net.Conn).Write(pushData)
		if err != nil {
			fmt.Printf("Push message failed: %v\n", err)
		}
	}
}

API / 路由/ 基于 HTTP 协议

做的事情有

  • 通过调用 r.Static("/static", "./public") 将 “/static” 路径映射到 “./public” 目录,用于提供静态资源文件。

  • 接下来,通过调用 r.LoadHTMLGlob("templates/*") 加载了所有位于 “templates” 目录下的 HTML 模板文件。

  • 之后,定义了一个处理根路径 ("/") 的 GET 请求的路由

  • 当GET 的路径为douyin/feed/时, 调用controller.Feed, 其他也一样

  • 注意: Gin 默认情况下会返回 404 错误

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
func initRouter(r *gin.Engine) {
  // public directory is used to serve static resources
  r.Static("/static", "./public")
  r.LoadHTMLGlob("templates/*")

  // home page
  r.GET("/", func(c *gin.Context) {
    c.HTML(http.StatusOK, "index.html", gin.H{
			"title": "Main website",
		})
  })
  
  apiRouter := r.Group("/douyin")

  // basic apis
  apiRouter.GET("/feed/", controller.Feed)
  apiRouter.GET("/user/", controller.UserInfo)
  apiRouter.POST("/user/register/", controller.Register)
  apiRouter.POST("/user/login/", controller.Login)
    // -------我的工作在这里----------
  apiRouter.POST("/publish/action/", controller.Publish)
  apiRouter.GET("/publish/list/", controller.PublishList)


}

返回类型

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
var DemoVideos = []Video{
	{
		Id:            1,
		Author:        DemoUser,
        // 静态资源 https://49e48d523a1b2e3ac60f4f7e9aec3628-app.1024paas.com/static/2_mmexport1692111641344.mp4
		PlayUrl:       "https://www.w3schools.com/html/movie.mp4",
		CoverUrl:      "https://cdn.pixabay.com/photo/2016/03/27/18/10/bear-1283347_1280.jpg",
		FavoriteCount: 0,
		CommentCount:  0,
		IsFavorite:    false,
	},

}


type Response struct {
	StatusCode int32  `json:"status_code"`
	StatusMsg  string `json:"status_msg,omitempty"`
}
1
2
3
4
// 存储
func (c *gin.Context) SaveUploadedFile(file *multipart.FileHeader, dst string) error

func (c *gin.Context) JSON(code int, obj interface{})

GORM 使用

go 语言 gorm 基本使用_go gorm_含光君~的博客 - CSDN 博客

其他库

抽取图片

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//  将视频抽取一帧, 转化为流
package examples

import (
	"bytes"
	"fmt"
	"io"
	"os"

	ffmpeg "github.com/u2takey/ffmpeg-go"
)

func ExampleReadFrameAsJpeg(inFileName string, frameNum int) io.Reader {
	buf := bytes.NewBuffer(nil)
	err := ffmpeg.Input(inFileName).
		Filter("select", ffmpeg.Args{fmt.Sprintf("gte(n,%d)", frameNum)}).
		Output("pipe:", ffmpeg.KwArgs{"vframes": 1, "format": "image2", "vcodec": "mjpeg"}).
		WithOutput(buf, os.Stdout).
		Run()
	if err != nil {
		panic(err)
	}
	return buf
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
package main

import (
	"fmt"
	"strings"
)

func main() {
	inFileName := "path/to/video.mp4"

	index := strings.LastIndex(inFileName, ".")
	if index != -1 {
		outFileName := strings.Join([]string{inFileName[:index+1], "jpeg"}, "")
		fmt.Print(outFileName)
	} else {
		fmt.Println("Invalid file name")
	}
}

sync.Map

sync.Map 是 Go 语言提供的一种并发安全的映射(Map)结构。它可以在多个 goroutine 并发访问时保证数据的安全性,而不需要额外的加锁操作。

sync.Map 的主要特点如下:

  1. 无需显式初始化,可以直接声明并使用。
  2. 支持并发的读取和写入操作,可以在多个 goroutine 中同时访问。
  3. 内部自动进行了并发安全的处理,不需要额外的锁机制。
  4. 提供了几个常用的方法来对映射进行操作,如 Store、Load、Delete 等。

需要注意的是,sync.Map 相对于普通的 map 有一些限制:

  1. 不允许使用 nil 作为键值。
  2. 对于存储的值类型没有约束,可以是任意类型。
  3. 没有提供获取映射长度的方法。
Licensed under CC BY-NC-SA 4.0
最后更新于 Oct 13, 2024 18:49 +0800
使用 Hugo 构建
主题 StackJimmy 设计