Debian 9 配置 vue 开发环境

最近开始捡起来前端 vue 的开发,这里记录一下环境搭建的过程。

安装nodejs

官网教程 适用于大部分场景了。在这里我安装当前最新的LTS版 v14.x,两个命令即可:

curl -sL https://deb.nodesource.com/setup_14.x | bash -
apt-get install -y nodejs

很不幸,在我的机器上出现了下面的错误:

The AppStream system cache was updated, but some errors were detected, which might lead to missing metadata.

image-20201125122253041

实际上我也没有头绪。随意敲了几个命令,upgrade 了几个包解决了,具体原因还是不清楚:

apt-get update
apt-get upgrade
apt autoremove

image-20201125122823911

确认版本:

node -v
npm -v

image-20201125130514125

nodejs配置

使用阿里的镜像源

npm install -g cnpm --registry=https://registry.npm.taobao.org

在 .zshrc 或 .bashrc 中添加新命令

alias cnpm="npm --registry=https://registry.npm.taobao.org \
--cache=$HOME/.npm/.cache/cnpm \
--disturl=https://npm.taobao.org/dist \
--userconfig=$HOME/.cnpmrc"

安装vue-cli

npm install -g @vue/cli

查看版本:

vue --version

@vue/cli 4.5.9

很遗憾,在这里我也遇到了问题。

大概可以看出来系统已经安装过了vue,而且是 2.x 版本的vue。在这里先进行卸载。

npm ERR! EEXIST: file already exists, symlink '../lib/node_modules/@vue/cli/bin/vue.js' -> '/usr/bin/vue'

image-20201125132803736

卸载:

npm uninstall -g vue-cli

创建demo应用

由于目前vue已经到3的版本了,3也兼容2的版本。所以这里我就直接使用 vue3 做测试了。

vue create kelu

会弹出相关的交互式问题,下面是我做的选项,把 vuex router都安排上了。

image-20201125135011676

运行

npm run serve

从原有代码中运行

npm install
npm run serve

一个简单的 gin websocket 例子

这个例子是我在开发时候写的测试demo,框架基于 Gin。

基本功能非常简单:

  • 当客户端连接上的时候,发送一个自定义的id,

  • 当收到客户端的json信息时,基于不同的返回码,打印日志。

代码包含两个文件,一个是ws的通用文件ws.go,一个是逻辑处理文件kelu.go,在这里做个记录。

依赖包

github.com/gin-gonic/gin
github.com/gorilla/websocket

ws.go


package ws

import (
  "github.com/gorilla/websocket"
  "net/http"
  "github.com/gin-gonic/gin"
  "fmt"
  "github.com/kelu.org/base/pkg/app"
)

// WebSocket 更新用
var keluWsUpgrader = websocket.Upgrader{
  ReadBufferSize:  1024,
  WriteBufferSize: 1024,
  CheckOrigin:     checkOrigin,
}

func checkOrigin(r *http.Request) bool {
  return true
}

// 路由入口
func ApiWs(c *gin.Context) {
  ws, err := keluWsUpgrader.Upgrade(c.Writer, c.Request, nil)
  if err != nil {
    return
  }

  wsid, err := KeluWsOpen(ws)
  fmt.Println("kelu open socket: ",wsid)
  if err != nil {
    return
  }

  defer ws.Close()

  for {
    //读取ws中的数据
    receivedData := app.Response{}

    // メッセージ読み込み
    err = ws.ReadJSON(&receivedData)
    if err != nil {
      fmt.Printf("KeluWs reading ws error: %v", err)
      break
    }

    switch receivedData.Code {
    case http.StatusAccepted:
      // some code ... ...
      break;
    case http.StatusOK:
      // some code ... ...
      break;

    default:
      ws.WriteJSON(KeluWsBad(wsid,receivedData.Data))
    }

  }

  KeluWsDone(wsid)
  return
}

kelu.go

