jaysun

jaysun 查看完整档案

北京编辑  |  填写毕业院校腾讯(北京)  |  PHP Golang 编辑 sunshinev.github.io/laravel-gii-home/ 编辑
编辑

Code is law

个人动态

jaysun 提出了问题 · 9月25日

build提示文件过大

The bundle size is significantly larger than recommended.

image.png

这个怎么解决呀

The bundle size is significantly larger than recommended.
Consider reducing it with code splitting: https://umijs.org/docs/load-on-demand
You can also analyze the project dependencies using ANALYZE=1

关注 2 回答 1

jaysun 发布了文章 · 9月21日

2147483647 一个神奇的数字

error: Error 1062: Duplicate entry '134314-2147483647' for key 'xxxx'

这是条MySQL insert语句报的错误,按照常规的思路来看,就是唯一索引重复了。

所以开始检查insert的内容是否有重复,但是却没有搜索到2147483647 这个数字

后来想了一下,灵光一闪~
发现数据库的字段类型是int 这意味着

INT4-21474836482147483647

所以当写入的数字特别大溢出的时候,就会写入的数字都变成2147483647

查看原文

赞 1 收藏 0 评论 1

jaysun 提出了问题 · 7月17日

golang 报错 segmentation violation code=0x1

介绍一个报错

panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x8 pc=0x109aa48]

代码如下

package main

import (
    "log"
)

type Hi struct {
    Name string
}

var h *Hi

func main() {
    /*
        panic: runtime error: invalid memory address or nil pointer dereference
        [signal SIGSEGV: segmentation violation code=0x1 addr=0x8 pc=0x109aa48]
    */
    h.Name = "fail"
    log.Println(h)
}

关注 4 回答 3

jaysun 赞了文章 · 6月15日

Mysql联合索引最左匹配原则

前言

之前在网上看到过很多关于mysql联合索引最左前缀匹配的文章,自以为就了解了其原理,最近面试时和面试官交流,发现遗漏了些东西,这里自己整理一下这方面的内容。


最左前缀匹配原则

在mysql建立联合索引时会遵循最左前缀匹配的原则,即最左优先,在检索数据时从联合索引的最左边开始匹配,示例:
对列col1、列col2和列col3建一个联合索引

KEY test_col1_col2_col3 on test(col1,col2,col3);

联合索引 test_col1_col2_col3 实际建立了(col1)、(col1,col2)、(col,col2,col3)三个索引。

SELECT * FROM test WHERE col1=“1” AND clo2=“2” AND clo4=“4”

上面这个查询语句执行时会依照最左前缀匹配原则,检索时会使用索引(col1,col2)进行数据匹配。

注意

索引的字段可以是任意顺序的,如:

SELECT * FROM test WHERE col1=“1” AND clo2=“2”
SELECT * FROM test WHERE col2=“2” AND clo1=“1”

这两个查询语句都会用到索引(col1,col2),mysql创建联合索引的规则是首先会对联合合索引的最左边的,也就是第一个字段col1的数据进行排序,在第一个字段的排序基础上,然后再对后面第二个字段col2进行排序。其实就相当于实现了类似 order by col1 col2这样一种排序规则。

有人会疑惑第二个查询语句不符合最左前缀匹配:首先可以肯定是两个查询语句都包含索引(col1,col2)中的col1、col2两个字段,只是顺序不一样,查询条件一样,最后所查询的结果肯定是一样的。既然结果是一样的,到底以何种顺序的查询方式最好呢?此时我们可以借助mysql查询优化器explain,explain会纠正sql语句该以什么样的顺序执行效率最高,最后才生成真正的执行计划。

