golang中判断两个slice是否相等

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
package main

import (
    "bytes"
    "fmt"
)

func main() {
    a := []byte{0, 1, 3, 2}
    b := []byte{0, 1, 3, 2}
    c := []byte{1, 1, 3, 2}

    fmt.Println(bytes.Equal(a, b))
    fmt.Println(bytes.Equal(a, c))
}

计算中文长度

1
2
3
title := "中国人a"
fmt.Println(len([]rune(title))) // 第一种方案:输出 4
fmt.Println(utf8.RuneCountInString(title)) // 第二种方案:输出 4

数据库初始化

什么是 DSN 信息?

DSN 全称为 Data Source Name,表示 数据源信息,用于定义如何连接数据库。不同数据库的 DSN 格式是不同的,这取决于数据库驱动的实现,下面是 go-sql-driver/sql 的 DSN 格式,如下所示:

1
2
// [用户名[:密码]@][协议(数据库服务器地址)]]/数据库名称?参数列表
[username[:password]@][protocol[(address)]]/dbname[?param1=value1&...&paramN=valueN]

如何配置 sql. DB 的 SetMaxOpenConns SetMaxIdleConns 和 SetConnMaxLifetime

如何配置 sql. DB 的 SetMaxOpenConns SetMaxIdleConns 和 SetConnMaxLifetime

 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
import (
    "github.com/go-sql-driver/mysql"
    _ "github.com/go-sql-driver/mysql" // 引入 mysql 驱动
)

// 初始化单个数据库
func initDB() {
    var err error

    // 设置数据库连接信息
    config := mysql.Config{
        User:                    "root",
        Passwd:                  "123456",
        Net:                     "tcp",
        Addr:                    "127.0.0.1:3305",
        DBName:                  "goblog",
        AllowNativePasswords:    true,
    }

    // 准备数据库连接池
    db, err = sql.Open("mysql", config.FormatDSN())
    checkError(err)

    // 设置最大连接数,不能超过数据库本身设置的最大连接数 `show variables like 'max_connections';`
    db.SetMaxOpenConns(200) // yim 设置了 512
    // 设置最大空闲连接数
    db.SetMaxIdleConns(100) // yim 设置了 256
    // 设置每个连接的过期时间,这里的推荐,比较保守的做法是设置五分钟
    db.SetConnMaxLifetime(5 * time.Minute)

    err = db.Ping()
    checkError(err)
}

func checkError(err error) {
    if err != nil {
        log.Fatal(err)
    }
}

根据结构体返回字段名切片

 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
func main()  {
    // 输出:[ `name`  `password`  `gender`  `create_time`  `update_time`  `id`  `number` ]
    fmt.Println(RawFieldNames(&User{}))
}

const dbTag = "db" // 根据哪个标签获取字段名称

type User struct {
    Name       string `db:"name"` // 用户名称
    Password   string `db:"password"` // 用户密码
    Gender     string `db:"gender"` // 男|女|未公开
    CreateTime time.Time `db:"create_time"`
    UpdateTime time.Time `db:"update_time"`
    Id         int64 `db:"id"`
    Number     string `db:"number"` // 学号
}

func RawFieldNames(in interface{}) []string {
    out := make([]string, 0)
    v := reflect.ValueOf(in)
    if v.Kind() == reflect.Ptr {
        v = v.Elem()
    }

    // we only accept structs
    if v.Kind() != reflect.Struct {
        panic(fmt.Errorf("ToMap only accepts structs; got %T", v))
    }

    typ := v.Type()
    for i := 0; i < v.NumField(); i++ {
        // gets us a StructField
        fi := typ.Field(i)
        if tagv := fi.Tag.Get(dbTag); tagv != "" {
            out = append(out, fmt.Sprintf(" `%s` ", tagv))
        } else {
            out = append(out, fmt.Sprintf( `"%s"` , fi.Name))
        }
    }

    return out
}

移除数组元素

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
func Remove(strings []string, strs ...string) []string {
    out := append([]string(nil), strings...)

    for _, str := range strs {
        var n int
        for _, v := range out {
            if v != str {
                out[n] = v
                n++
            }
        }
        out = out[:n]
    }

    return out
}

