Go 即时通讯系统:日志模块重构,并从main函数开始

article/2025/8/13 20:40:38

重构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))}
}
  1. 初始化路由
    • 创建 HTTP 请求路由器。
    • 会在这里定义所有的 API 端点(endpoints)和对应的处理函数。
    • 返回一个实现了 http.Handler 接口的路由器对象。
  2. 启动后台服务
    • 使用 go 关键字启动一个 goroutine,异步运行 chat.MyServer.Start() 方法。
    • 这通常用于启动需要长期运行的 WebSocket 服务。
  3. 配置 HTTP 服务器
  4. 启动 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
}
  1. Gin 模式设置gin.SetMode(gin.ReleaseMode) 将 Gin 设置为发布模式,减少调试信息输出。
  2. 中间件使用Cors() 中间件处理跨域请求,Recovery 中间件捕获并处理 panic。
  3. 路由分组:所有路由都定义在根分组 ("") 下,清晰的 RESTful 风格路由设计。
  4. 路由类型:用户管理: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() // 处理请求}
}
  1. 跨域头设置
    • 允许所有来源 (*),生产环境应替换为具体域名
    • 允许的 HTTP 方法
    • 允许的请求头
    • 允许客户端访问的响应头
    • 允许携带凭证
  2. OPTIONS 请求处理:直接返回 200 状态码,满足浏览器的预检请求。
  3. 异常捕获:使用 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()
}
  1. WebSocket 升级
    • 使用 gorilla/websocket 包的 Upgrader 将 HTTP 连接升级为 WebSocket 连接
    • CheckOrigin 返回 true 允许所有跨域连接(生产环境应做限制)
  2. 客户端管理
    • 通过 user 查询参数识别用户,创建客户端对象,包含 WebSocket 连接和消息通道
    • 通过注册通道将客户端注册到中央服务器。
  3. 并发处理:为每个客户端启动独立的读 (client.Read()) 和写 (client.Write()) 协程,实现全双工通信。

代码地址:IM-Go


http://www.hkcw.cn/article/tptYrEjmCy.shtml

相关文章

力扣面试150题--二叉树的锯齿形层序遍历

Day 56 题目描述 思路 锯齿形就是一层是从左向右&#xff0c;一层是从右向左&#xff0c;那么我们可以分析样例&#xff0c;对于第奇数层是从左向右&#xff0c;第偶数层是从右向左&#xff0c;于是可以采取一个计数器&#xff0c;采取链表方式&#xff0c;从左向右就是正常插…

uni-app学习笔记二十一--pages.json中tabBar设置底部菜单项和图标

如果应用是一个多 tab 应用&#xff0c;可以通过 tabBar 配置项指定一级导航栏&#xff0c;以及 tab 切换时显示的对应页。 在 pages.json 中提供 tabBar 配置&#xff0c;不仅仅是为了方便快速开发导航&#xff0c;更重要的是在App和小程序端提升性能。在这两个平台&#xff…

Vue3+SpringBoot全栈开发:从零实现增删改查与分页功能

前言 在现代化Web应用开发中&#xff0c;前后端分离架构已成为主流。本文将详细介绍如何使用Vue3作为前端框架&#xff0c;SpringBoot作为后端框架&#xff0c;实现一套完整的增删改查(CRUD)功能&#xff0c;包含分页查询、条件筛选等企业级特性。 技术栈介绍 前端&#xff1…

用户资产化视角下开源AI智能名片链动2+1模式S2B2C商城小程序的应用研究

摘要&#xff1a;在数字化时代&#xff0c;平台流量用户尚未完全转化为企业的数字资产&#xff0c;唯有将其沉淀至私域流量池并实现可控、随时触达&#xff0c;方能成为企业重要的数字资产。本文从用户资产化视角出发&#xff0c;探讨开源AI智能名片链动21模式S2B2C商城小程序在…

用dayjs解析时间戳,我被提了bug

引言 前几天开发中突然接到测试提的一个 Bug&#xff0c;说我的时间组件显示异常。 我很诧异&#xff0c;这里初始化数据是后端返回的&#xff0c;我什么也没改&#xff0c;这bug提给我干啥。我去问后端&#xff1a;“这数据是不是有问题&#xff1f;”。后端答&#xff1a;“…

适配器模式:让不兼容接口协同工作

文章目录 1. 适配器模式概述2. 适配器模式的分类2.1 类适配器2.2 对象适配器 3. 适配器模式的结构4. C#实现适配器模式4.1 对象适配器实现4.2 类适配器实现 5. 适配器模式的实际应用场景5.1 第三方库集成5.2 遗留系统集成5.3 系统重构与升级5.4 跨平台开发 6. 类适配器与对象适…

多模态AI的企业应用场景:视觉+语言模型的商业价值挖掘

关键词&#xff1a;多模态AI | 视觉语言模型 | 企业应用 | 商业价值 | 人工智能 &#x1f4da; 文章目录 一、引言&#xff1a;多模态AI时代的到来二、多模态AI技术架构深度解析三、客服场景&#xff1a;智能化服务体验革命四、营销场景&#xff1a;精准投放与创意生成五、研…