为什么要使用联合索引

  • 减少开销。建一个联合索引(col1,col2,col3),实际相当于建了(col1),(col1,col2),(col1,col2,col3)三个索引。每多一个索引,都会增加写操作的开销和磁盘空间的开销。对于大量数据的表,使用联合索引会大大的减少开销!
  • 覆盖索引。对联合索引(col1,col2,col3),如果有如下的sql: select col1,col2,col3 from test where col1=1 and col2=2。那么MySQL可以直接通过遍历索引取得数据,而无需回表,这减少了很多的随机io操作。减少io操作,特别的随机io其实是dba主要的优化策略。所以,在真正的实际应用中,覆盖索引是主要的提升性能的优化手段之一。
  • 效率高。索引列越多,通过索引筛选出的数据越少。有1000W条数据的表,有如下sql:select from table where col1=1 and col2=2 and col3=3,假设假设每个条件可以筛选出10%的数据,如果只有单值索引,那么通过该索引能筛选出1000W10%=100w条数据,然后再回表从100w条数据中找到符合col2=2 and col3= 3的数据,然后再排序,再分页;如果是联合索引,通过索引筛选出1000w10% 10% *10%=1w,效率提升可想而知!

引申

对于联合索引(col1,col2,col3),查询语句SELECT * FROM test WHERE col2=2;是否能够触发索引?
大多数人都会说NO,实际上却是YES。
原因

EXPLAIN SELECT * FROM test WHERE col2=2;
EXPLAIN SELECT * FROM test WHERE col1=1;

观察上述两个explain结果中的type字段。查询中分别是:

  1. type: index
  2. type: ref

index:这种类型表示mysql会对整个该索引进行扫描。要想用到这种类型的索引,对这个索引并无特别要求,只要是索引,或者某个联合索引的一部分,mysql都可能会采用index类型的方式扫描。但是呢,缺点是效率不高,mysql会从索引中的第一个数据一个个的查找到最后一个数据,直到找到符合判断条件的某个索引。所以,上述语句会触发索引。
ref:这种类型表示mysql会根据特定的算法快速查找到某个符合条件的索引,而不是会对索引中每一个数据都进行一一的扫描判断,也就是所谓你平常理解的使用索引查询会更快的取出数据。而要想实现这种查找,索引却是有要求的,要实现这种能快速查找的算法,索引就要满足特定的数据结构。简单说,也就是索引字段的数据必须是有序的,才能实现这种类型的查找,才能利用到索引。

附:

个人总结资料,如转载请注明出处!

参考文献:
https://www.zhihu.com/questio...
https://segmentfault.com/q/10...
https://github.com/phpforlan/...

查看原文

赞 35 收藏 19 评论 14

jaysun 发布了文章 · 5月13日

Go-sword 项目更新 : Gitbook 文档已发布 + 官方 QQ 群已建立 + 官网已支持讨论留言

Go-sword 项目更新 :Gitbook 文档已发布+官方 QQ 群已建立+官网已支持讨论留言

QQ 群:690393633

官方文档

https://go-sword-doc.osinger.com/

f9becb076946caf5406cc026ae28ada01589366310.jpg

官网更新

https://sunshinev.github.io/go-sword-home/

5b7c12f0ed5efc4158685972defc4eb21589366187.jpg

讨论区

f986253c8bf4000c90a86ac272ead5291589366226.jpg

查看原文

赞 0 收藏 0 评论 0

jaysun 发布了文章 · 4月27日

Go-sword 基于Go语言的可视化web管理后台生成工具

e5355b6316b83dcf4b6be9b7544f8e331588255151.jpg

她能满足你哪些?

  1. 你有代码洁癖,不希望工具过多干扰你的业务逻辑
  2. 你希望生成的页面可以自定义修改,来实现更多的功能
  3. 你希望代码特别简单易读,且容易自定义
  4. 你希望能快速构建管理后台,哪怕仅仅是将数据库里的数据可视化
  5. 你希望一键生成,无需复杂操作
  6. 你希望使用iView的组件,快速构建页面

那么go-sword可以满足你

Github 地址如下:
https://github.com/sunshinev/...

Go-sword(利刃)是一款基于Go语言的可视化web管理后台生成工具

目标就是快速的创建CRUD可视化的后台

根据MySQL的表结构,创建完整的管理后台界面,开发者无需再重复手动的创建具有CRUD能力的页面
只需要点击按钮即可生成完整的管理后台

136e8b44d5d4acf00d5a63125928bd731587996269.jpg

特点

  1. 一键生成,无需写一行代码
  2. 支持增加、删除、编辑、列表、批量删除、分页、检索
  3. 页面基于Vue.js + iView
  4. 针对每个数据表都生成了单独的逻辑文件,开发者可以求使用Vue或者iView来实现功能更加丰富的页面

