Go 即时通讯系统:日志模块重构,并从main函数开始
重构logger
上次写的logger.go过于繁琐,有很多没用到的功能;重构后只提供了简洁的日志接口,支持日志轮转、多级别日志记录等功能,并采用单例模式确保全局只有一个日志实例
全局变量
var (once sync.Once // 用于实现单例模式的同步控制Logger *zap.Logger // 全局日志实例String = zap.String // 导出常用的 zap.Field 构造方法Any = zap.AnyErr = zap.ErrorInt = zap.IntFloat32 = zap.Float32
)
默认配置常量
const (defaultLogPath = "./logs" // 默认日志存储目录defaultLogLevel = "debug" // 默认日志级别defaultMaxSize = 100 // 单个日志文件最大大小(MB)defaultMaxBackups = 30 // 保留的旧日志文件数量defaultMaxAge = 7 // 日志保留天数defaultCompress = true // 是否压缩旧日志
)
初始化日志记录器
// Init 初始化日志记录器(单例模式)
func Init() *zap.Logger {once.Do(func() {// 确保日志目录存在if err := os.MkdirAll(defaultLogPath, 0755); err != nil {panic(err)}// 设置日志级别level := getLogLevel(defaultLogLevel)// 日志文件路径logFile := getDatedLogFilename(defaultLogPath)// 设置日志轮转writer := zapcore.AddSync(&lumberjack.Logger{Filename: logFile,MaxSize: defaultMaxSize, // MBMaxBackups: defaultMaxBackups, // 保留的旧日志文件数量MaxAge: defaultMaxAge, // 保留天数Compress: defaultCompress, // 是否压缩})// 编码器配置encoderConfig := zap.NewProductionEncoderConfig()encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoderencoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder // 不带颜色// 核心配置core := zapcore.NewCore(// zapcore.NewJSONEncoder(encoderConfig), // json格式zapcore.NewConsoleEncoder(encoderConfig), // 使用 Console 编码器writer,level,)// 创建LoggerLogger = zap.New(core)})return Logger
}
获取日志实例
func GetLogger() *zap.Logger {if Logger == nil {Init() // 自动初始化}return Logger
}
直接可用的日志方法
func Debug(msg string, fields ...zap.Field) {GetLogger().Debug(msg, fields...)
}func Info(msg string, fields ...zap.Field) {GetLogger().Info(msg, fields...)
}func Warn(msg string, fields ...zap.Field) {GetLogger().Warn(msg, fields...)
}func Error(msg string, fields ...zap.Field) {GetLogger().Error(msg, fields...)
}func Fatal(msg string, fields ...zap.Field) {GetLogger().Fatal(msg, fields...)
}func Sync() error {return GetLogger().Sync()
}
代码地址:logger.go
从main函数开始
func main() {defer log.Sync() // 记录日志log.Info("start chat server...")newRouter := router.NewRouter()go chat.MyServer.Start()s := &http.Server{Addr: ":8080",Handler: newRouter,ReadTimeout: 10 * time.Second,WriteTimeout: 10 * time.Second,MaxHeaderBytes: 1 << 20,}err := s.ListenAndServe()if err != nil {log.Error("server start error", log.Err(err))}
}
- 初始化路由
- 创建 HTTP 请求路由器。
- 会在这里定义所有的 API 端点(endpoints)和对应的处理函数。
- 返回一个实现了
http.Handler
接口的路由器对象。
- 启动后台服务
- 使用
go
关键字启动一个 goroutine,异步运行chat.MyServer.Start()
方法。 - 这通常用于启动需要长期运行的 WebSocket 服务。
- 使用
- 配置 HTTP 服务器
- 启动 HTTP 服务器:开始监听指定端口(8080)的 HTTP 请求。
自定义Gin路由
NewRouter 函数解析
func NewRouter() *gin.Engine {gin.SetMode(gin.ReleaseMode)server := gin.Default()server.Use(Cors())server.Use(Recovery)socket := RunSocketgroup := server.Group(""){// 用户管理功能group.GET("/user", api.GetUserList)group.GET("/user/:uuid", api.GetUserDetails)group.GET("/user/name", api.GetUserOrGroupByName)group.POST("/user/register", api.Register)group.POST("/user/login", api.Login)group.PUT("/user", api.ModifyUserInfo)group.POST("/friend", api.AddFriend)group.GET("/message", api.GetMessage)group.GET("/file/:fileName", api.GetFile)group.POST("/file", api.SaveFile)group.GET("/group/:uuid", api.GetGroup)group.POST("/group/:uuid", api.SaveGroup)group.POST("/group/join/:userUuid/:groupUuid", api.JoinGroup)group.GET("/group/user/:uuid", api.GetGroupUsers)group.GET("/socket.io", socket)}return server
}
- Gin 模式设置:
gin.SetMode(gin.ReleaseMode)
将 Gin 设置为发布模式,减少调试信息输出。 - 中间件使用:
Cors()
中间件处理跨域请求,Recovery
中间件捕获并处理 panic。 - 路由分组:所有路由都定义在根分组 (
""
) 下,清晰的 RESTful 风格路由设计。 - 路由类型:用户管理:GET/POST/PUT 操作;文件管理:文件上传/下载;WebSocket:实时通信端点。
跨域处理中间件 (Cors)
func Cors() gin.HandlerFunc {return func(c *gin.Context) {method := c.Request.Methodorigin := c.Request.Header.Get("Origin")if origin != "" {// 设置跨域响应头c.Header("Access-Control-Allow-Origin", "*")c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, UPDATE")c.Header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization")c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Cache-Control, Content-Language, Content-Type")c.Header("Access-Control-Allow-Credentials", "true")}// 处理 OPTIONS 预检请求if method == "OPTIONS" {c.JSON(http.StatusOK, "ok!")return}// 异常捕获defer func() {if err := recover(); err != nil {log.Error("HttpError", log.Any("HttpError", err))}}()c.Next() // 处理请求}
}
- 跨域头设置:
- 允许所有来源 (
*
),生产环境应替换为具体域名 - 允许的 HTTP 方法
- 允许的请求头
- 允许客户端访问的响应头
- 允许携带凭证
- 允许所有来源 (
- OPTIONS 请求处理:直接返回 200 状态码,满足浏览器的预检请求。
- 异常捕获:使用 defer+recover 捕获处理过程中的 panic,记录错误日志。
恢复中间件 (Recovery)
func Recovery(c *gin.Context) {defer func() {if r := recover(); r != nil {log.Error("gin catch error", log.Any("error", r))c.JSON(http.StatusOK, response.FailMsg("系统内部错误"))}}()c.Next()
}
panic 恢复:捕获路由处理函数中可能发生的 panic,记录错误日志
WebSocket 实现 (RunSocket)
var upGrader = websocket.Upgrader{CheckOrigin: func(r *http.Request) bool {return true // 允许所有跨域 WebSocket 连接},
}func RunSocket(c *gin.Context) {user := c.Query("user") // 获取用户标识if user == "" {return // 无用户标识则拒绝连接}log.Info("newUser", log.String("newUser", user))// 升级 HTTP 连接为 WebSocket 连接ws, err := upGrader.Upgrade(c.Writer, c.Request, nil)if err != nil {return}// 创建客户端对象client := &server.Client{Name: user,Conn: ws,Send: make(chan []byte),}// 注册客户端到服务器server.MyServer.Register <- client// 启动读写协程go client.Read()go client.Write()
}
- WebSocket 升级:
- 使用
gorilla/websocket
包的Upgrader
将 HTTP 连接升级为 WebSocket 连接 CheckOrigin
返回 true 允许所有跨域连接(生产环境应做限制)
- 使用
- 客户端管理:
- 通过
user
查询参数识别用户,创建客户端对象,包含 WebSocket 连接和消息通道 - 通过注册通道将客户端注册到中央服务器。
- 通过
- 并发处理:为每个客户端启动独立的读 (
client.Read()
) 和写 (client.Write()
) 协程,实现全双工通信。
代码地址:IM-Go
相关文章:

Go 即时通讯系统:日志模块重构,并从main函数开始
重构logger 上次写的logger.go过于繁琐,有很多没用到的功能;重构后只提供了简洁的日志接口,支持日志轮转、多级别日志记录等功能,并采用单例模式确保全局只有一个日志实例 全局变量 var (once sync.Once // 用于实现…...
CppCon 2014 学习:Exception-Safe Coding
以下是你提到的内容(例如 “Exception-Safe Coding 理解” 和 “Easier to Read!” 等)翻译成中文并进一步解释: 承诺:理解异常安全(Exception-Safe Coding) 什么是异常安全? 异常安全是指&a…...

MYSQL MGR高可用
1,MYSQL MGR高可用是什么 简单来说,MySQL MGR 的核心目标就是:确保数据库服务在部分节点(服务器)发生故障时,整个数据库集群依然能够继续提供读写服务,最大限度地减少停机时间。 2. 核心优势 v…...

阿里通义实验室突破空间音频新纪元!OmniAudio让360°全景视频“声”临其境
在虚拟现实和沉浸式娱乐快速发展的今天,视觉体验已经远远不够,声音的沉浸感成为打动用户的关键。然而,传统的视频配音技术往往停留在“平面”的音频层面,难以提供真正的空间感。阿里巴巴通义实验室(Qwen Lab࿰…...

异步上传石墨文件进度条前端展示记录(采用Redis中String数据结构实现-苏东坡版本)
昔者,有客临门,亟需自石墨文库中撷取卷帙若干。此等文册,非止一卷,乃累牍连篇,亟需批量转置。然吾辈虑及用户体验,当效东坡"腹有诗书气自华"之雅意,使操作如行云流水,遂定…...

处理知识库文件_编写powershell脚本文件_批量转换其他格式文件到pdf文件---人工智能工作笔记0249
最近在做部门知识库,选用的dify,作为rag的工具,但是经过多个对比,最后发现, 比较好用的是,纳米搜索,但是可惜纳米搜索无法在内网使用,无法把知识库放到本地,导致 有信息…...

rtpmixsound:实现音频混音攻击!全参数详细教程!Kali Linux教程!
简介 一种将预先录制的音频与指定目标音频流中的音频(即 RTP)实时混合的工具。 一款用于将预先录制的音频与指定目标音频流中的音频(即 RTP)实时混合的工具。该工具创建于 2006 年 8 月至 9 月之间。该工具名为 rtpmixsound。它…...
【Netty系列】解决TCP粘包和拆包:LengthFieldBasedFrameDecoder
目录 如何使用? 1. 示例代码(基于Netty) 2. 关键参数解释 3. 协议格式示例 4. 常见配置场景 场景1:长度字段包含自身 场景2:长度字段在消息中间 5. 注意事项 举个例子 完整示例:客户端与服务端交互…...
stm与51单片机哪个更适合新手学
一句话总结 51单片机:像学骑自行车,简单便宜,但只能在小路上骑。 STM32:像学开汽车,复杂但功能强,能上高速公路,还能拉货载人(做复杂项目)。 1. 为啥有人说“先学51单片…...

【计算机网络】第3章:传输层—面向连接的传输:TCP
目录 一、PPT 二、总结 TCP(传输控制协议)详解 1. 概述 核心特性: 2. TCP报文段结构 关键字段说明: 3. TCP连接管理 3.1 三次握手(建立连接) 3.2 四次挥手(终止连接) 4. 可…...
从架构视角设计统一网络请求体系 —— 基于 uni-app 的前后端通信模型
在使用 uni-app 开发跨平台应用时,设计一套清晰、统一、可扩展的网络请求模块 是前期架构的关键环节。良好的请求模块不仅提高开发效率,更是保证后期维护、调试和业务扩展的基础。 一、网络请求设计目标 在uni-app中设计网络请求模块,应遵循…...

《信号与系统》--期末总结V1.0
《信号与系统》–期末总结V1.0 学习链接 入门:【拯救期末】期末必备!8小时速成信号与系统!【拯救期末】期末必备!8小时速成信号与系统!_哔哩哔哩_bilibili 精通:2022浙江大学信号与系统(含配…...
第32次CCF计算机软件能力认证-2-因子化简
因子化简 刷新 时间限制: 2.0 秒 空间限制: 512 MiB 下载题目目录(样例文件) 题目背景 质数(又称“素数”)是指在大于 11 的自然数中,除了 11 和它本身以外不再有其他因数的自然数。 题…...

mac笔记本如何快捷键截图后自动复制到粘贴板
前提:之前只会进行部分区域截图操作(commandshift4)操作,截图后发现未自动保存在剪贴板,还要进行一步手动复制到剪贴板的操作。 mac笔记本如何快捷键截图后自动复制到粘贴板 截取 Mac 屏幕的一部分并将其自动复制到剪…...

高考加油!UI界面生成器!
这个高考助力标语生成器具有以下特点: 视觉设计:采用了蓝色为主色调,搭配渐变背景和圆形装饰元素,营造出宁静而充满希望的氛围,非常适合高考主题。 标语生成:内置了超过 100 条精心挑选的高考加油标语&a…...

window ollama部署模型
注意去官网下载ollama,这个win和linux差别不大,win下载exe,linux用官网提供的curl命令 模型下载表:deepseek-r1 使用命令:Ollama API 交互 | 菜鸟教程 示例: 1.查看已加载模型: 2.文本生成接口 curl -X POST http://localhost:11434/v1/completions -H "Conte…...

用mediamtx搭建简易rtmp,rtsp视频服务器
简述: 平常测试的时候搭建rtmp服务器很麻烦,这个mediamtx服务器,只要下载就能运行,不用安装、编译、配置等,简单易用、ffmpeg推流、vlc拉流 基础环境: vmware17,centos10 64位,wi…...

ubuntu安装devkitPro
建议开个魔法 wget https://apt.devkitpro.org/install-devkitpro-pacman chmod x ./install-devkitpro-pacman sudo ./install-devkitpro-pacman(下面这句如果报错也没事) sudo ln -s /proc/self/mounts /etc/mtab往~.bashrc添加 export DEVKITPRO/o…...

Linux(10)——第二个小程序(自制shell)
目录 编辑 一、引言与动机 📝背景 📝主要内容概括 二、全局数据 三、环境变量的初始化 ✅ 代码实现 四、构造动态提示符 ✅ 打印提示符函数 ✅ 提示符生成函数 ✅获取用户名函数 ✅获取主机名函数 ✅获取当前目录名函数 五、命令的读取与…...
github actions入门指南
GitHub Actions 是 GitHub 提供的持续集成和持续交付(CI/CD)平台,允许开发者自动化软件工作流程(如构建、测试、部署)。以下是详细介绍: 一、核心概念 Workflow(工作流程) 持续集成的…...

代码随想录算法训练营 Day59 图论Ⅸ dijkstra优化版 bellman_ford
图论 题目 47. 参加科学大会(第六期模拟笔试) 改进版本的 dijkstra 算法(堆优化版本) 朴素版本的 dijkstra 算法解法的时间复杂度为 O ( n 2 ) O(n^2) O(n2) 时间复杂度与 n 有关系,与边无关系 类似于 prim 对应点多…...
HTML实战:响应式个人资料页面
我将创建一个现代化的响应式个人资料页面,展示HTML在实际应用中的强大功能。这个页面将包含多个实战元素:导航栏、个人简介、技能展示、作品集和联系表单。 设计思路 使用Flexbox和Grid布局实现响应式设计 添加CSS过渡效果增强交互体验 实现深色/浅色模式切换功能 创建悬停动…...
Mac电脑上本地安装 MySQL并配置开启自启完整流程
文章目录 一、mysql安装1.1 使用 Homebrew 安装(推荐)1.2 手动下载 MySQL 社区版1.3 常见问题1.4 图形化管理工具(可选) 二、Mac 上配置 MySQL 开机自动启动2.1 使用 launchd 系统服务(原生支持)2.2 通过 H…...
JavaSE:面向对象进阶之内部类(Inner Class)
JavaSE 面向对象进阶之内部类(Inner Class) 一、内部类的核心概念 内部类是定义在另一个类内部的类,它与外部类存在紧密的逻辑关联,主要作用: 封装细节:隐藏实现细节,对外提供简洁接口。访问…...

【HW系列】—安全设备介绍(开源蜜罐的安装以及使用指南)
文章目录 蜜罐1. 什么是蜜罐?2. 开源蜜罐搭建与使用3. HFish 开源蜜罐详解安装步骤使用指南关闭方法 总结 蜜罐 1. 什么是蜜罐? 蜜罐(Honeypot)是一种主动防御技术,通过模拟存在漏洞的系统或服务(如数据库…...

汽车总线分析总结(CAN、LIN、FlexRay、MOST、车载以太网)
目录 一、汽车总线技术概述 二、主流汽车总线技术对比分析 1. CAN总线(Controller Area Network) 2. LIN总线(Local Interconnect Network) 3. FlexRay总线 4. MOST总线(Media Oriented Systems Transport&#x…...

MyBatisPlus--条件构造器及自定义SQL详解
条件构造器 在前面学习快速入门的时候,练习的增删改查都是基于id去执行的,但是在实际开发业务中,增删改查的条件往往是比较复杂的,因此MyBatisPlus就提供了一个条件构造器来帮助构造复杂的条件。 MyBatisPlus支持各种复杂的wher…...

OVD开放词汇检测 Detic 训练COCO数据集实践
0、引言 纯视觉检测当前研究基本比较饱和,继续创新提升空间很小,除非在CNN和transformer上提出更强基础建模方式。和文本结合是当前的一大趋势,也是计算机视觉和自然语言处理结合的未来趋势,目前和文本结合的目标检测工作还是有很…...

docker、ctr、crictl命令简介与使用
概述 在使用k3s过程中,经常需要使用ctr和crictl两个命令,本文记录一下。 ctr 类似docker命令是docker-shim容器运行时的客户端工具,ctr是Containerd的客户端工具。一个简单的CLI接口,用作Containerd本身的一些调试用途…...
WEB安全--SQL注入--bypass技巧2
继之前文章的补充: WEB安全--SQL注入--bypass技巧_sql注入过滤空格-CSDN博客 Q1:发现sql注入的时间盲注时,如果时间盲注的函数都被过滤了,怎么办? 除了找其他函数替换、编码等方式,还有以下方式绕过&…...