复盘日志库zap框架

zap 是 uber 开源的一个高性能,结构化,分级记录的日志记录包。
main函数基本配置如下

core.Config = &core.ZapConf{
    Level:         "info",                       // 级别
    Format:        "console",                    // 输出 console json
    Prefix:        "[server]",                   // 前缀
    Director:      "logs",                       // 保存目录
    LinkName:      "latest_log",                 // 链接文件
    ShowLine:      true,                         // 是否显示行号
    EncodeLevel:   "LowercaseColorLevelEncoder", // 编码级
    StacktraceKey: "stacktrace",                 // 栈名
    LogInConsole:  true,                         // 输出控制台
}

zapApp := core.InitZap(core.Config)
for i := 0; i < 10000000; i++ {
    zapApp.Info("日志内容:", zap.Any("user", "001")) // 写入文件
}
defer zapApp.Sync()

核心配置如下

package core

import (
    "fmt"
    rotatelogs "github.com/lestrrat-go/file-rotatelogs"
    "go.uber.org/zap"
    "go.uber.org/zap/zapcore"
    "gopkg.in/natefinch/lumberjack.v2"
    "os"
    "path"
    "time"
    "zaptest/utils"
)

// ZapConf Zap配置结构
type ZapConf struct {
    Level         string `mapstructure:"level" json:"level" yaml:"level"`                           // 级别
    Format        string `mapstructure:"format" json:"format" yaml:"format"`                        // 输出
    Prefix        string `mapstructure:"prefix" json:"prefix" yaml:"prefix"`                        // 日志前缀
    Director      string `mapstructure:"director" json:"director"  yaml:"director"`                 // 日志文件夹
    LinkName      string `mapstructure:"link-name" json:"linkName" yaml:"link-name"`                // 软链接名称
    ShowLine      bool   `mapstructure:"show-line" json:"showLine" yaml:"showLine"`                 // 显示行
    EncodeLevel   string `mapstructure:"encode-level" json:"encodeLevel" yaml:"encode-level"`       // 编码级
    StacktraceKey string `mapstructure:"stacktrace-key" json:"stacktraceKey" yaml:"stacktrace-key"` // 栈名
    LogInConsole  bool   `mapstructure:"log-in-console" json:"logInConsole" yaml:"log-in-console"`  // 输出控制台
}

// Config 全局Zap配置
var Config *ZapConf
var Level zapcore.Level

func InitZap(config *ZapConf) (logger *zap.Logger) {

    // 判断是否有存储日志文件夹
    if ok, _ := utils.PathExists(config.Director); !ok {
        // 不存在创建日志存储目录
        fmt.Printf("create %v directory\n", config.Director)
        _ = os.Mkdir(config.Director, os.ModePerm)
    }

    // 初始化配置文件的Level
    switch config.Level {
    case "debug":
        Level = zap.DebugLevel
    case "info":
        Level = zap.InfoLevel
    case "warn":
        Level = zap.WarnLevel
    case "error":
        Level = zap.ErrorLevel
    case "dpanic":
        Level = zap.DPanicLevel
    case "panic":
        Level = zap.PanicLevel
    case "fatal":
        Level = zap.FatalLevel
    default:
        Level = zap.InfoLevel
    }

    if Level == zap.DebugLevel || Level == zap.ErrorLevel {
        logger = zap.New(getEncoderCore(), zap.AddStacktrace(Level))
    } else {
        logger = zap.New(getEncoderCore())
    }
    // 如果开启行号
    if config.ShowLine {
        logger = logger.WithOptions(zap.AddCaller())
    }
    return logger
}

// CustomTimeEncoder 自定义日志输出时间格式
func CustomTimeEncoder(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
    enc.AppendString(t.Format(Config.Prefix + "2006/01/02 - 15:04:05.000"))
}

// getEncoderConfig 获取zapcore.EncoderConfig
func getEncoderConfig() (conf zapcore.EncoderConfig) {
    conf = zapcore.EncoderConfig{
        MessageKey:     "message",
        LevelKey:       "level",
        TimeKey:        "time",
        NameKey:        "logger",
        CallerKey:      "caller",
        StacktraceKey:  Config.StacktraceKey,
        LineEnding:     zapcore.DefaultLineEnding,
        EncodeLevel:    zapcore.LowercaseLevelEncoder,
        EncodeTime:     CustomTimeEncoder,
        EncodeDuration: zapcore.SecondsDurationEncoder,
        EncodeCaller:   zapcore.FullCallerEncoder,
    }
    switch {
    case Config.EncodeLevel == "LowercaseLevelEncoder": // 小写编码器(默认)
        conf.EncodeLevel = zapcore.LowercaseLevelEncoder
    case Config.EncodeLevel == "LowercaseColorLevelEncoder": // 小写编码器带颜色
        conf.EncodeLevel = zapcore.LowercaseColorLevelEncoder
    case Config.EncodeLevel == "CapitalLevelEncoder": // 大写编码器
        conf.EncodeLevel = zapcore.CapitalLevelEncoder
    case Config.EncodeLevel == "CapitalColorLevelEncoder": // 大写编码器带颜色
        conf.EncodeLevel = zapcore.CapitalColorLevelEncoder
    default:
        conf.EncodeLevel = zapcore.LowercaseLevelEncoder
    }
    return conf
}

