字符串

> 按字节数截断字符串

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

import "unicode/utf8"

const Hellip = "…"

func SubStrContainSuffix(str string, maxBytes int, suffix string) string {
if len(str) <= maxBytes {
return str
}
maxBytesNotContainSuffix := maxBytes - len(suffix)
return SubStringByByte(str, maxBytesNotContainSuffix) + suffix
}

func SubStringByByte(str string, maxBytes int) string {
if len(str) <= maxBytes {
return str
}
byteCount := 0
for i := 0; i < len(str); {
_, size := utf8.DecodeRuneInString(str[i:])
if byteCount+size > maxBytes {
return str[:i]
}
byteCount += size
i += size
}
return str
}




时间

> 低版本go语言获取毫秒数

time.Now() 能够获取当前时间,而 UnixMilli() 方法可得到当前时间的毫秒级时间戳。UnixMilli() 方法是在 Go 1.17 版本引入的。

1
2
3
4
// 获取当前时间
now := time.Now()
// 获取毫秒级时间戳
millisecond := now.UnixMilli()

低版本可以这么玩:

1
2
3
4
// 获取当前时间
now := time.Now()
// 获取毫秒级时间戳
millisecond := now.UnixNano() / int64(time.Millisecond)

PS:go语言中的时间定义

1
2
3
4
5
6
7
8
const (
Nanosecond Duration = 1
Microsecond = 1000 * Nanosecond
Millisecond = 1000 * Microsecond
Second = 1000 * Millisecond
Minute = 60 * Second
Hour = 60 * Minute
)




> 时间戳转格式化时间

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

import (
"fmt"
"time"
)

// TimestampToDateTime 将秒级时间戳转换为指定格式的时间字符串(东八区)
// 参数:timestamp - 秒级时间戳(如 1712058764 对应 2024-04-02 19:52:44)
// 返回:格式化后的时间字符串(示例:"2024-04-02 19:52:44")
func TimestampToDateTime(timestamp int64) string {
// 将时间戳转换为 time.Time 对象(UTC 时间)
t := time.Unix(timestamp, 0)

// 转换为东八区时间(UTC+8)
// 若需其他时区,修改 "Asia/Shanghai" 为对应时区(如 "America/New_York")
loc, _ := time.LoadLocation("Asia/Shanghai")
tInLocal := t.In(loc)

// 格式化为目标字符串(Go 的时间格式化参考模板:2006-01-02 15:04:05)
return tInLocal.Format("2006-01-02 15:04:05")
}

func main() {
// 测试用例:2024-04-02 19:52:44 对应的秒级时间戳
testTimestamp := int64(1712058764)
result := TimestampToDateTime(testTimestamp)
fmt.Printf("时间戳 %d 转换结果: %s\n", testTimestamp, result)
// 输出:时间戳 1712058764 转换结果: 2024-04-02 19:52:44
}





panic

> 在函数外部recover panic

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

import "log"

type panicHandler func(err interface{})

type optionPanicHandler func(*optionPanicHandlerParams)

type optionPanicHandlerParams struct {
panicHandler panicHandler
}

func WithPanicHandler(panicHandler panicHandler) optionPanicHandler {
return func(optParams *optionPanicHandlerParams) {
optParams.panicHandler = panicHandler
}
}

func WithRecover(f func(), opts ...optionPanicHandler) {
defer func() {
if err := recover(); err != nil {
optParams := &optionPanicHandlerParams{}
for _, opt := range opts {
opt(optParams)
}
if optParams.panicHandler == nil {
optParams.panicHandler = defaultPanicHandler
}
optParams.panicHandler(err)
}
}()

f()
}

func defaultPanicHandler(err interface{}) {
log.Printf("panic: %v", err)
}

例1:

1
go util.WithRecover(func() { f2s.recordFileUpload(language, fileContent) })

例2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
util.WithRecover(func() {
switch method {
case Get:
handlerFunc = enforceGet(handlerFunc)
case Post:
handlerFunc = enforcePost(handlerFunc)
default:
log.Printf("invalid method: %s", method)
}
handlerFunc(w, r, nil)
}, util.WithPanicHandler(func(err interface{}) {
panicHandler(err, method, realPath)
}))

func panicHandler(err interface{}, method string, realPath string) {
log.Printf("panic: %v, method: %s, realPath: %s", err, method, realPath)
}




脚本

> 定时任务检查进程

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

import (
"fmt"
"os/exec"
"strings"

"git.garena.com/guowei.gong/tool-center/internal/pkg/client/http/sop"
"github.com/gw-gong/gwkit-go/global_settings"
"github.com/gw-gong/gwkit-go/log"
)

const (
ProcessNameTestEnvAssistant = "test_env_assistant"
)

type CheckBotProcessWorker struct {
exclusiveLock
processName string
mm sop.MessageManager
groupID string
}

func NewCheckBotProcessWorker(processName string, mm sop.MessageManager, groupID string) *CheckBotProcessWorker {
return &CheckBotProcessWorker{
processName: processName,
mm: mm,
groupID: groupID,
}
}

func (c *CheckBotProcessWorker) Work() {
ctx := log.WithFields(global_settings.GetServiceContext(), log.Str("cron_job", "check_bot_process"), log.Str("process_name", c.processName))
if !c.exclusiveLock.acquire() {
log.Errorc(ctx, "acquire lock failed")
return
}
defer c.exclusiveLock.release()

// 检查进程是否存在
if err := c.checkProcessExists(); err != nil {
log.Errorc(ctx, "check process exists failed", log.Err(err))

if _, err := c.mm.SendTextMessageToGroup(ctx, c.groupID, "🚨 bot 进程已停止", false); err != nil {
log.Errorc(ctx, "send notification message failed", log.Err(err))
}
return
}

log.Infoc(ctx, "bot process check completed successfully", log.Str("process_name", c.processName))
}

// checkProcessExists 检查进程是否存在
func (c *CheckBotProcessWorker) checkProcessExists() error {
// 使用 ps 命令检查进程
cmd := exec.Command("ps", "-ef")
output, err := cmd.Output()
if err != nil {
return err
}

// 将输出按行分割
lines := strings.Split(string(output), "\n")

// 检查每一行是否包含目标进程名
for _, line := range lines {
// 跳过空行
if strings.TrimSpace(line) == "" {
continue
}

// 分割行内容,ps -ef 的输出格式是:UID PID PPID C STIME TTY TIME CMD
fields := strings.Fields(line)
if len(fields) < 8 {
continue
}

// 获取命令部分(第8个字段,索引为7)
cmd := fields[7]

// 移除路径前缀,只比较文件名
baseName := cmd
if idx := strings.LastIndex(cmd, "/"); idx != -1 {
baseName = cmd[idx+1:]
}

// 检查文件名是否匹配目标进程名
if baseName == c.processName {
return nil // 找到匹配的进程
}
}

return fmt.Errorf("process %s not found", c.processName)
}