系统构建必要功能实现
jwt签名验证
创建签名工具包
// 在token中保存id、username
type UserInfo struct {
Id int
UserName string
}
type MyClaims struct {
User UserInfo
// 标准Claims结构体,可传入配置项
jwt.RegisteredClaims
}
// 返回信息集合
const (
ISS = "签发者异常"
EXP = "过期时间异常"
AUD = "接受者异常"
IAT = "签发时间异常"
NBF = "该令牌尚未生效"
JTI = "身份标识校验错误"
SUB = "用户身份错误"
OTHER = "身份校验失败"
OK = "身份校验成功"
)
// 过期时间(这里设置两小时)
const TokenExpireDuration = time.Hour * 2
// 签名
var mySecret = []byte("shuonihao")
// 生成token,传入UserInfo结构体
func GenerateToken(userInfo UserInfo) (string, error) {
expirationTime := time.Now().Add(TokenExpireDuration) // 两个小时有效期
claims := MyClaims{
User: userInfo,
RegisteredClaims: jwt.RegisteredClaims{
// 过期时间
ExpiresAt: jwt.NewNumericDate(expirationTime),
// 签发时间
IssuedAt: jwt.NewNumericDate(time.Now()),
// 生效时间
NotBefore: jwt.NewNumericDate(time.Now()),
// 签发者
Issuer: "dengwenhao",
// 主题
Subject: "测试",
// 用户类型
Audience: []string{"ok", "no"},
},
}
// 生成Token,指定签名算法和claims
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
// 签名
if tokenString, err := token.SignedString(mySecret); err != nil {
return "", err
} else {
return tokenString, nil
}
}
// 获取签名,需要传入解析结构的是一个函数
func Secret() jwt.Keyfunc {
return func(token *jwt.Token) (interface{}, error) {
return mySecret, nil
}
}
// 解析token
func ParseToken(tokenss string) (*MyClaims, string) {
token, err := jwt.ParseWithClaims(tokenss, &MyClaims{}, Secret())
if err != nil {
if ve, ok := err.(*jwt.ValidationError); ok {
if ve.Errors&jwt.ValidationErrorMalformed != 0 {
return nil, OTHER
} else if ve.Errors&jwt.ValidationErrorExpired != 0 {
return nil, EXP
} else if ve.Errors&jwt.ValidationErrorNotValidYet != 0 {
return nil, NBF
} else {
return nil, JTI
}
}
}
if claims, ok := token.Claims.(*MyClaims); ok && token.Valid {
return claims, OK
}
return nil, OTHER
}
在业务中使用
// 签发token
token, _ := utils.GenerateToken(userInfo)
// 解析token(从请求体中获取token字符串)
tokenStr := ctx.Request.Header.Get("Authorization")
// 如果没有token,token==nil
token, msg := utils.ParseToken(tokenStr)
在中间件中使用
// 可以先从控制器获取token传入中间件,或者在中间件中获取token需要使用的时候返回给控制器
func JWTAuth() gin.HandlerFunc {
return func(ctx *gin.Context) {
auth := ctx.Request.Header.Get("Authorization")
if len(auth) == 0 {
// 没有token直接终止请求
ctx.Abort()
ctx.String(http.StatusOK, "未登录无权限")
return
}
// 校验token,只要出错直接拒绝请求
token, msg := utils.ParseToken(auth)
if token == nil {
ctx.Abort()
ctx.String(http.StatusOK, msg)
return
} else {
// 控制器需要用到token的时候直接发送过去,随时取用
ctx.Set("token", token)
}
ctx.Next()
}
}
// 续签Token
// 普通令牌的时间不宜过长
// 过期不超过额定时间续签token
func JWTAuthPlus() gin.HandlerFunc {
return func(ctx *gin.Context) {
auth := ctx.Request.Header.Get("Authorization")
if len(auth) == 0 {
ctx.Abort()
ctx.String(http.StatusOK, "未登录无权限")
return
}
// 校验token
token, msg := utils.ParseToken(auth)
fmt.Println(msg)
if msg != utils.OK {
if msg == utils.EXP {
// 判断是否过期,调用续签函数(判断逻辑在续签那里)
newToken, _ := utils.RenewToken(token)
// 续签成功给响应头设置一个newtoken字段
if newToken != "" {
ctx.Header("newtoken", newToken)
ctx.Request.Header.Set("Authorization", newToken)
// 把新的解析给控制器
lastetToken, _ := utils.ParseToken(newToken)
ctx.Set("token", lastetToken)
ctx.Next()
return
}
}
// Token验证失败或续签失败直接拒绝请求
ctx.Abort()
ctx.String(http.StatusOK, msg)
return
}
// token未过期继续执行其他中间件
ctx.Set("token", token)
ctx.Next()
}
}
// 单点登录
// 双token方案
// 一个普通token时间较短,一个refresh_token较长时间,只要refresh_token未过期就再续签一个普通token
// 主要用于第三方登录,第三方提供的token一般时间较短,不能使用户过于频繁地登录第三方,后台操作即可
在控制器中获取token
token, _ := ctx.Get("token")
注册中间件
// 单个路由
userRouter.GET("/auth", middleware.JWTAuth(), user.UserController{}.AuthToken)
// 单组路由
userRouter := r.Group("/user", middleware.JWTAuth())
// 全局使用
r.Use(middleware.JWTAuth())
图形验证码
第三方包推荐
### 图形验证码或者音频验证码,这里我们使用这个包的图形验证码
go get github.com/mojocn/base64Captcha
创建工具包
// 有默认缓存机制,不用额外使用缓存中间件
// 10240个,和两分钟缓存
var store = base64Captcha.NewMemoryStore(base64Captcha.GCLimitNumber, base64Captcha.Expiration/5)
// 生成验证码
func CreateImageCode() (id, b64s, code string, err error) {
var driver base64Captcha.Driver
var driverString base64Captcha.DriverString
// 配置验证码信息
captchaConfig := base64Captcha.DriverString{
Height: 60,
Width: 200,
NoiseCount: 3,
ShowLineOptions: 2 | 4,
Length: 6,
Source: "1234567890abcdefghjkmnopqrstuvwxyzABCDEFGHJKLMNOPQRSTUVWXYZ",
BgColor: &color.RGBA{
R: 3,
G: 102,
B: 214,
A: 125,
},
Fonts: []string{"wqy-microhei.ttc"},
}
driverString = captchaConfig
driver = driverString.ConvertFonts()
captcha := base64Captcha.NewCaptcha(driver, store)
// 改一下源码,返回一下这个验证码的值,便于校验
lid, lb64s, code, lerr := captcha.Generate()
return lid, lb64s, code, lerr
}
// 验证captcha是否正确
// 需要一个生成验证码的id,id配对的value(也就是用户输入的值)
// 前端携带一个id、一个value
func CaptVerify(id string, val string) bool {
if id == "" || val == "" {
return false
}
// 校验一次验证码后清除
return store.Verify(id, val, true)
}
在控制器中使用
// 生成验证码
// 一般只返回id和base64图片就可以了
id, base64Str, code, err := utils.CreateImageCode()
// 校验验证码
flag := utils.CaptVerify(ctx.Param("id"), ctx.Param("code"))
接口文档
swagger
swagger是我接触web应用开发以来最熟悉的一个接口文档框架。
在gin中
包安装
# 主要用于生成命令
go get -u github.com/swaggo/swag/cmd/swag
go install github.com/swaggo/swag/cmd/swag@latest
go get -u github.com/swaggo/gin-swagger
go get -u github.com/swaggo/files
在控制器中
// @Summary 用户查询
// @Description 只查询自己子集的用户
// @Tags 用户管理
// @Accept json
// @Produce json
// @Success 200 {object} models.ResultCommon[[]models.UserSelect] "查询成功"
// @Router /user/subset [get]
// @Security JWT
func (u UserController) QueryUserSubset(ctx *gin.Context) {
// 获取id
token, _ := ctx.Get("token")
// 断言成那个类型
id := &token.(*jwt.MyClaims).User.Id
// 声明承载体
userList := []models.UserSelect{}
models.DB.Model(&models.User{}).Where("fid=?", id).Find(&userList)
ctx.JSON(http.StatusOK, &models.ResultCommon[[]models.UserSelect]{
Msg: "查询成功",
Code: "200",
Result: userList,
})
}
在main中
// @title RBAC权限管理系统
// @version 1.0.0
// @description RBAC权限管理的接口文档
// @host localhost:8002
// @BasePath /api/v1
// @schemes http https
// @securityDefinitions.apikey JWT
// @in header
// @name Authorization
func main() {
r := gin.Default()
r.SetTrustedProxies([]string{"127.0.0.1"})
docs.SwaggerInfo.BasePath = "/api/v1"
v1 := r.Group("/api/v1")
{
routers.UserApi(v1)
routers.RoleApi(v1)
routers.MenuApi(v1)
}
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerfiles.Handler))
r.Run("127.0.0.1:8002")
}
日志
内置日志模块(简单使用)
默认是控制台输出
func main() {
file, _ := os.Create("gin.log")
// 可以文件、控制台一起
gin.DefaultWriter = io.MultiWriter(file, os.Stdout)
router := gin.Default()
router.GET("/", func(ctx *gin.Context) {
ctx.String(http.StatusOK, "你好啊")
})
router.Run("127.0.0.1:8003")
}
格式化debug
router := gin.Default()
gin.DebugPrintRouteFunc = func(httpMethod, absolutePath, handlerName string, nuHandlers int) {
log.Printf("[logger] %v %v %v %v\n", httpMethod, absolutePath, handlerName, nuHandlers)
}
fmt.Println(router.Routes())
自定义输出
router := gin.New()
router.Use(gin.LoggerWithFormatter(func(params gin.LogFormatterParams) string {
// 用户IP、请求时间、请求方法、请求路径、状态码、耗时
return fmt.Sprintf("[myLogger] %v %v %v %v %v %v",
params.TimeStamp.Format("2006-01-02 15:04:05"),
params.ClientIP,
params.Method,
params.Path,
params.StatusCode,
params.Latency
)
}))
// [myLogger] 2023-10-10 15:32:52 127.0.0.1 GET / 200 0s
色彩
采用ANSI escape code在终端控制颜色格式 在碰到一个色彩的时候开始,碰到另一个结束 掘金博主文章
// gin中的源码
const (
green = "\033[97;42m"
white = "\033[90;47m"
yellow = "\033[90;43m"
red = "\033[97;41m"
blue = "\033[97;44m"
magenta = "\033[97;45m"
cyan = "\033[97;46m"
reset = "\033[0m"
)
// 256色
for i := 0; i < 16; i++ {
for j := 0; j < 16; j++ {
code := i*16 + j
fmt.Printf("\u001b[38;5;%dm%-4d",code, code)
}
fmt.Println("\u001b[0m")
}
// 3开头是前景色,4开头是后景色
fmt.Println("\u001b[30m 黑色 \u001b[0m")
fmt.Println("\u001b[31m 红色 \u001b[0m")
fmt.Println("\u001b[32m 绿色 \u001b[0m")
fmt.Println("\u001b[33m 黄色 \u001b[0m")
fmt.Println("\u001b[34m 蓝色 \u001b[0m")
fmt.Println("\u001b[35m 洋红 \u001b[0m")
fmt.Println("\u001b[36m 青色 \u001b[0m")
fmt.Println("\u001b[37m 白色 \u001b[0m")
return fmt.Sprintf("[myLogger] %v %v %s %v %s %v %s %v %s %v",
params.TimeStamp.Format("2006-01-02 15:04:05"),
params.ClientIP,
params.MethodColor(), params.Method, params.ResetColor(),
params.Path,
params.StatusCodeColor(), params.StatusCode, params.ResetColor(),
params.Latency,
)
配置式记录日志,用另一个中间件
// file, _ := os.Create("gin.log")
file, _ := os.OpenFile("./gin.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
defer file.Close()
fileWriter := io.MultiWriter(file)
router.Use(gin.LoggerWithConfig(gin.LoggerConfig{
Formatter: func(params gin.LogFormatterParams) string {
// 用户IP、请求时间、请求方法、请求路径、状态码、耗时
return fmt.Sprintf("[myLogger] %v %v %v %v %v %v\n",
params.TimeStamp.Format("2006-01-02 15:04:05"),
params.ClientIP,
params.Method,
params.Path,
params.StatusCode,
params.Latency,
)
},
// 输出的地方
Output: fileWriter,
// 要排除记录日志的路由
SkipPaths: []string{"/r1"},
}))
第三方库推荐Logrus
这是go社区最流行的日志库
使用内置设置
自定义格式
type MyFomatter struct {
}
// 日志颜色
const (
green = "\033[97;32m"
white = "\033[90;37m"
yellow = "\033[90;33m"
red = "\033[97;31m"
blue = "\033[97;34m"
magenta = "\033[97;35m"
cyan = "\033[97;36m"
reset = "\033[0m"
)
func (m MyFomatter) Format(entry *logrus.Entry) ([]byte, error) {
// 生成颜色
var color string
switch entry.Level {
case logrus.ErrorLevel:
color = red
case logrus.WarnLevel:
color = yellow
case logrus.InfoLevel:
color = blue
case logrus.DebugLevel:
color = magenta
default:
color = white
}
// 设置buffer
var buffer *bytes.Buffer
if entry.Buffer == nil {
buffer = &bytes.Buffer{}
} else {
buffer = entry.Buffer
}
// 设置时间
time := entry.Time.Format("2006/01/02 15:04:05")
// 合成buffer
fmt.Fprintf(buffer, "%s[%s]%s [%s] %s\n", color, entry.Level, reset, time, entry.Message)
return buffer.Bytes(), nil
}
自定义hook时间分割
type FileWriter struct {
}
func (fw FileWriter) Levels() []logrus.Level {
return logrus.AllLevels
}
func (fw FileWriter) Fire(entry *logrus.Entry) error {
// 获得时间
filePath := fmt.Sprintf("./logs/%s.log", time.Now().Format("01020304"))
file, _ := os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
line, _ := entry.String()
file.Write([]byte(line))
defer file.Close()
return nil
}
func test() {
for i := 0; i < 10; i++ {
logrus.Warn("okkkk")
time.Sleep(time.Second * 10)
logrus.Error("okkkk")
time.Sleep(time.Second * 10)
}
wg.Done()
}
var wg sync.WaitGroup
func main() {
router := gin.New()
logrus.AddHook(&FileWriter{})
logrus.SetFormatter(MyFomatter{Prefix: "GinApp"})
wg.Add(1)
go test()
router.Run("127.0.0.1:8003")
wg.Wait()
}
在gin中
type MyFomatter struct {
// 日志前缀
Prefix string
}
func (m MyFomatter) Format(entry *logrus.Entry) ([]byte, error) {
// 设置buffer
var buffer *bytes.Buffer
if entry.Buffer == nil {
buffer = &bytes.Buffer{}
} else {
buffer = entry.Buffer
}
fmt.Fprintf(buffer, "[%s] %s\n", m.Prefix, entry.Message)
return buffer.Bytes(), nil
}
func LogMiddleWare() gin.HandlerFunc {
return func(ctx *gin.Context) {
// 开始时间
startTime := time.Now()
path := ctx.Request.URL.Path
if ctx.Request.URL.RawQuery != "" {
path = path + "?" + ctx.Request.URL.RawQuery
}
ctx.Next()
timeStr := startTime.Format("2006/01/02 15:04:05")
clientIP := ctx.ClientIP()
method := ctx.Request.Method
statusCode := ctx.Writer.Status()
endTime := time.Now()
latency := endTime.Sub(startTime)
logrus.Infof("%s [%s] %s | %d | %s | %s", timeStr, method, clientIP, statusCode, latency, path)
}
}
type FileWriter struct {
}
func (fw FileWriter) Levels() []logrus.Level {
return logrus.AllLevels
}
func (fw FileWriter) Fire(entry *logrus.Entry) error {
level := entry.Level
// 获得等级
filePath := fmt.Sprintf("./logs/%s.log", level)
file, _ := os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
line, _ := entry.String()
file.Write([]byte(line))
defer file.Close()
return nil
}
func main() {
router := gin.New()
router.Use(LogMiddleWare())
logrus.AddHook(FileWriter{})
logrus.SetLevel(logrus.DebugLevel)
logrus.SetFormatter(MyFomatter{Prefix: "GinApp"})
router.SetTrustedProxies([]string{"127.0.0.1"})
router.GET("/r2", func(ctx *gin.Context) {
ctx.String(http.StatusOK, "你好啊")
})
router.Run("127.0.0.1:8003")
}
即时通讯WebScoket
流程
- 发起握手
- 从客户端接收数据帧
- 发送数据帧给客户端
- 关闭握手
内置http模块的使用
太复杂,跳过
gorilla/websocket包的使用
简单使用
var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
}
// 服务端处理程序
func EchoMessage(w http.ResponseWriter, r *http.Request) {
conn, _ := upgrader.Upgrade(w, r, nil)
for {
// 读取客户端的消息
msgType, msg, err := conn.ReadMessage()
if err != nil {
return
}
fmt.Printf("%s sent: %s\n", conn.RemoteAddr(), string(msg))
// 把消息写回客户端,做一个回音处理
if err = conn.WriteMessage(msgType, msg); err != nil {
return
}
}
}
// 客户端处理程序
func DisplayEcho(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "./websocket/index.html")
}
func main(){
// 注册两个路由
http.HandleFunc("/echo", websocket.EchoMessage)
http.HandleFunc("/echo_display", websocket.DisplayEcho)
// 启动HTTP服务,监听端口8080
http.ListenAndServe("127.0.0.1:8080", nil)
}
使用postman或者客户端页面可以进行测试
在gin中集成一个
协程和互斥锁常量
// Index.go
var Wg sync.WaitGroup
var Mutex sync.Mutex
消息结构体模块
// Message.go
const (
// 系统消息
SYSTEM = 1
// 用户消息
USER = 2
)
const (
// 用户进入聊天室
Register = 1
// 用户离开聊天室
UnRegister = 2
// 普通消息
Common = 3
)
type Message struct {
// 消息简讯
Sms int
// 消息具体
Text string
// 在线人数
Total int
}
type WsMessage struct {
Type int `json:"type"`
Data *Message `json:"data"`
}
// 接收用户的消息
type ClientMessage struct {
// 后续可以根据Type来实现对应处理
Type int `json:"type"`
// 消息正文
Msg string `json:"msg"`
}
服务端模块
// Hub.go
// 在线客户端结构体
type ClientManager struct {
Clients map[string]*Client // 记录在线用户
Broadcast chan []byte //触发消息广播
Register chan *Client // 触发新用户登陆
UnRegister chan *Client // 触发用户退出
}
func NewClientManager() *ClientManager {
return &ClientManager{
Clients: make(map[string]*Client), // 初始化在线用户列表
Broadcast: make(chan []byte), // 创建广播通道
Register: make(chan *Client), // 创建新用户登陆通道
UnRegister: make(chan *Client), // 创建用户退出通道
}
}
func (manager *ClientManager) InitSend(cur *Client, count int) {
defer Wg.Done()
// 广播有人进入
resp, _ := json.Marshal(&WsMessage{Type: SYSTEM, Data: &Message{Sms: Register, Text: fmt.Sprintf("%s进入聊天室", cur.UserId), Total: count}})
manager.Broadcast <- resp
// 再给进入的人单独发送聊天记录
// 这个暂时不做
}
func (manager *ClientManager) Run() {
defer Wg.Done()
for {
select {
// 消息广播
case msg := <-manager.Broadcast:
for _, conn := range manager.Clients {
Mutex.Lock()
conn.Send <- msg
Mutex.Unlock()
}
// 新用户来到
case conn := <-manager.Register:
manager.Clients[conn.ID] = conn
// 如果有新用户连接则发送最近聊天记录和在线人数给他
count := len(manager.Clients)
// 广播有人进入
Wg.Add(1)
go manager.InitSend(conn, count)
// 用户离线
case conn2 := <-manager.UnRegister:
delete(manager.Clients, conn2.ID)
// 给客户端刷新在线人数
Wg.Add(1)
go func() {
defer Wg.Done()
resp, _ := json.Marshal(&WsMessage{Type: SYSTEM, Data: &Message{Sms: UnRegister, Text: fmt.Sprintf("%s离开聊天室", conn2.UserId), Total: len(manager.Clients)}})
manager.Broadcast <- resp
}()
}
}
}
客户端模块
// 客户端结构体
type Client struct {
ID string
IpAddress string
IpSource string
UserId string
Socket *websocket.Conn
Send chan []byte
Start time.Time
ExpireTime time.Duration // 一段时间没有接收到心跳则过期
Manager *ClientManager
}
// Read 读取客户端发送过来的消息
func (c *Client) Read() {
// 出现故障后把当前客户端注销
defer func() {
Wg.Done()
_ = c.Socket.Close()
c.Manager.UnRegister <- c
}()
for {
_, data, err := c.Socket.ReadMessage()
if err != nil {
fmt.Println("读取消息失败", err.Error())
break
}
var msg ClientMessage
err = json.Unmarshal(data, &msg)
if err != nil {
fmt.Println(err.Error())
break
}
resp, _ := json.Marshal(&WsMessage{Type: USER, Data: &Message{Sms: Common, Text: msg.Msg, Total: len(c.Manager.Clients)}})
Mutex.Lock()
c.Manager.Broadcast <- resp
Mutex.Unlock()
}
}
// 根据send管道,把消息发到客户端
func (c *Client) Write() {
defer func() {
Wg.Done()
_ = c.Socket.Close()
Mutex.Lock()
c.Manager.UnRegister <- c
Mutex.Unlock()
}()
for msg := range c.Send {
if len(msg) == 0 {
// 没有消息则发送空响应
err := c.Socket.WriteMessage(websocket.CloseMessage, []byte{})
if err != nil {
fmt.Println(err.Error())
return
}
return
}
err := c.Socket.WriteMessage(websocket.TextMessage, msg)
if err != nil {
fmt.Println(err.Error())
return
}
}
}
// Check 实时监测过期
func (c *Client) Check() {
for {
now := time.Now()
var duration = now.Sub(c.Start)
if duration >= c.ExpireTime {
c.Manager.UnRegister <- c
break
}
}
}
控制器模块
func WsHandler(ctx *gin.Context, Manager *models.ClientManager) {
conn, err := (&websocket.Upgrader{
// 决解跨域问题
CheckOrigin: func(r *http.Request) bool { return true },
}).Upgrade(ctx.Writer, ctx.Request, nil)
if err != nil {
http.NotFound(ctx.Writer, ctx.Request)
fmt.Println(err.Error())
return
}
// 可以使用session存储在线
ip := ctx.ClientIP()
// 在这里可以解析IP归属地
id := generateClientID()
client := &models.Client{
ID: id,
Socket: conn,
Send: make(chan []byte),
IpAddress: ip,
IpSource: "江西",
UserId: ctx.Query("username"),
Start: time.Now(),
ExpireTime: time.Minute * 10,
Manager: Manager,
}
models.Mutex.Lock()
client.Manager.Register <- client
models.Mutex.Unlock()
models.Wg.Add(1)
go client.Read()
models.Wg.Add(1)
go client.Write()
// go client.Check()
}
// // 获取uuid
func generateClientID() string {
return uuid.New().String()
}
路由模块、主函数
func main() {
manager := models.NewClientManager()
router := gin.Default()
models.Wg.Add(1)
go manager.Run()
router.GET("/ws", func(ctx *gin.Context) {
controllers.WsHandler(ctx, manager)
})
router.Run("127.0.0.1:8080")
}
使用postman测试或者自编客户端测试
定时任务
内置time包提供功能
基本使用
// 时间结构体
// type Timer struct {
// C <-chan Time
// r runtimeTimer
// }
// 最基本的定时器
mtimer := time.NewTimer(time.Second)
fmt.Println("time start!")
<-mtimer.C
fmt.Println("time over!")
延迟执行
// 延时执行
fmt.Println("两秒延时计划开始")
<-time.After(time.Second * 2)
fmt.Println("两秒延时计划结束")
// 延时执行函数
fmt.Println("两秒延时执行函数计划开始")
chan1 := make(chan int)
time.AfterFunc(time.Second*2, func() {
fmt.Println("两秒延时计划Ing")
chan1 <- 1
})
// AfterFunc并没有传入C,以下报错
// <-myTimer.C
for {
// chan多路复用,获取管道的值
select {
case n := <-chan1:
fmt.Println("两秒后我收到了响应,值是:" + fmt.Sprint(n))
fmt.Println("两秒延时执行函数计划结束")
return
default:
fmt.Println("等待Ing~")
}
}
停止执行
mtimer3 := time.NewTimer(time.Second * 2)
fmt.Println("下一步操作等待两秒执行")
time.Sleep(time.Second)
mtimer3.Stop()
fmt.Println("不,我只需要等待一秒就可以执行")
周期性
// 周期性定时器Ticker
mTicker := time.NewTicker(time.Minute * 30)
for range mTicker.C {
fmt.Println("三秒后到我")
tool.MailMsg()
}
//使用自定义的Ticker
type MyTicker struct {
MyTick *time.Ticker
Runner func()
}
// 创建一个定时任务
func NewMyTicker(interval int, f func()) *MyTicker {
return &MyTicker{
MyTick: time.NewTicker(time.Duration(interval) * time.Second),
Runner: f,
}
}
// 启动定时器需要执行的任务
func (t *MyTicker) Start() {
for range t.MyTick.C {
t.Runner()
}
}
func TestMyTicker() {
fmt.Println("【定时任务】启动!当前时间:" + time.Now().Format("2006/01/02 15:04:05"))
}
//创建一个定时任务
ticker := tool.NewMyTicker(3, tool.TestMyTicker)
ticker.Start()
cron,强大的定时任务包
在egg.js篇有介绍时间表达式的使用
基本使用
// 设置一个定时任务
// spec:时间表达式
// fn:执行函数
func TimeTask(spec string, fn func()) {
c := cron.New()
err := c.AddFunc(spec, fn)
if err != nil {
fmt.Println(err)
}
c.Start()
defer c.Stop()
select {}
}
使用实例
// 每天下午三点清理七天前的日志文件
func RemoveLog() {
TimeTask("0 25 15 * * ?", func() {
dirName := time.Now().AddDate(0, 0, -7).Format("20060102")
// 文件夹路径
folderPath := "./logs/" + dirName
// 使用Stat函数检查文件夹是否存在
_, err := os.Stat(folderPath)
if os.IsNotExist(err) {
fmt.Println("日志文件夹不存在")
} else {
// 文件夹存在,尝试删除
err = os.RemoveAll(folderPath)
if err != nil {
fmt.Println("删除日志文件夹时发生错误:", err)
} else {
fmt.Println("日志文件夹已成功删除")
}
}
})
}