1626ee1d3300ac6db6669d63721d96381587996351.jpg

开始

安装

go get -u  github.com/sunshinev/go-sword

安装完成后,确保go-sword命令在GOPATH/bin目录下,可执行

启动服务

go-sword -db {db_database} -password {db_password} -user {db_uesr} -module {module_name}

例如:go-sword -db blog -password 123456 -user root -module go-sword-app

以上命令,就是连接数据库blog,用户名root,密码12345,在go-sword命令的当前目录下创建项目go-sword-app

启动成功的提示

Go-Sword will create new project named go-sword-app in current directory

[Server info]
Server port : 8080
Project module : go-sword-app

[db info]
MySQL host : localhost
MySQL port : 3306
MySQL user : root
MySQL password : 123456

Start successful, server is running ...
Please request: http://localhost:8080

参数说明

+---------------------------------------------------+
|                                                   |
|            Welcome to use Go-Sword                |
|                                                   |
|                Visualized tool                    |
|        Fastest to create CRUD background          |
|      https://github.com/sunshinev/go-sword        |
|                                                   |
+---------------------------------------------------+
Usage of go-sword:
  // 要连接的数据库信息
  -db string
      MySQL database
  -host string
      MySQL Host (default "localhost")
  // 重要:module参数单独作解释
  -module string
      New project module, the same as  'module' in go.mod file.   (default "go-sword-app/")
  // go-sword 服务启动的默认端口
  -p string
      Go-sword Server port (default "8080")
  -password string
      MySQL password
  -port int
      MySQL port (default 3306)
  -user string
      MySQL user

参数: -module

-module 参数是代表要创建的项目名称,同时也是新项目go.mod文件中的module字段的值,这点请务必保持一致。

注意

新项目会在运行go-sword命令的当前目录下,直接创建module目录,作为新项目

开始使用服务

Start successful, server is running ...
Please request: http://localhost:8080

根据服务启动的提示,直接点击http://localhost:8080即可进入web的可视化工具页面

59384a43cbc382dec53dd76d169a5d001587995174.jpg

重要:页面功能介绍

  1. 首先下拉选择MySQL 的表格,然后点击Preview按钮,即可渲染出需要创建的文件
  2. 首次创建新项目文件需要点击select all全部选择,首次创建包含了项目启动必需的核心文件
  3. 点击Generate按钮,既可以看到提示文件创建成功
  4. 到目前为止,我们的后台已经创建成功了

注意:

  1. 首次创建,文件需要全部选择
  2. 如果创建第二个管理页面,那么可以只选择 select diff & new按钮,然后点击Generate按钮
  3. 每次生成新的管理界面后,请重启新创建的项目

开始使用新项目

进入到我们新创建的项目目录

➜  test tree -L 2
.
└── go-sword-app
    ├── controller
    ├── core
    ├── go.mod
    ├── go.sum
    ├── main.go
    ├── model
    ├── resource
    ├── route
    └── view

比如说我们,刚刚是在test目录运行的go-sword命令,创建的项目就是test/go-sword-app

我们进入test/go-sword-app目录下按照以下命令启动项目

初始化新项目 go mod init

利用go mod初始化项目,这里的module就是我们前面讲到的要与项目名称保持一致!!

go mod init {module}

启动项目

go run main.go

然后会看到下面的提示,点击http://localhost:8082既可以进入后台管理界面

Enjoy your system ^ ^
Generated by Go-sword
https://github.com/sunshinev/go-sword

[Server info]
Server port : 8082

[db info]
MySQL host : localhost
MySQL port : 3306
MySQL user : root
MySQL password : 123456

Start successful, server is running ...
Please request: http://localhost:8082

管理后台效果

  1. 后端报错提醒
  2. 增加、删除、编辑、列表、批量删除、分页、检索

1626ee1d3300ac6db6669d63721d96381587996351.jpg

一些问题

  1. 因为golang的map结构遍历乱序的问题,部分页面输出的字段顺序不能保证和数据库字段顺序一致
  2. 关于module的参数,可能还会有更好的解决方案
  3. 没有提供用户注册、登录的能力,这也不符合初衷,最开始就是想做的更加基础,快速创建页面
  4. 生成的项目代码,还有很大的优化空间

