Save
Saving
  • Zero

    上传成功后点击“保存更改”,发现头像并没有变化,于是我开始从Nginx日志开始查,一直查到NodeBB的配置文件,都没有发现问题。👨‍💻 👨‍💻 👨‍💻

    posted in GuaiK故障处理 read more
  • Zero

    Python时间字符串转时间戳

    import time
    
    dt = '2020-01-01 00:00:00'
    ts = int(time.mktime(time.strptime(dt, "%Y-%m-%d %H:%M:%S")))
    print(ts)
    

    posted in GuaiK实验室 read more
  • Zero

    GuaikOrg/go-snowflake

    • Github传送门

    https://github.com/GUAIK-ORG/go-snowflake


    Build Status

    ❄️ GO-Snowflake

    Snowflake简介

    在单机系统中我们会使用自增id作为数据的唯一id,自增id在数据库中有利于排序和索引,但是在分布式系统中如果还是利用数据库的自增id会引起冲突,自增id非常容易被爬虫爬取数据。在分布式系统中有使用uuid作为数据唯一id的,但是uuid是一串随机字符串,所以它无法被排序。

    Twitter设计了Snowflake算法为分布式系统生成ID,Snowflake的id是int64类型,它通过datacenterId和workerId来标识分布式系统,下面看下它的组成:

    1bit 41bit 5bit 5bit 12bit
    符号位(保留字段) 时间戳(当前时间-纪元时间) 数据中心id 机器id 自增序列

    算法简介

    在使用Snowflake生成id时,首先会计算时间戳timestamp(当前时间 - 纪元时间),如果timestamp数据超过41bit则异常。同样需要判断datacenterId和workerId不能超过5bit(0-31),在处理自增序列时,如果发现自增序列超过12bit时需要等待,因为当前毫秒下12bit的自增序列被用尽,需要进入下一毫秒后自增序列继续从0开始递增。


    快速开始

    安装

    git clone https://github.com/GUAIK-ORG/go-snowflake.git

    运行

    go run main.go

    测试

    本机测试:

    参数 配置
    OS MacBook Pro (13-inch, Late 2016, Four Thunderbolt 3 Ports)
    CPU 2.9 GHz 双核Intel Core i5
    RAM 8 GB 2133 MHz LPDDR3

    测试代码

    func TestLoad() {
        var wg sync.WaitGroup
        s, err := snowflake.NewSnowflake(int64(0), int64(0))
        if err != nil {
            glog.Error(err)
            return
        }
        var check sync.Map
        t1 := time.Now()
        for i := 0; i < 200000; i++ {
            wg.Add(1)
            go func() {
                defer wg.Done()
                val := s.NextVal()
                if _, ok := check.Load(val); ok {
                    // id冲突检查
                    glog.Error(fmt.Errorf("error#unique: val:%v", val))
                    return
                }
                check.Store(val, 0)
                if val == 0 {
                    glog.Error(fmt.Errorf("error"))
                    return
                }
            }()
        }
        wg.Wait()
        elapsed := time.Since(t1)
        glog.Infof("generate 20k ids elapsed: %v", elapsed)
    }
    

    运行结果

    load

    使用说明

    创建Snowflake对象

    // NewSnowflake(datacenterid, workerid int64) (*Snowflake, error)
    // 参数1 (int64): 数据中心ID (可用范围:0-31)
    // 参数2 (int64): 机器ID    (可用范围:0-31)
    // 返回1 (*Snowflake): Snowflake对象 | nil
    // 返回2 (error): 错误码
    s, err := snowflake.NewSnowflake(int64(0), int64(0))
    if err != nil {
        glog.Error(err)
        return
    }
    

    生成唯一ID

    s, err := snowflake.NewSnowflake(int64(0), int64(0))
    // ......
    // (s *Snowflake) NextVal() int64
    // 返回1 (int64): 唯一ID
    id := s.NextVal()
    // ......
    

    通过ID获取数据中心ID与机器ID

    // ......
    // GetDeviceID(sid int64) (datacenterid, workerid int64)
    // 参数1 (int64): 唯一ID
    // 返回1 (int64): 数据中心ID
    // 返回2 (int64): 机器ID
    datacenterid, workerid := snowflake.GetDeviceID(id))
    

    通过ID获取时间戳(创建ID时的时间戳 - epoch)

    // ......
    // GetTimestamp(sid int64) (timestamp int64)
    // 参数1 (int64): 唯一ID
    // 返回1 (int64): 从epoch开始计算的时间戳
    t := snowflake.GetTimestamp(id)
    

    通过ID获取生成ID时的时间戳

    // ......
    // GetGenTimestamp(sid int64) (timestamp int64)
    // 参数1 (int64): 唯一ID
    // 返回1 (int64): 唯一ID生成时的时间戳
    t := snowflake.GetGenTimestamp(id)
    

    通过ID获取生成ID时的时间(精确到:秒)

    // ......
    // GetGenTime(sid int64)
    // 参数1 (int64): 唯一ID
    // 返回1 (string): 唯一ID生成时的时间
    tStr := snowflake.GetGenTime(id)
    

    查看时间戳字段使用占比(41bit能存储的范围:从epoch开始往后69年)

    // ......
    // GetTimestampStatus() (state float64)
    // 返回1 (float64): 时间戳字段使用占比(范围 0.0 - 1.0)
    status := snowflake.GetTimestampStatus()
    

    posted in GuaiK实验室 read more
  • Zero

    GuaikOrg/go-restful


    🚀GO-Restful框架

    快速开始

    安装

    git clone https://github.com/GUAIK-ORG/go-restful.git

    运行

    go run main.go -log_dir=log -alsologtostderr

    测试

    ./test/session.html提供了一个js的登陆测试用例,请双击运行。测试用的邮箱和密码为:email:demo@guaik.org passwd:hello!

    负载测试

    新建文件:abtest.txt,内容如下:

    {
     "email": "demo@guaik.org",
     "passwd": "hello!"
    }
    

    ab测试指令:ab -n 10000 -c 100 -p "./abtest.txt" -T "application/json" -H "Content-Type: application/json" http://localhost:8080/session

    本机测试:(有条件的可用服务器测试)

    参数 配置
    OS MacBook Pro (13-inch, Late 2016, Four Thunderbolt 3 Ports)
    CPU 2.9 GHz 双核Intel Core i5
    RAM 8 GB 2133 MHz LPDDR3

    测试结果:

    ab-load


    框架介绍

    arch

    框架代码在pkg/restful目录下

    go-restful标准化了Restful接口开发,提供了post delete put get四种操作方式。

    ./cmd目录下session.go实现了一个标准的Restful处理者,可参考使用。

    框架提供了标准的返回数据:当status为0时代表操作成功,并且可在body中获取返回数据。

    在handler中设置成功状态:

    resp.Success(map[string]interface{} {
        "uid":   uid,
        "token": token,
    })
    

    客户端接收到的数据为:

    {"status": 0, "error_code": 0, "error_msg": null, "body": {"token": "xxxxxxxx", "uid": "10001"}}
    

    框架提供了多语言的错误信息,可通过配置的形式注册错误信息:

    restful.HandlerOpts{
        // 配置接口错误信息
        MakeErrorFunc: func(err *restful.Errors){
            err.NewError(1000, "email or passwd error")
            err.Translate(1000, "cn", "邮箱或密码错误") // 中文翻译
        },
    },
    

    客户端接收到的数据为:

    {"status": -1, "error_code": 1000, "error_msg": {"cn": "邮箱或密码错误", "en": "email or passwd error"}, "body": null}
    

    框架可自定义请求解析器,默认提供了json格式解析在./parser/json-parser.go中。

    框架支持过滤器队列,对请求数据进行预处理,在目录./filters目录下默认提供了两个过滤器。

    check.go : 负责参数格式校验,支持string,float64,int64,bool,[]interface{},正则表达式校验。

    token.go : 用来校验访问令牌信息。(需结合缓存和数据库进行修改)。

    将过滤器用于处理者:只要有任何一个过滤器error != nil,之后的过滤器将不会被执行,请求将被丢弃。

    restful.HandlerOpts{
        Filters: []restful.Filter{
            // 1、该接口需要验证token,如果token无效将不被执行
            &filter.CheckToken{},
            // 2、校验参数
            &filter.CheckParams{
                // 参数检查
                Params: map[string]interface{}{
                    // 正则校验
                    "email": filter.FieldRegexp(`^([A-Za-z0-9_\-\.])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$`),
                    // 6~12长度字符串校验
                    "passwd": filter.FieldString().SetLength(6, 12),
                },
            },
        },
    },
    

    posted in GuaiK实验室 read more
  • Zero

    Golang允许跨站Http请求


    func (s *Service) Handler(w http.ResponseWriter, request *http.Request) {
        w.Header().Add("Access-Control-Allow-Origin", "*")
        w.Header().Add("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,PATCH,OPTIONS")
        w.Header().Add("Access-Control-Allow-Headers", "Content-Type, Accept, Authorization")
    }
    

    posted in GuaiK实验室 read more
  • Zero

    “Type ‘EventEmitter’ is not generic” ERROR in angular


    产生原因:
    在使用VSCode添加EventEmitter时,IDE会自动导入imports EventEmitter from Node.js

    import { EventEmitter } from "events";
    

    修改成Angular中的EventEmitter即可:

    import { EventEmitter } from "@angular/core";
    

    posted in GuaiK实验室 read more
  • Zero

    Angular2安装Http模块


    默认情况下Angular2没有自带Http模块,需要安装才能使用。


    npm install @angular/http
    

    posted in GuaiK实验室 read more
  • Zero

    Golang基于Redis的分布式锁


    我理解的分布式锁:

    1、为了避免单持有锁的进程奔溃而无法释放锁,所以必须能够为锁设定过期时间自动释放锁资源
    2、使用TTL检查锁是否成功的被设置过期时间,如果返回-1(未被设置)的话,使用Expire为其设定过期时间
    3、在释放锁的时候需要使用Watch命令,确保监测的值在事务执行时时未被改变,如果其他进程修改了锁,会触发事务异常,然后重新执行Watch。


    分享一个我写的Redis封装类,仅实现了连接和锁

    package dao
    
    import (
    	"time"
    
    	"github.com/go-redis/redis/v7"
    	uuid "github.com/satori/go.uuid"
    )
    
    type Rds struct {
    	Client         *redis.Client
    	AcquireTimeout int32
    	LockTimeout    int32
    }
    
    func NewRds(host, passwd string, db int) (rds *Rds, err error) {
    	rdsOpt := &redis.Options{
    		Addr:     host,
    		Password: passwd,
    		DB:       db,
    	}
    	client := redis.NewClient(rdsOpt)
    	_, err = client.Ping().Result()
    	if err != nil {
    		return
    	}
    	rds = &Rds{Client: client, AcquireTimeout: 10, LockTimeout: 10}
    	return
    }
    
    func (r *Rds) AcquireLockWithTimeout(key string) (identifier string, b bool) {
    	identifier = uuid.NewV4().String()
    	lockname := "lock:" + key
    	end := time.Now().Add(time.Second * time.Duration(r.AcquireTimeout))
    	for time.Now().Before(end) {
    		if r.Client.SetNX(lockname, identifier, time.Second*time.Duration(r.LockTimeout)).Val() {
    			// 如果key不存在,并成功设置了key
    			b = true
    			return
    		} else if r.Client.TTL(lockname).Val() == -1 {
    			// 如果key存在,但是没有剩余时间
    			r.Client.Expire(lockname, time.Second*time.Duration(r.LockTimeout))
    		}
    		time.Sleep(time.Microsecond)
    	}
    	return
    }
    
    func (r *Rds) ReleaseLock(key, identifier string) (b bool) {
    	lockname := "lock:" + key
    	txf := func(tx *redis.Tx) error {
    		v, err := tx.Get(lockname).Result()
    		if err != nil {
    			return err
    		}
    		_, err = tx.Pipelined(func(pipe redis.Pipeliner) error {
    			if v == identifier {
    				pipe.Del(lockname)
    				b = true
    			}
    			return nil
    		})
    		return err
    	}
    	for {
    		err := r.Client.Watch(txf, lockname)
    		if err == nil {
    			break
    		} else if err == redis.TxFailedErr {
    			glog.Error(err)
    		}
    	}
    	return
    }
    

    posted in GuaiK实验室 read more
  • Zero

    使用Golang对MongoDB创建索引


    1、下载MongoDB驱动

    go get github.com/mongodb/mongo-go-driver 
    

    2、连接MongoDB

    import (
    	"context"
    	"log"
    
    	"go.mongodb.org/mongo-driver/mongo"
    	"go.mongodb.org/mongo-driver/mongo/options"
    	"go.mongodb.org/mongo-driver/mongo/readpref"
    )
    
    client, err := mongo.NewClient(options.Client().ApplyURI("mongodb://127.0.0.1:27017"))
    if err != nil {
        log.Fatal(err)
    }
    err = client.Connect(context.TODO())
    if err != nil {
        log.Fatal(err)
    }
    

    3、测试连接是否成功

    if err := client.Ping(context.TODO(), readpref.Primary()); err != nil {
        log.Fatal(err)
    }
    

    4、创建唯一索引

    // Email可能为空(注册时被必填项),为了防止
    // 空值的唯一冲突,所以设置成稀疏索引
    opEmail := options.Index()
    opEmail.SetName("email_index")
    opEmail.SetUnique(true)
    opEmail.SetSparse(true)
    
    // 默认唯一索引配置
    opDef := options.Index()
    opDef.SetName("def_index")
    opDef.SetUnique(true)
    
    // 使用配置生成索引对象
    modDef := mongo.IndexModel{
    	Keys: bson.M{
    		"nickname": 1,
    		"phone":    1,
    	}, Options: opDef,
    }
    modEmail := mongo.IndexModel{
    	Keys: bson.M{
    		"email": 1,
    	}, Options: opEmail,
    }
    
    // 为Collection配置索引,Indexes().CreateMany()可以一次
    // 指定多个索引对象
    col := client.Database("db_name").Collection("User")
    col.Indexes().CreateMany(context.TODO(), []mongo.IndexModel{modDef, modEmail})
    

    5、测试

    type RecUserI struct {
    	Nickname string             `bson:"nickname"`
    	Phone    string             `bson:"phone"`
    	Email    string             `bson:"email"`
    }
    // 邮箱为空值,并且使电话冲突
    result, err := col.InsertOne(context.TODO(), RecUserINickname: "guaik0", Phone: "18888888888"})
    if err != nil {
    	log.Fatal(err)
    	return
    }
    result, err = col.InsertOne(context.TODO(), RecUserINickname: "guaik1", Phone: "18888888888"})
    if err != nil {
    	log.Fatal(err)
    	return
    }
    

    posted in GuaiK实验室 read more
  • Zero

    优酷视频提取


    优酷使用的是m3u形式的媒体播放格式,通过拦截Http Response并且判断Content-Type是否等于application/vnd.apple.mpegurl可以很容易的拿到m3u文件,那么就可以批量下载ts格式的视频文件了,按照m3u列表进行播放即可。

    土豆视频提取


    土豆视频采用了定长格式的MP4格式视频,每播放完一段才会缓存下一段,所以相对于优酷来说并不能一次性的拿到所有的视频,在测试的时候发现在广告等待期间它不会加载MP4视频。

    posted in GuaiK实验室 read more