// 获取核心
func getEncoderCore() (core zapcore.Core) {
    // 按照日期写入文件
    //writer, err := GetWriteSyncerByDate()
    //if err != nil {
    //  fmt.Printf("Get Write Syncer Failed err:%v", err.Error())
    //  return
    //}

    // 按照大小写入文件
    writer := GetWriteSyncerBySize()

    return zapcore.NewCore(getEncoder(), writer, Level)
}

// 判断是json还是console格式化
func getEncoder() zapcore.Encoder {
    if Config.Format == "json" {
        return zapcore.NewJSONEncoder(getEncoderConfig())
    }
    return zapcore.NewConsoleEncoder(getEncoderConfig())
}

// GetWriteSyncerByDate 按照日期分割日志
func GetWriteSyncerByDate() (zapcore.WriteSyncer, error) {
    fileWriter, err := rotatelogs.New(
        //path.Join(Config.Director, "%Y-%m-%d.log"),  // 日志文件名
        //rotatelogs.WithMaxAge(7*24*time.Hour),       // 日志最大保存时间
        //rotatelogs.WithRotationTime(24*time.Minute), // 日志轮转时间
        //rotatelogs.WithLinkName(Config.LinkName),

        //每分钟
        path.Join(Config.Director, "%Y-%m-%d-%H-%M.log"),
        rotatelogs.WithLinkName(Config.LinkName), //生成软链,指向最新日志文件
        rotatelogs.WithRotationTime(time.Minute), //最小为1分钟轮询。默认60s  低于1分钟就按1分钟来
        rotatelogs.WithRotationCount(3),          //设置3份 大于3份 或到了清理时间 开始清理
        rotatelogs.WithRotationSize(1*1024*1024), //设置100MB大小,当大于这个容量时,创建新的日志文件

        //rotatelogs.WithRotationSize(100),
        //rotatelogs.WithRotationTime(24*time.Hour),        // 日志轮转时间
    )
    if err != nil {
        return nil, err
    }

    if Config.LogInConsole {
        return zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout), zapcore.AddSync(fileWriter)), nil
    }
    return zapcore.AddSync(fileWriter), nil
}

// GetWriteSyncerBySize 按照大小分割日志
func GetWriteSyncerBySize() zapcore.WriteSyncer {
    fileWriter := &lumberjack.Logger{
        Filename:   Config.Director + "/app.log", // 日志文件名
        MaxSize:    1,                            // 每个日志文件的最大尺寸(单位:MB)
        MaxBackups: 5,                            // 保留的旧日志文件的最大数量
        MaxAge:     7,                            // 保留的日志文件最大天数
        Compress:   true,                         // 是否压缩日志文件
    }

    if Config.LogInConsole {
        return zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout), zapcore.AddSync(fileWriter))
    }
    return zapcore.AddSync(fileWriter)
}

// GetWriteSyncerByDateAndSize 日期日志 + 大小日志
func GetWriteSyncerByDateAndSize() (zapcore.WriteSyncer, error) {
    // 按日期分割
    dateWriter, err := rotatelogs.New(
        path.Join(Config.Director, "%Y-%m-%d.log"),
        rotatelogs.WithMaxAge(7*24*time.Hour),
        rotatelogs.WithRotationTime(24*time.Hour),
    )
    if err != nil {
        return nil, err
    }

    // 按大小分割
    sizeWriter := &lumberjack.Logger{
        Filename:   Config.Director + "/app.log",
        MaxSize:    1, // 1MB
        MaxBackups: 5,
        MaxAge:     7,
        Compress:   true,
    }

    // 同时输出到日期和大小分割的日志
    if Config.LogInConsole {
        return zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout), zapcore.AddSync(dateWriter), zapcore.AddSync(sizeWriter)), nil
    }
    return zapcore.NewMultiWriteSyncer(zapcore.AddSync(dateWriter), zapcore.AddSync(sizeWriter)), nil
}