超时处理 & 定时器

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// 超时处理
select {
    case <-ch:
    case <-time.After(time.Second * 1): // 1秒后超时触发
        fmt.Println("timeout")
}

// 定时器
count := 0
ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop()
for {
    <-ticker.C
    fmt.Println(count)
    count++
    if count >= 5 {
        break
    }
}

获取真实IP

 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
func GetRemoteAddr(r *http.Request) string {
    v := r.Header.Get("X-Forward-For")
    if len(v) > 0 {
        return v
    }
    return r.RemoteAddr
}

// 获取本机ip
var ip string
func GetLocalIP() string {
    if ip != "" {
        return ip
    }
    addrs, err := net.InterfaceAddrs()
    if err != nil {
        return ""
    }
    for _, address := range addrs {
        // check the address type and if it is not a loopback the display it
        if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
            if ipnet.IP.To4() != nil {
                ip = ipnet.IP.String()
                return ip
            }
        }
    }
    return ""
}

md5 && hmac

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
func Md5(str string) string {
    h := md5.New()
    h.Write([]byte(str))
    return hex.EncodeToString(h.Sum(nil))
}

func HmacSha256(data string, secret string) string {
    h := hmac.New(sha256.New, []byte(secret))
    h.Write([]byte(data))
    return hex.EncodeToString(h.Sum(nil))
}

// 简单的密码加密方案
func makePassword(pwd string) string {
    return helper.Md5(helper.HmacSha256("盐", pwd))
}

利用反射,结构体转map方法

 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
// 结构体转map方法
func Struct2Map(obj interface{}) map[string]interface{} {
    t := reflect.TypeOf(obj)
    v := reflect.ValueOf(obj)

    var data = make(map[string]interface{})
    if t.Kind() != reflect.Struct {
        return data
    }

    for i := 0; i < t.NumField(); i++ {
        data[t.Field(i).Name] = v.Field(i).Interface()
    }

    return data
}

// 结构体转map方法,key直接取结构体上变量名的 json 名
func Struct2MapJson(obj interface{}) map[string]interface{} {
    t := reflect.TypeOf(obj)
    v := reflect.ValueOf(obj)

    var data = make(map[string]interface{})
    if t.Kind() != reflect.Struct {
        return data
    }

    for i := 0; i < t.NumField(); i++ {
        data[t.Field(i).Tag.Get("json")] = v.Field(i).Interface()
    }
    return data
}

// 结构体转map方法,处理嵌套结构体
func StructToMapByJson(obj interface{}) map[string]interface{} {
    t := reflect.TypeOf(obj)
    v := reflect.ValueOf(obj)

    var data = make(map[string]interface{})
    if t.Kind() != reflect.Struct {
        return data
    }

    for i := 0; i < t.NumField(); i++ {
        value := v.Field(i)

        // 处理嵌套结构体
        t2 := reflect.TypeOf(value.Interface())
        if t2.Kind() == reflect.Struct {
            for j := 0; j < t2.NumField(); j++ {
                temp := t2.Field(j)
                _ = temp
                data[t2.Field(j).Tag.Get("json")] = value.Field(j).Interface()
            }
            continue
        }

        data[t.Field(i).Tag.Get("json")] = value.Interface()
    }

    return data
}

同步队列+并发消费

 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
package main

import (
    "context"
    "fmt"
    "sync"
    "time"
)

// 同步队列+并发消费案例
type (
    Task struct {
        StartTime time.Time
    }

    TaskManager struct {
        Tasks chan *Task
        Wg    *sync.WaitGroup
        Ctx   context.Context
    }
)

func main() {
    timeoutCtx, cancel := context.WithTimeout(context.Background(), 10*time.Minute) // 10分钟超时
    defer cancel()

    var manager = &TaskManager{
        Tasks: make(chan *Task, 10), // 等待队列10,并发5 执行任务
        Wg:    &sync.WaitGroup{},
        Ctx:   timeoutCtx,
    }

    runDaemon(manager)

    // 生产者:负责生产任务
    var tasks []*Task
    tasks = append(tasks, &Task{})
    for i := 0; i < 20; i++ {
        manager.Wg.Add(1)
        manager.Tasks <- &Task{StartTime: time.Now()}
    }

    manager.Wg.Wait()
}