func KeluWsOpen(ws *websocket.Conn) (int64, error) {
  wsid := time.Now().UnixNano()
  welcome := app.Response{http.StatusAccepted, "ok", "ok", map[string]interface{}{"wsid": wsid}}
  err := ws.WriteJSON(welcome)
  if err != nil {
    fmt.Printf("open websocket error while writing message to client: %v", err)
    ws.Close()
    return 0, err
  }

  return wsid, nil
}

func KeluWsBad(wsid int64, data interface{}) *app.Response {
  resultData := app.Response{http.StatusBadRequest, strconv.FormatInt(wsid, 10), "", data}
  return &resultData
}

func KeluWsDone(wsid int64){
  // some code ... ...
}

golang jwt 解析过期信息

golang 下解析 jwt 信息,基本上都用的这个包. https://github.com/dgrijalva/jwt-go

官方的 GitHub 案例,也提供了简单的解析方法:

import "github.com/dgrijalva/jwt-go"

func ParseToken(token string) (*Claims, error) {
  tokenClaims, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (interface{}, error) {
    return jwtSecret, nil
  })

  if tokenClaims != nil {
    if claims, ok := tokenClaims.Claims.(*Claims); ok && tokenClaims.Valid {
      return claims, nil
    }
  }

  return nil, err
}

ParseWithClaims方 法里会对token做验证,只有验证通过的才会进行后续的逻辑。

但对于已经过期的信息,验证显然是不能通过的,使用这个方法会抛出 get claim is nil 的错误。这时候应该使用 ParseUnverified 方法 跳过验证.

func GetUsernameFromToken(cookie string) (string, error) {
  token, _, err := new(jwt.Parser).ParseUnverified(cookie, jwt.MapClaims{})
  if err != nil {
    return "", err
  }

  if claims, ok := token.Claims.(jwt.MapClaims); ok {
    jwtToken := KeluJwtToken{}
    tmp, err := json.Marshal(claims)
    if err != nil {
      return "", err
    }

    err = json.Unmarshal(tmp, &jwtToken)
    if err != nil {
      return "", err
    }
    return jwtToken.Username, nil
  }

  return "", errors.New("cannot parase cookie")
}

参考资料


golang 解析 map[string]interface{} 和 json 到 struct

从 json 转成 strcut 可以算是go开发中的常事了。这里记录两个窍门。

通过 json 自动生成 struct 声明

一般来说如果只有三四个字段,还是比较轻松的,如果有50个以上的字段,光是定义结构体,都要累死一拨人了~

这时候这个网站 https://app.quicktype.io/ 真是个神器一般的存在,可以根据 json 定义go struct。

image-20201215165155521

猜测json的内部数据类型

进行开发,解析json数据时,我先写了以下的测试代码,用于判断数据的格式。不得不说,强类型的语言就是有这种不方便。

同时要注意下面几个事项:

  • 解析出来的 int 类型会变成 float64 类型
  • 要注意和前端的交互,某些情形下前端解析 int64 会有精度问题,如果涉及前端的交互,尽量避免使用int64,或先转成string再与前端交互。
func GuessType(k string, v interface{})  {
  switch value := v.(type) {
  case nil:
    fmt.Println(k, "is nil", "null")
  case string:
    fmt.Println(k, "is string", value)
  case int:
    fmt.Println(k, "is int", value)
  case float64:
    fmt.Println(k, "is float64", value)
  case []interface{}:
    fmt.Println(k, "is an array:")
    for i, u := range value {
      fmt.Println(i, u)
    }
  case map[string]interface{}:
    fmt.Println(k, "is an map:")
  default:
    fmt.Println(k, "is unknown type", fmt.Sprintf("%T", v))
  }

}

func GuessTypeFromMapStringInterface(m map[string]interface{}){
  for k, v := range m {
    GuessType(k,v)
  }
}

将 map[string]interface{} 和 json 转成struct

由上一个步骤接着往下走,使用 encoding/json 库中的格式化命令,先将 json 格式化,再反格式化成 struct:

import "encoding/json"

  tmpJson, err := json.Marshal(data)
  if err != nil {
    return nil, err
  }
  create := KeluTest{}
  json.Unmarshal(tmpJson, &create)

参考资料