页面功能展示

列表

ea1f86ebc1b5c88aaf6484fa078584951587997286.jpg

删除

70279af696d9a230001f821cdf3a1ac21587997368.jpg

预览

2d1871a645acc3d3544ad7f77a0d6fca1587997398.jpg

编辑

a9255db26b2af0365655840f6afd27851587997440.jpg

Go-sword fork

如果想要自定义的话,那么需要注意,Go-sword 项目可以打包成一个那单独的命令来执行,因为将所有的静态文件也进行了打包

静态文件压缩命令如下:

go-bindata -o assets/resource/dist.go -pkg resource resource/dist/...
go-bindata -o assets/stub/stub.go -pkg stub stub/...
go-bindata -o assets/view/view.go -pkg view view/...
查看原文

赞 1 收藏 0 评论 2

jaysun 提出了问题 · 4月27日

解决如何代码触发选择iview table的某些指定行?

如何代码触发选择iview table的某些指定行?

关注 3 回答 2

jaysun 发布了文章 · 4月15日

Vue 使用websocket + json + protobuf 与后端交互golang

看这篇文章的时候,千万不要害怕代码,重要的核心的都加注释了,原理很简单!!祝阅读顺利

当学习一门新的语言的时候,总是喜欢先建立一个Demo项目,通过构建一个基本的项目,来了解语言的特点。

对于web的交互,以前常用的技术主要是Ajax、Form表单提交这类,如果要做长连接,可以使用Websocket

关于websocket和socket其实是两个东西,如果要比较的话,应该是拿websocket和http 来比较。

websocket 发送json

websocket发送json这是一种常规的方式

值得一提的是,Vue框架中使用axios发送POST请求的时候,默认Content-Type是application/json,所以在后端接受的时候,要做流处理。

比如像PHP的话,要用php://input,如果是go,那么就要使用下面的代码,来获取请求body的全部内容,然后使用json.Unmarshal来解析

func Receive(w http.ResponseWriter, r *http.Request) {
    b, _ := ioutil.ReadAll(r.Body)
}

我们继续来看Vue的websocket部分

export default {
    data () {
        return {
            username:'',
            email:'',
            content:'',
            message_list:[],
            ws:null
        }
    },
    methods: {
        handleRecv:function(data) {
            var jsonData = JSON.parse(data)
            this.message_list.unshift(jsonData.data)
        },
        wsOpen: function () {
            var that = this
            var ws = new WebSocket("ws://localhost:9000/ws")

            ws.onopen = function () {
                console.info("ws open")
            }

            ws.onmessage = function (evt) {
                that.handleRecv(evt.data)
            }

            ws.onclose  = function () {
                console.info("ws close")
            }

            this.ws = ws
        },
        wsSend: function() {
            if(this.ws == null) {
                console.info("连接尚未打开")
            }

            this.ws.send(JSON.stringify({
                email:this.email,
                content:this.content
            }))
        }
    },
    mounted(){
        this.wsOpen();
    }
}

上面这段代码,是定义在Vue的组件中的。其实核心就是通过mounted组件挂载完成之后,来调用new WebSocket创建连接,并且注册onOpen,onMessage,onClose事件。

通过websocket来发送json,实际上是传递了一个json的字符串,对于基于golang的后端,我们同样需要搭建websocket 服务端来接收消息。

golang的websocket服务

websocket是在http协议上进行的升级。

这里我们使用的是websocket包

"github.com/gorilla/websocket"

对于main函数,我们构建如下

// 定义了flag参数
var addr = flag.String("addr",":9000","http service address")

var upgrader = websocket.Upgrader{}

func main() {
    flag.Parse()

    http.HandleFunc("/ws",echo)

    err := http.ListenAndServe(*addr, nil)
    if err != nil {
        log.Fatalf("%v", err)
    }
}

echo 函数如下定义,不要害怕这么长一段代码,其实核心很简单

  1. 我们首先建立结构体
// 消息回复类型,包含了code和data主体
type MessageRet struct {
    Code int `json:"code"`
    Data interface{} `json:"data"`
}