func runDaemon(manager *TaskManager) {
    // 并发 5 个协程消费队列
    for i := 0; i < 5; i++ {
        go func() {
            for {
                select {
                case task := <-manager.Tasks:
                    // todo 执行相关业务逻辑
                    fmt.Println("消费任务:start_time:", task.StartTime.String())
                    manager.Wg.Done()
                case <-manager.Ctx.Done():
                    return
                }
            }
        }()
    }
}

interface{}变量转为字符串值

 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
// Strval 获取变量的字符串值
// 浮点型 3.0将会转换成字符串3, "3"
// 非数值或字符类型的变量将会被转换成JSON格式字符串
func Strval(value interface{}) string {
    // interface 转 string
    var key string
    if value == nil {
        return key
    }

    switch value.(type) {
    case float64:
        ft := value.(float64)
        key = strconv.FormatFloat(ft, 'f', -1, 64)
    case float32:
        ft := value.(float32)
        key = strconv.FormatFloat(float64(ft), 'f', -1, 64)
    case int:
        it := value.(int)
        key = strconv.Itoa(it)
    case uint:
        it := value.(uint)
        key = strconv.Itoa(int(it))
    case int8:
        it := value.(int8)
        key = strconv.Itoa(int(it))
    case uint8:
        it := value.(uint8)
        key = strconv.Itoa(int(it))
    case int16:
        it := value.(int16)
        key = strconv.Itoa(int(it))
    case uint16:
        it := value.(uint16)
        key = strconv.Itoa(int(it))
    case int32:
        it := value.(int32)
        key = strconv.Itoa(int(it))
    case uint32:
        it := value.(uint32)
        key = strconv.Itoa(int(it))
    case int64:
        it := value.(int64)
        key = strconv.FormatInt(it, 10)
    case uint64:
        it := value.(uint64)
        key = strconv.FormatUint(it, 10)
    case string:
        key = value.(string)
    case []byte:
        key = string(value.([]byte))
    default:
        newValue, _ := json.Marshal(value)
        key = string(newValue)
    }

    return key
}

map实现集合方案

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 第一种
m := make(map[string]bool)
m["a"] = true
if m["a"] {
    fmt.Println("a exist")
}
if m["b"] {
    fmt.Println("b exist")
} else {
    fmt.Println("b no exist")
}

// 第二种
m2 := make(map[string]struct{})
m2["c"] = struct{}{}
if _, ok := m2["c"]; ok {
    fmt.Println("c exist")
}
if _, ok := m2["d"]; ok {
    fmt.Println("d exist")
} else {
    fmt.Println("d no exist")
}

浮点数四舍五入

 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
// Round 四舍五入
// 返回将 val 根据指定精度 precision(十进制小数点后数字的数目)进行四舍五入的结果,precision可为0
func Round(val float64, precision int) float64 {
    p := math.Pow10(precision)
    return math.Floor(val*p+0.5) / p
}

// 元转分
func Yuan2Fen(f float64) int64 {
    if f == 0 {
        return 0
    }
    decimalValue := decimal.NewFromFloat(f)
    decimalValue = decimalValue.Mul(decimal.NewFromInt(100))
    return decimalValue.BigInt().Int64()
}

// 分转元
func FenToYuan(i int64) string {
    if i == 0 {
        return "0.00"
    }
    decimalValue := decimal.NewFromInt(i)
    decimalValue = decimalValue.Div(decimal.NewFromInt(100))
    return decimalValue.String()
}

url编码特殊处理

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// url.QueryEscape 不会编译“~”字符,但php那边会编译成"%7E",所以需要手动转换
func urlEncode(str string) string {
    specialChar := map[string]string{
        "~": "%7E",
    }
    for k, v := range specialChar {
        str = strings.Replace(url.QueryEscape(str), k, v, -1)
    }
    return str
}

下载网络文件到本地

 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
70
71
72
// 下载文件到本地
// url 网络文件链接
// filePath 本地文件存放路径,如/Users/xxx/Downloads/test.wav
func DownLoad(url, filePath string) error {
    // Get the data
    resp, err := http.Get(url)
    if err != nil {
        return err
    }
    defer resp.Body.Close()
    // Create output file
    out, err := os.Create(filePath)
    if err != nil {
        return err
    }
    defer out.Close()
    // copy stream
    _, err = io.Copy(out, resp.Body)
    if err != nil {
        return err
    }
    return nil
}