// PathExists 检查路径是否存在
func PathExists(path string) (bool, error) {
    _, err := os.Stat(path)
    if err == nil {
        return true, nil
    }
    if os.IsNotExist(err) {
        return false, nil
    }
    return false, err
}

复盘web库gin框架

安装

go get -u github.com/gin-gonic/gin

基础使用

package main

import (
  "net/http"

  "github.com/gin-gonic/gin"
)

func main() {
  r := gin.Default()
  r.GET("/ping", func(c *gin.Context) {
    c.JSON(http.StatusOK, gin.H{
      "message": "pong",
    })
  })
  r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}

复杂使用

gin.SetMode(gin.ReleaseMode)
router := gin.Default()

// 不存在的路由
router.NoRoute(controller.NotFound)
// 不存在的方法
router.NoMethod(controller.NotFound)
// 静态文件目录
//router.Static("/files", "./files")
// 静态文件
router.StaticFile("/favicon.ico", "./static/favicon.ico")
// 静态文件目录
router.Static("/static", "./static")
// 模板标签
router.Delims("{{{", "}}}")
// 模板加载路径
router.LoadHTMLGlob("views/**/**/*")
// 模板调用函数
router.SetFuncMap(template.FuncMap{
    "SafeJs":              view.SafeJS,
})

// 使用中间件
router.Use(middleware.Cors())
// 实例化控制器
controllersV1 := new(v1.Controller)

// 路由分组
api := router.Group("api")
{
    // 无需权限校验的分组
    apiV1 := api.Group("v1")
    {
        // 设置路由 POST、GET等
        apiV1.GET("user/test_a", controllersV1.TestA)
        apiV1.GET("user/test_b", controllersV1.TestB)
        apiV1.POST("user/test_c", controllersV1.TestC)
    }
    // 需要权限校验的分组
    apiV1.Use(middleware.JWTAuth())
    {
        apiV1.GET("user/test_d", controllersV1.TestD)
    }

    // 需要权限校验
    //apiV1.Use(middleware.CasbinRBAC())

}
router.Run()

中间件的编写

// 跨域中间件
// Cors 处理跨域请求,支持options访问
func Cors() gin.HandlerFunc {
    return func(c *gin.Context) {
        method := c.Request.Method
        origin := c.Request.Header.Get("Origin")
        c.Writer.Header().Set("Access-Control-Allow-Origin", origin)
        c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type,AccessToken,X-CSRF-Token, Authorization, Token,X-Token,X-User-Id")
        c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS,DELETE,PUT")
        c.Writer.Header().Set("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type")
        c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
        // 放行所有OPTIONS方法
        if method == "OPTIONS" {
            c.AbortWithStatus(http.StatusNoContent)
        }
        // 处理请求
        c.Next()
    }
}
// JWT 处理中间件
func JWTAuth() gin.HandlerFunc {
    return func(c *gin.Context) {
        // jwt鉴权取头部信息 x-token 登录时回返回token信息 这里前端需要把token存储到cookie或者本地localStorage中 不过需要跟后端协商过期时间 可以约定刷新令牌或者重新登录
        token := c.Request.Header.Get("x-token")
        if token == "" {
            c.JSON(http.StatusUnauthorized, gin.H{"error": "未登录或非法访问"})
            c.Abort()
            return
        }
        fmt.Println("JWTAuth")
        c.Set("claims", 11)
        c.Next()
    }
}

控制器

# 结构体形式的控制器
type IndexApi struct {
}

// Index
func (a IndexApi) Index(c *gin.Context) {
    c.JSON(200, gin.H{"message": "hello world"})
}
# 单个函数控制器方法
func Index(c *gin.Context) {
    c.JSON(200, gin.H{"message": "hello world"})
}

数据输出

c.JSON(200, gin.H{"message": "hello world"})
其他的善用搜索与文档

对Go的复盘计划

距离上一次的go工作已经过去差不多一年半了,最近一年半PHP为主了,对go又生疏了。
接下来开始重新学习go,放下的再捡起来吧

Debian和 Ubuntu下将Python程序打包成.deb安装包

查看python版本

it0@it0-pc:~/桌面/tests/testdeb$ python3 -V
Python 3.8.10

查看pip版本

it0@it0-pc:~/桌面/tests/testdeb$ pip3 -V
pip 24.1.2 from /usr/local/lib/python3.8/dist-packages/pip (python 3.8)

将工程打包生成一个可执行文件