// card包含了接受参数,存储json解析后的对象
type Card struct {
    Email string  `json:"email"`
    Content string `json:"content"`
}
  1. 下面是主要的echo函数,完成的就是升级http,监听某个链接的请求,并且进行回复

这里面用到的方法主要是

  • upgrader.CheckOrigin 解决跨域问题
  • upgrader.Upgrade()
  • c.ReadMessage()
  • c.WriteMessage()

func echo(w http.ResponseWriter, r *http.Request) {
    // 跨域
    upgrader.CheckOrigin = func(r *http.Request) bool {
        return true
    }
    // 升级http
    c, err := upgrader.Upgrade(w,r,nil)
    if err != nil {
        log.Fatalf("%v",err)
    }

    defer c.Close()

    // 结构体指针
    var card = &Card{}

    for {
        // 阻塞,等待读取消息
        mt, message, err := c.ReadMessage()
        if err !=nil {
            log.Println("read",nil)
            break
        }
        
        // 解析参数
        err = json.Unmarshal(message, card)
        if err != nil {
            log.Fatalf("json parse error %v",err)
            break
        }
        // 这里可以自定义handle处理
        card.Email = "server - "+card.Email
        card.Content = "server - "+card.Content
        
        // 重新打包,返回~~~~
        var ret = MessageRet{Code:http.StatusOK,Data:*card}
        b, _ := json.Marshal(ret)

        log.Printf("recv : %s", b)

        err = c.WriteMessage(mt, b)
        if err != nil {
            log.Fatalf("write",nil)
            break
        }
    }
}

注意上面c.WriteMessage(mt,b) 的第一个参数是MessageType类型,有两个值分别是代表 二进制、文本

websocket结合protobuf

在上面的例子当中,我们看到通过websocket来建立长连接,并且与go的websocket服务进行通信

使用websocket来避免轮询已经是一个减轻服务器请求压力的办法了,那么我们能否在传输协议上在做一些改变,来减小传输的包体大小。

使用protobuf

关于protobuf,是一种编码协议,可以想象一下json、xml。

protoc是proto文件的编译器,proto-gen-go是protoc的插件,可以根据proto文件,编译成go文件。

google-protobuf 现在也支持了生成 js文件。

用protobuf的还有一个好处是指,如果我在go的服务端,定义好了Request的包体内容,以及Response的包体内容,那么生成go文件后,也可以同时生成js文件

这样双方就可以按照同样的参数模式来进行开发,就等于proto文件,相当于接口文档了

那我们先生成一个proto文件,比如说websocket要发请求,goserver要返回内容,那就涉及了两个消息结构的定义

syntax = "proto3";
// 请求
message ChatRequest {
    string email = 1;
    string content = 2;
}
// 响应
message ChatResponse {
    int32 code = 1;
    ChatRequest data = 2;
}

然后使用protoc来生成文件go/js

protoc --go_out=plugins=grpc:. *.proto
protoc --js_out=import_style=commonjs,binary:. *.proto

proto go文件的使用

先引入go的proto文件

import (
    pb "message_board_api/proto/chat"
)

然后再代码中获取到websocket的消息后,进行proto解析,非常简单~~~~

// 使用protobuf解析
        pbr := &pb.ChatRequest{}
        err = proto.Unmarshal(message, pbr)
        if err != nil {
            log.Fatalf("proto 解析失败 %v", err)
            break
        }

proto js文件使用

proto的js文件,需要配合google-protobuf.js一起使用,根据官网文档来讲,如果要在浏览器中使用,需要用browserify进行打包。

在Vue的组件中,引入包

    import "google-protobuf"
    import proto from  "../proto/chat_pb.js"

下面我们来看websocket结合protobuf,与传统的json有什么不同,同样不要害怕这么一大段代码,我们主要看method部分