// 获取网络文件的大小-链接(返回以字节为单位的大小)
func GetFileSize(url string) (int64, error) {
    // 发送 HEAD 请求
    resp, err := http.Head(url)
    if err != nil {
        return 0, fmt.Errorf("无法获取文件信息: %v", err.Error())
    }
    defer resp.Body.Close()

    // 检查响应状态码
    if resp.StatusCode != http.StatusOK {
        return 0, fmt.Errorf("请求失败,状态码: %d", resp.StatusCode)
    }

    // 从响应头中获取 Content-Length
    contentLength := resp.Header.Get("Content-Length")
    if contentLength == "" {
        // 下载文件获取文件大小
        ext := GetUrlFileExt(url)
        filePath := config.TmpFilePath + "/" + uuid.New().String() + ext
        if err := DownLoad(url, filePath); err != nil {
            return 0, err
        }
        if l, err := GetFileSizeLocal(filePath); err != nil {
            return 0, err
        } else {
            return l, nil
        }
    }

    l, err := strconv.ParseInt(contentLength, 10, 64)
    if err != nil {
        return 0, fmt.Errorf("文件大小异常:%s", contentLength)
    }

    return l, nil
}

// 获取网络文件的大小-本地(返回以字节为单位的大小)
func GetFileSizeLocal(filePath string) (int64, error) {
    // 获取文件信息
    fileInfo, err := os.Stat(filePath)
    if err != nil {
        return 0, fmt.Errorf("无法获取文件信息: %v", err.Error())
    }

    return fileInfo.Size(), nil
}

获取链接后缀

1
2
3
4
5
func main() {
    url := "https://example.com/files/document.pdf"
    fmt.Println(path.Base(url)) // 输出 document.pdf
    fmt.Println(path.Ext(url))  // 输出 .pdf
}

用正则表达式处理html代码

 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
// 去掉html代码中的标签,只保留纯文本
func TrimHtml(src string) string {
    //将HTML标签全转换成小写
    re, _ := regexp.Compile(`<[\S\s]+?>`)
    src = re.ReplaceAllStringFunc(src, strings.ToLower)
    //去除STYLE
    re, _ = regexp.Compile(`<style[\S\s]+?</style>`)
    src = re.ReplaceAllString(src, "")
    //去除SCRIPT
    re, _ = regexp.Compile(`<script[\S\s]+?</script>`)
    src = re.ReplaceAllString(src, "")
    //去除所有尖括号内的HTML代码
    re, _ = regexp.Compile(`<[\S\s]+?>`)
    src = re.ReplaceAllString(src, "")
    //去除连续的换行符
    re, _ = regexp.Compile(`\s{2,}`)
    src = re.ReplaceAllString(src, "\n\n")
    return strings.TrimSpace(src)
}

// 获取html代码中的图片链接
func GetHtmlImages(htmls string) []string {
    var imgRE = regexp.MustCompile(`<img[^>]+\bsrc=["']([^"']+)["']`)
    imgs := imgRE.FindAllStringSubmatch(htmls, -1)
    out := make([]string, len(imgs))
    for i := range out {
        out[i] = imgs[i][1]
    }
    return out
}

对称加密算法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// sha1加密
func Sha1(s string) string {
    h := sha1.New()
    h.Write([]byte(s))
    return base64.StdEncoding.EncodeToString(h.Sum(nil))
}

// HMACSHA1是从 SHA1 哈希函数构造的一种键控哈希算法
func HMACSHA1(secret, value string) string {
    key := []byte(secret)
    mac := hmac.New(sha1.New, key)
    mac.Write([]byte(value))
    return base64.StdEncoding.EncodeToString(mac.Sum(nil))
}

获取文件链接URL的后缀

1
2
3
4
5
6
7
8
9
// 获取文件链接的后缀
func GetUrlFileExt(requestUrl string) string {
    parsedURL, err := url.Parse(requestUrl)
    if err != nil {
        return ""
    }
    fileName := filepath.Base(parsedURL.Path)
    return filepath.Ext(fileName)
}