注意一定要保证python程序在电脑上可以正常运行!!!!!!!
该安装第三方库的安装库,该安装扩展的安装扩展!!!!!!!

python3 main.py

当前项目目录结构

.
├── config
├── DB
├── horizontalSliderUI.py
├── icons
├── main.py
├── ui
└── utils

安装pyinstaller

pip3 install pyinstaller

检查pyinstaller安装成功

pyinstaller -v

在python代码项目目录执行以下命令

pyinstaller -F -w main.py
# 可以通过pyinstaller --help查看帮助

此时项目结构 (产生了build、dist、main.spec)文件和文件夹

.
├── build
├── config
├── DB
├── dist
├── horizontalSliderUI.py
├── icons
├── main.py
├── main.spec
├── ui
└── utils

dist目录中的main文件就是编译好的二进制文件。
此时从其他地方新建目录testa把二进制移动到该目录
此时testa目录结构为:

.
└── main

把程序的依赖配置目录啥的都放到testa目录下(此处为testa示例 根据自己实际情况来)

.
├── DB
│   ├── log
│   │   └── log.txt
│   └── user_DB.db
└── main

此时终端到testa目录下执行./main文件测试程序是否可以正常启动,如有问题就看终端上的报错,没有问题则进行下一步。

把可执行程序和依赖文件打包成.deb文件

在其他位置新建makedeb目录,然后按照以下结构创建目录结构,以下是我makedeb目录下的结构

├── DEBIAN
└── usr
    ├── lib
    └── share
        ├── applications
        └── icons

将testa目录直接移动到lib目录下

├── DEBIAN
└── usr
    ├── lib
    │   └── testa
    │       ├── DB
    │       │   ├── log
    │       │   │   └── log.txt
    │       │   └── user_DB.db
    │       └── main
    └── share
        ├── applications
        └── icons

到DEBIAN目录新增control文件,内容如下(自行百度含义):

Package: debpacktest
Version: 1.0.0
Architecture: arm64
Maintainer: gao
Description: just a test

在/usr/share/applications下制作桌面图标 testa.desktop文件

[Desktop Entry]
Name=debpacktest
Comment=An example
Exec=/usr/lib/testa/m/main #可执行文件路径
Icon=/usr/share/icons/icc.ico #图标路径
Terminal=false #是否运行同时打开终端
Type=Application
X-Ubuntu-Touch=true
Categories=Development

为这个文件添加可执行权限

chmod +x testa.desktop

在/usr/share/icons/文件夹下放icc.ico图标(图标名自定义跟上边配置呼应)
此时makedeb的目录结构如下:

├── DEBIAN
│   └── control
└── usr
    ├── lib
    │   └── testa
    │       ├── DB
    │       │   ├── log
    │       │   │   └── log.txt
    │       │   └── user_DB.db
    │       └── main
    └── share
        ├── applications
        │   └── testa.desktop
        └── icons
            └── icc.ico

到这里,一个基本的deb软件包就大功告成了。
我们返回到makedeb的上级目录。我们执行

sudo dpkg -b makedeb makedeb_1.0.0_arm64.deb

包的命名要符合规范,我这里是随便打的。
检验的话,可以输入(安装deb包)

sudo dpkg -i makedeb_1.0.0_arm64.deb

我在应用列表里看到了我的程序
如果要卸载,请执行

sudo dpkg -r makedeb

参考链接
https://blog.csdn.net/u013541325/article/details/114954959
https://blog.csdn.net/qq_43790749/article/details/118027929

通过hook eval解密混淆的PHP文件文件

PHP混淆原理

一般来讲,混淆分为两种

利用拓展进行加密

比如 SG11、Swoole Compiler 等

不需要拓展,单文件加密

本文主要针对第二种,而单文件加密的一般都是对源码进行字符串操作,比如对字符串移位、拼接,或者重新定义变量,重新赋值数组,总之就是尽可能减少程序可读性。但是所有加密过的代码都会经过多次eval来重新还原为php代码执行,所以我们可以hook PHP中的eval函数来输出经过eval函数的参数,参数就是源码。

hook eval

PHP中的eval函数在Zend里需要调用zend_compile_string函数,我们写一个拓展直接hook这个函数就行了。不过我不会写c代码,所以参考网上的文章,在GitHub中找到了现成的一个拓展库。
https://github.com/bizonix/evalhook/tree/master
安装扩展之后终端执行

php xxx.php

即可打印出解析后的PHP代码 注意变量名无法被还原
参考链接 https://y4er.com/posts/hook-eval/

1 3 4 5 6 7 12