golang mgo 库的简单使用备忘

最近 golang 开发用到 mongodb,由于一些原因,我使用了 mgo 包进行开发。这篇文章记录一下 mgo 包的用法,一个比较浅的记录,做个备忘。

目前 mgo 已经不维护了,类似的包还有 官方七牛 (七牛的包基于mongo官方)的,都不错,可以试试。

mgo 的使用例子推荐这个网站——learningprogramming.net,写的挺好。

安装

go get gopkg.in/mgo.v2

使用

连接服务器

var mgoSession *mgo.Session
var mgoDatabase *mgo.Database

func GetMgo() *mgo.Session {
	return mgoSession
}

func GetDataBase() *mgo.Database {
	sessionCopy := mgoSession.Copy()
	mgoDatabase = sessionCopy.DB(setting.Config.Mongo.Database)
	return mgoDatabase
}

type Config struct {
	URL        string
	Database   string
	Collection string
	ReplicaSet string
}

func Setup(){
  mgoSession, err := mgo.Dial("username:password@10.19.0.57:7018,10.19.0.58:7018/dbname?replicaSet=replsetname")
  if err != nil {
    logrus.Error(err)
  }

  mgoSession.SetMode(mgo.Monotonic, true)
  mgo.SetDebug(true)
}

// 创建索引
func EnsureIndex(col string,keyArray []string) {
	mongoCol := GetDataBase().C(col)

	for _, key := range keyArray {
		err := mongoCol.EnsureIndex(
			mgo.Index{
				Key: []string{key},
				Background: true,
			})

		if err != nil {
			logrus.Errorln("create index error: ", err)
		}
	}
}

切换db

db := session.DB("test")

切换collection

c := db.C("users")

这样就能对mongo进行操作啦。

查找

查询可以用find,也可以用聚合管道(Aggregation Pipeline)。

find 就是普通的查询,聚合管道可以对集合中的文档进行变换和组合。

聚合管道以一个集合中的所有文档作为开始,然后这些文档从一个操作节点流向下一个节点 ,每个操作节点对文档做相应的操作。这些操作可能会创建新的文档或者过滤掉一些不符合条件的文档,在管道中可以对文档进行重复操作。

find

m := bson.M{
        "CurTimestamp": bson.M{
            "$gte": start,
            "$lte": end,
        },
        "Account":    account,
        "ToNodeType": "cloud",
    }

c.Find(m).Count()

pipeline

import "gopkg.in/mgo.v2/bson"

  match := bson.M{}

  if len(receivedData.Method) > 0 {
    matchIn := []interface{}{}
    for _, v := range receivedData.Method {
      matchIn = append(matchIn, v)
    }
    match["RequestMethod"] = bson.M{"$in":matchIn}
  }
  
  match["time"] = bson.M{"$lt": endedAt,"$gt": startedAt}
  

  pipeline := []bson.M{
    {"$match": match},
    {"$sort": bson.M{"time": -1}},
    {"$limit": limit},
    {"$skip": skip},
  }
  
  var xxxList []xxxItem
  c.Pipe(pipeline).All(&xxxList)

mapreduce

目前还没有使用mapreduce,以下例子来自《谈谈一些关于mgo的用法》

    m := new(mgo.MapReduce)
    m.Map = `function() { var date = new Date();
    date.setTime(this.CurTimestamp / 1000);
    var hour = date.getHours();
    if((hour >= 6) && (hour <= 11)) {
        result.morning++;
    }else if((hour >= 12) && (hour <= 18)){
        result.afternoon ++;
    }else if((hour >= 19) && (hour <= 23)) {
        result.night ++;
    }else{
        result.am ++;
    }
    emit(this.Account, {});}`
    m.Reduce = `function() {return result;}`
    m.Scope = bson.M{
        "result": bson.M{
            "morning":   0,
            "afternoon": 0,
            "night":     0,
            "am":        0,
        },
    }
    var value []timeResult
    c.Find().MapReduce(m, &value)

update

import "gopkg.in/mgo.v2/bson"