export default {
    data () {
        return {
            username:'',
            email:'',
            content:'',
            message_list:[
                {
                    email:'',
                    content:'',
                }
            ],
            ws:null
        }
    },
    methods: {
        // 处理protobuf内容
        handleRecv:function(data) {
            // 这里对接收到的二进制消息进行解码
            var rep = proto.ChatResponse.deserializeBinary(data)
            // 可以获取data和code
            console.info(rep.getData())

            console.info(rep.getCode())
            // 这里拼接消息,message_list是vue的一个列表,不要关心
            this.message_list.unshift({email:rep.getData().getEmail(),content:rep.getData().getContent()})
        },
        wsOpen: function () {
            var that = this
            var ws = new WebSocket("ws://localhost:9000/ws")
            // 这个地方特别重要,websocket默认是Uint8array
            ws.binaryType = 'arraybuffer';

            ws.onopen = function () {
                console.info("ws open")
            }

            ws.onmessage = function (evt) {
                console.info(evt)
                console.info("Received message:"+evt.data)
                that.handleRecv(evt.data)
            }

            ws.onclose  = function () {
                console.info("ws close")
            }

            this.ws = ws
        },
        wsSend: function() {
            if(this.ws == null) {
                console.info("连接尚未打开")
            }
            // 发送消息同样很简单,我们不需要关心格式
            var chat = new proto.ChatRequest()
            chat.setEmail(this.email)
            chat.setContent(this.content)

            this.ws.send(chat.serializeBinary())
        }
    },
    mounted(){
        this.wsOpen();
    }
}

只看上面注释的部分即可,其实分为两部分

  1. 按照约定的消息结构,set数据
  2. 按照约定的消息结构,get数据

注意:

  • proto.ChatResponse.deserializeBinary 是一个静态方法,不需要New
  • 一定要修改为arraybuffer,二进制数组

    ws.binaryType = 'arraybuffer';

通过上面的流程,我们已经基本了解了protobuf在websocket中的配合使用,除此之外还有一个protobuf.js 也很火,但是没有做特别的研究,比较喜欢官方的。

广播boardcast

但是这里有个细节问题,如果要建立通信,一般上来讲,我们不会直接将信息返回回去。因为websocket是全双工通信,不像http一样请求一次、返回一次、close。

如果我们要用websocket,第一个反应是长连接,搞个聊天室实时聊天。那么下面我们来实现一个聊天能力。

那么聊天能力,其实有一个特点就是广播,一个人说话,所有人都能收到。这样特别有意思,很久以前用ajax来做的话,还需要把数据存起来,然后每次再轮询读取输出给前端。现在都不用存到数据库里了。

核心:

  1. 广播实际上就是把消息广播给所有与服务器建立连接的客户端

看下实现

先建立一个map,用来储存客户端连接,在建立个消息缓冲通道

// 客户端集合
var clients = make(map[*websocket.Conn]string)

// 消息缓冲通道
var messages = make(chan *pb.ChatRequest, 100)

每次新建连接之后,都会将链接保存到client当中,进行缓存。

c, err := upgrader.Upgrade(w, r, nil)
    if err != nil {
        log.Fatalf("%v", err)
    }
    defer c.Close()

    // 将链接的地址写入到clients中
    who := c.RemoteAddr().String()
    log.Println(who)
    clients[c] = who

proto解析的部分已经讲过了,这里将解析的内容写入到messages通道里面。

    // 使用protobuf解析
        pbr := &pb.ChatRequest{}
        err = proto.Unmarshal(message, pbr)
        if err != nil {
            log.Fatalf("proto 解析失败 %v", err)
            break
        }

        // 解析后的内容写入messages 进行广播
        pbr.Email = pbr.Email + "<" + who + ">"
        messages <- pbr

下面就是核心的boardcast方法

func boardcast() {
    // 始终读取messages
    for msg := range messages {
        // 读取到之后进行广播,启动协程,是为了立即处理下一条msg
        go func() {
            for cli := range clients {
                // protobuf协议
                pbrp := &pb.ChatResponse{Code: http.StatusOK, Data: &pb.ChatRequest{Email: msg.Email, Content: msg.Content}}
                b, err := proto.Marshal(pbrp)
                if err != nil {
                    log.Fatalf("proto marshal error %v", err)
                }

                // 二进制发送
                cli.WriteMessage(websocket.BinaryMessage, b)
            }
        }()
    }
}