ffmpeg & mediainfo

  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
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
/**
 * @description 使用mediainfo计算本地音频或视频的时长,比ffmpeg更精确到毫秒
 * @method GetMediaDurationByMediainfo
 * @param filePath string 本地音频或视频文件路径
 * @return float64 时长(秒),错误信息
 */
func GetAudioDurationLocal(filePath, mediainfoPath string) (float64, error) {
     var duration float64

     // 检查文件是否存在
     if _, err := os.Stat(filePath); os.IsNotExist(err) {
          return duration, fmt.Errorf("文件不存在: %s", filePath)
     }

     // 执行mediainfo命令,输出JSON格式
     cmd := exec.Command(mediainfoPath, "--Output=JSON", filePath)
     out, err := cmd.CombinedOutput()
     if err != nil {
          return duration, fmt.Errorf("执行mediainfo命令失败: %v, 输出: %s", err, string(out))
     }

     // 解析JSON输出
     var mediainfoOutput struct {
          Media struct {
               Track []struct {
                    Type     string `json:"@type"`
                    Duration string `json:"Duration"`
               } `json:"track"`
          } `json:"media"`
     }

     if err = json.Unmarshal(out, &mediainfoOutput); err != nil {
          return duration, fmt.Errorf("解析mediainfo JSON输出失败: %v", err)
     }

     // 查找General或Video或Audio类型的track,获取Duration
     // Duration字段可能为毫秒或秒,需要根据实际情况判断
     var durationStr string
     for _, track := range mediainfoOutput.Media.Track {
          // 优先使用General类型的Duration,如果没有则使用Video或Audio
          if track.Type == "General" && track.Duration != "" {
               durationStr = track.Duration
               break
          } else if (track.Type == "Video" || track.Type == "Audio") && track.Duration != "" && durationStr == "" {
               durationStr = track.Duration
          }
     }

     if durationStr == "" {
          return duration, fmt.Errorf("无法从mediainfo输出中获取Duration信息")
     }

     // 处理Duration字符串,可能包含单位(如 "12565 ms" 或 "12.565 s")
     durationStr = strings.TrimSpace(durationStr)
     durationStrLower := strings.ToLower(durationStr)

     var durationFloat float64

     // 检查是否包含单位标识
     if strings.Contains(durationStrLower, "ms") || strings.Contains(durationStrLower, "毫秒") {
          // 包含毫秒单位,提取数值并转换为秒
          // 移除单位标识,提取数字部分
          durationStr = strings.TrimSpace(strings.TrimSuffix(strings.TrimSuffix(durationStrLower, "ms"), "毫秒"))
          durationStr = strings.TrimSpace(strings.TrimSuffix(durationStr, "s")) // 移除可能的 "s"
          durationFloat, err = strconv.ParseFloat(durationStr, 64)
          if err != nil {
               return duration, fmt.Errorf("解析Duration值失败: %v, 原始值: %s", err, durationStr)
          }
          duration = durationFloat / 1000.0
     } else if strings.HasSuffix(durationStrLower, "s") && !strings.HasSuffix(durationStrLower, "ms") || strings.Contains(durationStrLower, "秒") {
          // 包含秒单位(但不包含ms),提取数值即可
          durationStr = strings.TrimSpace(strings.TrimSuffix(durationStrLower, "s"))
          durationStr = strings.TrimSpace(strings.TrimSuffix(durationStr, "秒"))
          durationFloat, err = strconv.ParseFloat(durationStr, 64)
          if err != nil {
               return duration, fmt.Errorf("解析Duration值失败: %v, 原始值: %s", err, durationStr)
          }
          duration = durationFloat
     } else {
          // 不包含单位,需要判断是毫秒还是秒
          // 通常mediainfo的Duration是毫秒,但如果值很小(小于1000),可能是秒
          durationFloat, err = strconv.ParseFloat(durationStr, 64)
          if err != nil {
               return duration, fmt.Errorf("解析Duration值失败: %v, 原始值: %s", err, durationStr)
          }

          // 如果值小于1000,可能是秒;否则认为是毫秒
          // 注意:对于小于1秒的音频(如0.5秒),mediainfo可能返回500(毫秒)或0.5(秒)
          // 这里采用保守策略:如果值小于1000,认为是秒;否则认为是毫秒
          if durationFloat < 1000 {
               duration = durationFloat
          } else {
               duration = durationFloat / 1000.0
          }
     }

     return duration, nil
}