err = c.UpdateId(bson.ObjectIdHex(xxx.ID.Hex()), bson.M{"$set": bson.M{"ClientUsername": username,}})

err := c.Update(bson.M{"wsid": wsid}, bson.M{"$set": bson.M{"endtime": time.Now(),}})

修改字段的值($set)

c.Update(bson.M{"_id": bson.ObjectIdHex("5204af979955496907000001")}, bson.M{"$set": bson.M{ "name": "Jimmy Gu", "age": 34, }})

字段增加值inc($inc)

c.Update(bson.M{"_id": bson.ObjectIdHex("5204af979955496907000001")}, bson.M{"$inc": bson.M{ "age": -1, }})

从数组中增加一个元素push($push)

c.Update(bson.M{"_id": bson.ObjectIdHex("5204af979955496907000001")}, bson.M{"$push": bson.M{ "interests": "Golang", }})

从数组中删除一个元素pull($pull)

c.Update(bson.M{"_id": bson.ObjectIdHex("5204af979955496907000001")}, bson.M{"$pull": bson.M{ "interests": "Golang", }})

create

  create := XxxStruct{}
  create.Time = time.Now()
  create.Wsid = wsid
  c.Insert(create)

remove

c.Remove(bson.M{"name": "Jimmy Kuu"})

条件匹配

=($eq)

c.Find(bson.M{"name": "Jimmy Kuu"}).All(&users)

!=($ne)

c.Find(bson.M{"name": bson.M{"$ne": "Jimmy Kuu"}}).All(&users)

>($gt)

c.Find(bson.M{"age": bson.M{"$gt": 32}}).All(&users)

<($lt)

c.Find(bson.M{"age": bson.M{"$lt": 32}}).All(&users)

>=($gte)

c.Find(bson.M{"age": bson.M{"$gte": 33}}).All(&users)

<=($lte)

c.Find(bson.M{"age": bson.M{"$lte": 31}}).All(&users)

in($in)

c.Find(bson.M{"name": bson.M{"$in": []string{"Jimmy Kuu", "Tracy Yu"}}}).All(&users)

$nin 不在集合内

iter = c.Find(bson.M{"city": bson.M{"$nin": []string{"Shanghai", "Hangzhou"}}}).Iter()

$exists 是否包含键

iter = c.Find(bson.M{"city": bson.M{"$exists": true}}).Iter()

键值为null(键存在,键值为null)

iter = c.Find(bson.M{"city": bson.M{"$in": []interface{}{nil}, "$exists": true}}).Iter()

and($and)

c.Find(bson.M{"name": "Jimmy Kuu", "age": 33}).All(&users)

or($or)

c.Find(bson.M{"$or": []bson.M{bson.M{"name": "Jimmy Kuu"}, bson.M{"age": 31}}}).All(&users)

结果操作

排序 Sort

//按age升序,如果要降序Sort("-age")
iter = c.Find(bson.M{"age": bson.M{"$gte": 33}}).Sort("age").Iter()

限定结果数量 Limit

//使用Limit限定只去5条记录
iter = c.Find(bson.M{"age": bson.M{"$gte": 20}}).Sort("age").Limit(5).Iter()

跳过指定数量的记录 Skip

//跳过两条记录,取接下来的5条记录
iter = c.Find(bson.M{"age": bson.M{"$gte": 20}}).Sort("age").Skip(2).Limit(5).Iter()

计算记录的条数 Count

recordsCount, err := c.Find(bson.M{"age": bson.M{"$gte": 20}}).Count()

参考资料


windows 通过命令行设置应用程序的窗口位置

目前还没有使用,我总觉得以后会用到的,先转过来了。

AutoHotKey非常适合窗口定位任务。

这是一个示例脚本。将其命名为 notepad.ahk ,然后从命令行运行它或双击它。

Run, notepad.exe
WinWait, ahk_class Notepad
WinActivate
WinMove A,, 10, 10, A_ScreenWidth-20, A_ScreenHeight-20

它将启动一个应用程序(记事本),然后调整窗口大小,使其在窗口中居,所有边都有10像素边框。

参考资料