上面的boardcast方法,要交给协程goroutine处理,不然for range messages 会阻塞,所以在main方法中使用协程

func main() {
    flag.Parse()

    http.HandleFunc("/ws", echo)
    // 广播
    go boardcast()

    // pprof 这是一个状态监控,可以忽略,有空也可以研究下
    go func() {
        log.Println(http.ListenAndServe("localhost:6060", nil))
    }()

    // 这里的ListenAndServe 已经开启了goroutine协程了
    err := http.ListenAndServe(*addr, nil)
    if err != nil {
        log.Fatalf("%v", err)
    }
}

小结一下

对于创建websocket和protobuf的聊天能力来说有如下的流程

  1. 双方要实现websocket通信
  2. 约定proto消息协议,生成go和js两个版本
  3. 使用goroutine协程,来进行广播

其中上面的例子,并没有在client退出的时候,从clients中将链接删除,实际上可以用下面的形式,来删除,并且conn.close关闭连接。

delete(clients,cli)

希望上面的内容,对大家有所帮助。详细问题可以留言讨论

查看原文

赞 0 收藏 0 评论 0

jaysun 回答了问题 · 1月18日

解决开发 Laravel 包的时候,能在一个 Laravel 包里面使用其他 Laravel 包吗?

可以的,你看下这个项目的composer.json 配置文件
https://github.com/sunshinev/...

"require": {
        "sebastian/diff": "^3.0",
        "sunshinev/laravel-fe-render":"^1.0",
        "doctrine/dbal": "^2.10"
    },
    "require-dev": {
        "phpunit/phpunit": "^8.0",
        "orchestra/testbench": "^3.8|^4.0"
    },

这样可以在主项目引入这个包的时候,自动加载该扩展包依赖的其他扩展

关注 2 回答 2

jaysun 发布了文章 · 2019-12-11

Alfred Workflow 一键上传图片到github 返回markdown

Alfred Workflow 一键上传图片到github

在编写markdown文件中,截图作说明,可以直接截图后将图片上传到github,并且返回markdown格式的图片语法

注意:Pillow模块不支持从剪贴板获取gif图片,所以目前不支持gif上传

image

运行环境

Alfred + Mac

支持图片类型

  • JPG
  • PNG

工作原理

  1. 使用Alfred热键功能触发Workflow工作流程,执行Python脚本。
  2. 使用Pillow模块从剪贴板Clipboard中获取jpg/png图片文件,并且移动到project_path目录下
  3. 提交project_path目录下的文件到github仓库

安装

安装python的Pillow模块

pip install Pillow

下载Alfred的Workflow并打开安装

下载Markdown-image-upload-github

配置

打开Alfred的Workflow配置脚本的变量

配置项说明
github_repo图床仓库名称
github_username用户名
project_path本地的图床项目路径

image

上传图片

上传图片支持两种方式,一种是截取图片到剪贴板、另外一种是直接复制图片文件

截取图片到剪贴板(微信截图、mac自带截图工具)

  1. 截取图片到剪贴板
  2. command+g

复制jpg、png文件

  1. 复制图片文件
  2. command+g

说明

相关原理借鉴了《快速上传图片到七牛云空间kaito-kidd/markdown-image-alfred》

查看原文

赞 0 收藏 0 评论 0

认证与成就

  • 获得 77 次点赞
  • 获得 22 枚徽章 获得 0 枚金徽章, 获得 3 枚银徽章, 获得 19 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

  • laravel-gii

    可视化代码生成工具 CRUD +GUI 简洁、快捷安装,一键根据MySQL的表结构生成对应的Model、Observer、Controller、View、Route等相关项目文件,通过简单点击鼠标即可自动创建完整的CRUD后台,极大的提高了B端的开发效率。 生成的页面遵循vue的单文件模板,并且使用iview作为组件库。

  • go-sword

    Go-sword(利刃)是一款基于Go语言的可视化web管理后台生成工具 官方网站 https://sunshinev.github.io/go-sword-home/ 根据MySQL的表结构,创建完整的管理后台界面,开发者无需再重复手动的创建具有CRUD能力的页面 只需要点击按钮即可生成完整的管理后台

注册于 2015-08-20
个人主页被 1.2k 人浏览