// 获取音频宽高-本地文件(eg:ffprobe -v error -select_streams v:0 -show_entries stream=width,height -of csv=p=0 /Users/xxx/Downloads/xxx.mp4)
func GetMediaSizeLocal(filepath, ffprobePath, bashPath string) (int64, int64, error) {
    var width, height int64

    cmd := exec.Command(bashPath, "-c", ffprobePath+" -v error -select_streams v:0 -show_entries stream=width,height -of csv=p=0 "+filepath)
    out, err := cmd.CombinedOutput()
    if err != nil {
        return width, height, errors.New("cmd err: " + err.Error() + "->" + string(out))
    }
    sieStr := strings.TrimSpace(string(out))
    sizeArr := strings.Split(sieStr, ",")
    if len(sizeArr) < 2 {
        return width, height, nil
    }
    height, _ = strconv.ParseInt(sizeArr[0], 10, 64)
    width, _ = strconv.ParseInt(sizeArr[1], 10, 64)
    return width, height, nil
}

// 从视频中提取音频-链接
func GetAudioFile(url, filepath, ffmpegPath, bashPath string) (string, error) {
    var (
        audioFile string
        err       error
    )
    ext := GetUrlFileExt(url)
    if ext == "" {
        ext = ".mp4"
    }
    videoFile := filepath + "/" + uuid.New().String() + ext

    if err := DownLoad(url, videoFile); err != nil {
        return audioFile, err
    }

    audioFile, err = GetAudioFileLocal(videoFile, filepath, ffmpegPath, bashPath)
    if err != nil {
        return audioFile, err
    }

    if err := os.Remove(videoFile); err != nil {
        return audioFile, errors.New("remove err: " + err.Error())
    }

    return audioFile, nil
}

// 从视频中提取音频-本地文件(eg:ffmpeg -i /Users/xxx/Downloads/xxx.mp4 -f mp3 -vn /Users/xxx/Downloads/xxx.mp3)
func GetAudioFileLocal(videoPath, filepath, ffmpegPath, bashPath string) (string, error) {
    audioFile := filepath + "/" + uuid.New().String() + ".mp3"
    cmd := exec.Command(bashPath, "-c", ffmpegPath+" -i "+videoPath+" -f mp3 -vn "+audioFile)
    out, err := cmd.CombinedOutput()
    if err != nil {
        return audioFile, errors.New("cmd err: " + err.Error() + "->" + string(out))
    }
    return audioFile, nil
}

// 转换音视频格式-链接
// formatExt 转换格式后缀,如.mp3, .mp4
func TransferMediaFormat(formatExt, url, filepath, ffmpegPath, bashPath string) (string, error) {
    var (
        outputFile string
        err        error
    )
    ext := GetUrlFileExt(url)
    if ext == "" {
        ext = formatExt
    }
    mediaFile := filepath + "/" + uuid.New().String() + ext

    if err := DownLoad(url, mediaFile); err != nil {
        return outputFile, err
    }

    outputFile, err = TransferMediaFormatLocal(formatExt, mediaFile, filepath, ffmpegPath, bashPath)
    if err != nil {
        return outputFile, err
    }

    if err := os.Remove(mediaFile); err != nil {
        return outputFile, errors.New("remove err: " + err.Error())
    }

    return outputFile, nil
}

// 转换音视频格式-本地文件
// formatExt 转换格式后缀,如.mp3, .mp4
func TransferMediaFormatLocal(formatExt, mediaFile, filepath, ffmpegPath, bashPath string) (string, error) {
    outputFile := filepath + "/" + uuid.New().String() + formatExt
    cmd := exec.Command(bashPath, "-c", ffmpegPath+" -i "+mediaFile+" "+outputFile)
    out, err := cmd.CombinedOutput()
    if err != nil {
        return outputFile, errors.New("cmd err: " + err.Error() + "->" + string(out))
    }
    return outputFile, nil
}