设备驱动与文件系统:01 I/O与显示器

操作系统设备驱动学习之旅——以显示器驱动为例 从这一节开始&#xff0c;我要学习操作系统的第四个部分&#xff0c;就是i o设备的驱动。今天要讲的是第26讲&#xff0c;内容围绕i o设备中的显示器展开&#xff0c;探究显示器是如何被驱动的&#xff0c;也就是操作系统怎样让…

【计算机网络】Linux下简单的UDP服务器(超详细)

套接字接口 我们把服务器封装成一个类&#xff0c;当我们定义出一个服务器对象后需要马上初始化服务器&#xff0c;而初始化服务器需要做的第一件事就是创建套接字。 &#x1f30e;socket函数 这是Linux中创建套接字的系统调用,函数原型如下: int socket(int domain, int typ…

基于微信小程序的云校园信息服务平台设计与实现(源码+定制+开发)云端校园服务系统开发 面向师生的校园事务小程序设计与实现 融合微信生态的智慧校园管理系统开发

博主介绍&#xff1a; ✌我是阿龙&#xff0c;一名专注于Java技术领域的程序员&#xff0c;全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师&#xff0c;我在计算机毕业设计开发方面积累了丰富的经验。同时&#xff0c;我也是掘金、华为云、阿里云、InfoQ等平台…

6月1日星期日今日早报简报微语报早读

6月1日星期日&#xff0c;农历五月初六&#xff0c;早报#微语早读。 1、10个省份城镇化率超70%&#xff0c;广东城镇人口超9700万&#xff1b; 2、长沙居民起诉太平财险不赔“新冠险”&#xff0c;立案878天后获胜判&#xff1b; 3、海口&#xff1a;全市范围内禁止投放互联…

linux命令 systemctl 和 supervisord 区别及用法解读

目录 基础与背景服务管理范围配置文件和管理方式监控与日志依赖管理适用场景常用命令对照表实际应用场景举例优缺点对比小结参考链接 1. 基础与背景 systemctl 和 supervisord 都是用于管理和控制服务&#xff08;进程&#xff09;的工具&#xff0c;但它们在设计、使用场景和…

用mediamtx搭建简易rtmp,rtsp视频服务器

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

YOLOv5-入门篇笔记

1.创建环境 conda create -n yolvo5 python3.8 去pytorch.org下载1.8.2的版本。 pip --default-timeout1688 install torch1.8.2 torchvision0.9.2 torchaudio0.8.2 --extra-index-url https://download.pytorch.org/whl/lts/1.8/cu111 github上下载yolov5的zip pip --def…

设计模式-行为型模式-模版方法模式

概述 模板方法模式 :Template Method Pattern : 是一种行为型设计模式. 它定义了一个操作中的算法骨架&#xff0c;而将一些步骤延迟到子类中实现。 模板方法使得子类可以在不改变算法结构的情况下&#xff0c;重新定义算法中的某些步骤。 符合 开闭原则。 可以在算法的流程中&…

barker-OFDM模糊函数原理及仿真

文章目录 前言一、巴克码序列二、barker-OFDM 信号1、OFDM 信号表达式2、模糊函数表达式 三、MATLAB 仿真1、MATLAB 核心源码2、仿真结果①、barker-OFDM 模糊函数②、barker-OFDM 距离分辨率③、barker-OFDM 速度分辨率④、barker-OFDM 等高线图 四、资源自取 前言 本文进行 …

十三、【核心功能篇】测试计划管理:组织和编排测试用例

【核心功能篇】测试计划管理&#xff1a;组织和编排测试用例 前言准备工作第一部分&#xff1a;后端实现 (Django)1. 定义 TestPlan 模型2. 生成并应用数据库迁移3. 创建 TestPlanSerializer4. 创建 TestPlanViewSet5. 注册路由6. 注册到 Django Admin 第二部分&#xff1a;前端…

Python训练第四十一天

DAY 41 简单CNN 知识回顾 数据增强卷积神经网络定义的写法batch归一化&#xff1a;调整一个批次的分布&#xff0c;常用与图像数据特征图&#xff1a;只有卷积操作输出的才叫特征图调度器&#xff1a;直接修改基础学习率 卷积操作常见流程如下&#xff1a; 1. 输入 → 卷积层 →…

【C++进阶篇】哈希表的封装(赋源码)

C哈希表终极封装指南&#xff1a;从线性探测到STL兼容的迭代器魔法 一. 哈希表的封装1.1 基本结构1.1.1 插入1.1.2 查找1.1.3 删除1.1.4 Begin()1.1.5 End()1.1.6 构造函数1.1.7 析构函数 1.2 迭代器设计&#xff08;重点&#xff09;1.2.1 重载operator*()1.2.2 重载operator-…

238除自身以外数组的乘积

题目链接: https://leetcode.cn/problems/product-of-array-except-self/description/解法一&#xff1a;暴力解法 直接遍历一遍数组&#xff0c;求该数组的除该数之外的乘积&#xff0c;但是超时时间复杂度为n方。 vector<int> productExceptSelf(vector<int>&a…