-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfeed.xml
More file actions
72 lines (72 loc) · 48.6 KB
/
feed.xml
File metadata and controls
72 lines (72 loc) · 48.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
<?xml version="1.0" encoding="UTF-8"?><rss version="2.0">
<channel>
<title>TargetLiu</title>
<link>http://127.0.0.1:9899</link>
<description>TargetLiu - 吃土的码农</description>
<managingEditor>os@targetliu.com (TargetLiu)</managingEditor>
<pubDate>Fri, 26 May 2017 18:09:58 +0800</pubDate>
<item>
<title>Golang中WaitGroup、Context、goroutine定时器及超时学习笔记</title>
<link>http://http://targetliu.com/2017/5/26/golang-WaitGroup-context-ticker-timeout.html</link>
<description><blockquote>
<p>好久没有发过文章了 - -||,今天发一篇 <code>golang</code> 中 <code>goroutine</code> 相关的学习笔记吧,以示例为主。</p>
</blockquote>

<h2 id="waitgroup">WaitGroup</h2>

<p><code>WaitGroup</code> 在 <code>sync</code> 包中,用于阻塞主线程执行直到添加的 <code>goroutine</code> 全部执行完毕。</p>

<h2 id="context">Context</h2>

<p><code>Context</code> 是在 <code>Go1.7</code> 中移入标准库的。</p>

<blockquote>
<p><code>Context</code> 包不仅实现了在程序单元之间共享状态变量的方法,同时能通过简单的方法,使我们在被调用程序单元的外部,通过设置ctx变量值,将过期或撤销这些信号传递给被调用的程序单元。</p>
</blockquote>

<h2 id="goroutine的定时器及超时">goroutine的定时器及超时</h2>

<p>这是两个有趣又实用的功能,在标准库 <code>time</code> 包里提供。</p>

<h2 id="示例">示例</h2>

<h3 id="源码">源码</h3>

<!--more-->
<pre><code class="language-go">package main

import (
 &quot;context&quot;
 &quot;fmt&quot;
 &quot;sync&quot;
 &quot;time&quot;
)

func main() {
 ch := make(chan int)
 //定义一个WaitGroup,阻塞主线程执行
 var wg sync.WaitGroup
 //添加一个goroutine等待
 wg.Add(1)
 //goroutine超时
 go func() {
 //执行完成,减少一个goroutine等待
 defer wg.Done()
 for {
 select {
 case i := &lt;-ch:
 fmt.Println(i)
 //goroutine内部3秒超时
 case &lt;-time.After(3 * time.Second):
 fmt.Println(&quot;goroutine1 timed out&quot;)
 return
 }
 }
 }()
 ch &lt;- 1
 //新增一个1秒执行一次的计时器
 ticker := time.NewTicker(1 * time.Second)
 defer ticker.Stop()
 //新增一个10秒超时的上下文
 background := context.Background()
 ctx, _ := context.WithTimeout(background, 10*time.Second)
 //添加一个goroutine等待
 wg.Add(1)
 go func(ctx context.Context) {
 //执行完成,减少一个goroutine等待
 defer wg.Done()
 for {
 select {
 //每秒一次
 case &lt;-ticker.C:
 fmt.Println(&quot;tick&quot;)
 //内部超时,不会被执行
 case &lt;-time.After(5 * time.Second):
 fmt.Println(&quot;goroutine2 timed out&quot;)
 //上下文传递超时信息,结束goroutine
 case &lt;-ctx.Done():
 fmt.Println(&quot;goroutine2 done&quot;)
 return
 }
 }
 }(ctx)
 //等待所有goroutine执行完成
 wg.Wait()
}
</code></pre>

<h3 id="执行结果">执行结果</h3>
<pre><code>1
tick
tick
tick
goroutine1 timed out
tick
tick
tick
tick
tick
tick
tick
goroutine2 done
</code></pre>
</description>
<author>TargetLiu</author>
<pubDate>Fri, 26 May 2017 17:13:58 +0000</pubDate>
</item>
<item>
<title>开源一个Golang写的Excel(xlsx)导入MySQL小工具</title>
<link>http://http://targetliu.com/2016/9/16/golang-excel-xlsx-to-mysql.html</link>
<description><h2 id="简介">简介</h2>

<p>这是工作中用到的一个小工具,将Excel(xlsx)表导入MySQL表中,用Golang写的,每条记录单独一条 <code>goroutine</code> 处理,提高效率。支持随机数生成、密码生成、时间戳;支持关联查询、附表操作等。</p>

<h2 id="使用方法">使用方法</h2>

<ol>
<li><p>使用Go编译安装或直接下载:<a href="https://github.com/TargetLiu/xlsxtomysql/releases">https://github.com/TargetLiu/xlsxtomysql/releases</a></p></li>

<li><p>使用命令: <code>xlsxtomysql [DSN] [数据表名称] [*.xlsx]</code></p></li>
</ol>

<!--more-->

<h2 id="dsn">DSN</h2>

<p>格式:</p>
<pre><code>[username[:password]@][protocol[(address)]]/dbname[?param1=value1&amp;...&amp;paramN=valueN]
</code></pre>

<p>示例:</p>
<pre><code>root:123@tcp(127.0.0.1:3306)/dbname
</code></pre>

<p>注意:
Linux、Mac下可能需要输入 <code>\( \)</code></p>

<h2 id="excel表导入结构说明">Excel表导入结构说明</h2>

<ol>
<li><p>只支持单Sheet</p></li>

<li><p>第一行对应数据库表字段</p>

<ul>
<li><p>通过 <code>|</code> 分割</p></li>

<li><p><code>字段名|unique</code> 判断重复,重复的自动跳过</p></li>

<li><p><code>字段名|password|[md5|bcrypt]</code> 密码生成,第二个参数[md5|bcrypt]</p></li>

<li><p><code>字段名|find|表名|需要获取的字段|查询字段</code> 根据内容从其它表查询并获取字段,格式 <code>SELECT 需要获取的字段 FROM 表名 WHERE 查询字段 = 内容</code></p></li>

<li><p><code>:other</code> 附表操作</p></li>
</ul></li>

<li><p>内容行</p>

<ul>
<li><p><code>:random</code> 生成随机字符串</p></li>

<li><p><code>:time</code> 当前unix时间戳</p></li>

<li><p><code>:null</code> 空值,自动跳过该值,一般可用于自增id</p></li>

<li><p>如果该列为 <code>password</code> ,内容为[明文密码]或[明文密码|盐],盐可以通过[:字段名]获取之前的字段名。密码将自动根据字段名中填写的加密方式进行加密</p></li>

<li><p>如果该列为 <code>other</code> , 格式[表名|字段1|字段2|字段3&hellip;.] 其它表需要按顺序为每个字段添加内容。字段可以为[:null|:id(主表生成的自增ID)|:random|:time]</p></li>
</ul></li>
</ol>

<h2 id="excel截图">Excel截图</h2>

<p><img src="https://github.com/TargetLiu/xlsxtomysql/raw/master/screenshot.jpg" alt="Excel截图" /></p>

<h2 id="github">GitHub</h2>

<p><a href="https://github.com/TargetLiu/xlsxtomysql">https://github.com/TargetLiu/xlsxtomysql</a></p>
</description>
<author>TargetLiu</author>
<pubDate>Fri, 16 Sep 2016 15:37:41 +0000</pubDate>
</item>
<item>
<title>Golang学习笔记 - 标准库'net/http'的简析及自制简单路由框架</title>
<link>http://http://targetliu.com/2016/8/18/golang-http-router.html</link>
<description><blockquote>
<p>还是在继续学习Go的路上,曾经在使用PHP的时候吃过过度依赖框架的亏。现在学习Go的时候决定先打好基础,从标准库学起走。</p>
</blockquote>

<h2 id="源码分析">源码分析</h2>

<p>我们知道最简单的建立http服务器代码基本上都是这样的:</p>
<pre><code class="language-go">http.HandleFunc('/', func(w http.ResponseWriter, r *http.Request){
 fmt.Fprint(w, &quot;Hello world&quot;)
})
http.ListenAndServe(&quot;:8080&quot;, nil)
</code></pre>

<p>这样就成功的建立了一个监听 <code>8080</code> 端口的http服务器,当访问的时候输出 <code>Hello world</code></p>

<p>我们顺藤摸瓜来看看 <code>HandleFunc</code> 做了些什么事:</p>
<pre><code class="language-go">func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
 DefaultServeMux.HandleFunc(pattern, handler)
}
</code></pre>

<!--more-->

<p>这里继续通过调用 <code>DefaultServeMux</code> 的 <code>HandleFunc</code> 方法注册路由,这个 <code>DefaultServeMux</code> 又是何方圣神:</p>
<pre><code class="language-go">type ServeMux struct {
 mu sync.RWMutex
 m map[string]muxEntry
 hosts bool // whether any patterns contain hostnames
}

type muxEntry struct {
 explicit bool
 h Handler
 pattern string
}

// NewServeMux allocates and returns a new ServeMux.
func NewServeMux() *ServeMux { return new(ServeMux) }

// DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux = &amp;defaultServeMux

var defaultServeMux ServeMux
</code></pre>

<p><code>DefaultServeMux</code> 是 <code>net/http</code> 包提供的一个默认的 <code>ServeMux</code> 类型,<code>ServeMux</code> 实现了 <code>Handler</code> 接口。</p>

<p>追根究底,发现http服务器收到一条请求后通过 <code>go c.serve(ctx)</code> 开启<code>goroutine</code> 处理这个请求,在这个过程中调用了 <code>Handler</code> 接口函数 <code>ServeHTTP</code> 来做进一步的处理(比如匹配方法、链接等等)。</p>

<p>所以,我们就可以理解 <code>ServeMux</code> 就是 <code>net/http</code> 一个内置的路由功能。</p>

<p>继续回到 <code>HandleFunc</code> 来:</p>
<pre><code class="language-go">func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
 mux.Handle(pattern, HandlerFunc(handler))
}
</code></pre>

<p><code>ServeMux</code> 的 <code>HandleFunc</code> 方法将我们传入的路由具体实现函数转换成 <code>HandlerFunc</code> 类型并通过 <code>Handle</code> 注册到路由。这个 <code>HandlerFunc</code> 类型也实现了 <code>Handler</code> 接口:</p>
<pre><code class="language-go">type HandlerFunc func(ResponseWriter, *Request)

// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
 f(w, r)
}
</code></pre>

<p>最后到了 <code>Handle</code> 这个方法, <code>Handle</code> 方法通过将 <code>pattern</code> 路径以及实现了 <code>Handler</code> 接口的方法一一对应的保存到 <code>ServeMux</code> 的 <code>map[string]muxEntry</code> 中,方便后续请求的时候调用。因此,也可以通过 <code>Handle</code> 直接传入一个实现了 <code>Handler</code> 接口的方法注册路由。</p>

<p>至此,<code>net/http</code> 包中默认路由的注册过程基本上已经走完。</p>

<p>至于请求的时候路由调用,记住通过 <code>ServeHTTP</code> 查找 <code>map</code> 中对应路径并调用相关方法就行了。</p>

<h2 id="自制路由">自制路由</h2>

<p>通过以上的分析,我们可以依样画葫芦,实现自己的路由功能。</p>
<pre><code class="language-go">package route

import (
 &quot;net/http&quot;
 &quot;strings&quot;
)

//返回一个Router实例
func NewRouter() *Router {
 return new(Router)
}

//路由结构体,包含一个记录方法、路径的map
type Router struct {
 Route map[string]map[string]http.HandlerFunc
}

//实现Handler接口,匹配方法以及路径
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
 if h, ok := r.Route[req.Method][req.URL.String()]; ok {
 h(w, req)
 }
}

//根据方法、路径将方法注册到路由
func (r *Router) HandleFunc(method, path string, f http.HandlerFunc) {
 method = strings.ToUpper(method)
 if r.Route == nil {
 r.Route = make(map[string]map[string]http.HandlerFunc)
 }
 if r.Route[method] == nil {
 r.Route[method] = make(map[string]http.HandlerFunc)
 }
 r.Route[method][path] = f
}

</code></pre>

<p>使用:</p>
<pre><code class="language-go">r := route.NewRouter()
r.HandleFunc(&quot;GET&quot;, &quot;/&quot;, func(w http.ResponseWriter, r *http.Request) {
 fmt.Fprint(w, &quot;Hello Get!&quot;)
})
r.HandleFunc(&quot;POST&quot;, &quot;/&quot;, func(w http.ResponseWriter, r *http.Request) {
 fmt.Fprint(w, &quot;hello POST!&quot;)
})
http.ListenAndServe(&quot;:8080&quot;, r)
</code></pre>

<p>这个例子只是依样画葫芦的简单功能实现。</p>

<p>一个完整的路由框架应该包含更复杂的匹配、错误检测等等功能,大家可以试着自己动手试试。</p>

<p>阅读源码和重复造轮子都是学习的方法。</p>
</description>
<author>TargetLiu</author>
<pubDate>Thu, 18 Aug 2016 15:36:23 +0000</pubDate>
</item>
<item>
<title>Go写的简单TCP聊天室示例</title>
<link>http://http://targetliu.com/2016/8/3/golang-tcp-chat-room.html</link>
<description><p>最近学习GO,写了一个聊天室来加深对 <code>net</code> 包和多线程的理解。</p>

<p>期间也遇到了一些小问题,比如 <code>scan</code> 如何读取一整行(包括空格)等。总的来说个人感觉 <code>Go</code> 还是一门很年轻的语言,资料、技巧上面还需要时间的积累。</p>

<p>不过,我已入坑&hellip;..</p>

<h2 id="github">GitHub</h2>

<p><a href="https://github.com/TargetLiu/golang-chat">GitHub地址</a></p>

<h2 id="介绍">介绍</h2>

<p>一个简单的基于GO语言的聊天室示例,参考了网上许多类似代码。代码有简单注释。</p>

<p>单执行文件,通过命令的方式选择启动服务端监听还是客户端连接。</p>

<p>功能:客户端昵称、服务端消息、服务端踢人命令。</p>

<!--more-->

<h2 id="使用方法">使用方法</h2>

<ul>
<li>克隆代码</li>
</ul>
<pre><code>git clone https://github.com/TargetLiu/chat
</code></pre>

<ul>
<li>编译安装</li>
</ul>
<pre><code>go build
go install
</code></pre>

<ul>
<li>运行</li>
</ul>
<pre><code>//服务端
chat server [:端口号]
//客户端
chat client [服务端IP地址:端口号] 
</code></pre>

<ul>
<li>服务端命令</li>
</ul>
<pre><code>//发送消息直接输入即可
//踢人
kick|[客户端昵称]
</code></pre>
</description>
<author>TargetLiu</author>
<pubDate>Wed, 03 Aug 2016 15:34:50 +0000</pubDate>
</item>
<item>
<title>VSCODE中godef无法跳转到定义的问题</title>
<link>http://http://targetliu.com/2016/8/2/vscode-can-not-go-to-def.html</link>
<description><blockquote>
<p>之前研究GOLANG时一直用LiteIDE,不得不说,LiteIDE的确不错,但是总感觉缺乏美感,是一款很中规中矩的编辑器。网上看到大家对VSCODE评价不错,尝试后发现的确不错,布局简洁、插件化、支持中文,通过VSCODE GO扩展能够很舒服的写GO的代码。</p>
</blockquote>

<h2 id="问题描述">问题描述</h2>

<p>不过在实际使用过程中发现 <code>net</code> 包无法正常跳转到定义,如下段代码 <code>ResolveTCPAddr</code>就无法正常跳转</p>
<pre><code class="language-go">package main

import (
 &quot;net&quot;
)

func main() {
 _, err := net.ResolveTCPAddr(&quot;tcp&quot;, &quot;:4040&quot;)
}
</code></pre>

<!--more-->

<p>由于VSCODE GO中跳转到定义使用的是godef,遂通过godef的debug模式查看问题原因:</p>
<pre><code>godef -debug -f main.go net.ResolveTCPAddr
</code></pre>

<p>运行结果如下:</p>
<pre><code>2016/08/02 01:17:30 exprType tuple:false pkg: *ast.SelectorExpr net.ListenTCP [
2016/08/02 01:17:30 exprType tuple:false pkg: *ast.Ident net [
2016/08/02 01:17:30 exprType tuple:false pkg: *ast.ImportSpec &quot;net&quot; [
2016/08/02 01:17:30 ] -&gt; 0x0, Type{package &quot;&quot; *ast.ImportSpec &quot;net&quot;}
2016/08/02 01:17:30 ] -&gt; 0xc820157860, Type{package &quot;&quot; *ast.ImportSpec &quot;net&quot;}
2016/08/02 01:17:30 member Type{package &quot;&quot; *ast.ImportSpec &quot;net&quot;} 'ListenTCP' {
2016/08/02 01:17:30 /usr/local/go/src/net/cgo_android.go:10:8: cannot find identifier for package &quot;C&quot;: cannot find package &quot;C&quot; in any of:
 /usr/local/go/src/vendor/C (vendor tree)
 /usr/local/go/src/C (from $GOROOT)
 /Users/targetliu/dev/govendor/src/C (from $GOPATH)
 /Users/targetliu/dev/golang/src/C
2016/08/02 01:17:30 } -&gt; &lt;nil&gt;
2016/08/02 01:17:30 ] -&gt; 0x0, Type{bad &quot;&quot; &lt;nil&gt; }
2016/08/02 01:17:30 exprType tuple:false pkg: *ast.SelectorExpr net.ListenTCP [
2016/08/02 01:17:30 exprType tuple:false pkg: *ast.Ident net [
2016/08/02 01:17:30 exprType tuple:false pkg: *ast.ImportSpec &quot;net&quot; [
2016/08/02 01:17:30 ] -&gt; 0x0, Type{package &quot;&quot; *ast.ImportSpec &quot;net&quot;}
2016/08/02 01:17:30 ] -&gt; 0xc820157860, Type{package &quot;&quot; *ast.ImportSpec &quot;net&quot;}
2016/08/02 01:17:30 member Type{package &quot;&quot; *ast.ImportSpec &quot;net&quot;} 'ListenTCP' {
2016/08/02 01:17:30 /usr/local/go/src/net/cgo_android.go:10:8: cannot find identifier for package &quot;C&quot;: cannot find package &quot;C&quot; in any of:
 /usr/local/go/src/vendor/C (vendor tree)
 /usr/local/go/src/C (from $GOROOT)
 /Users/targetliu/dev/govendor/src/C (from $GOPATH)
 /Users/targetliu/dev/golang/src/C
2016/08/02 01:17:30 } -&gt; &lt;nil&gt;
2016/08/02 01:17:30 ] -&gt; 0x0, Type{bad &quot;&quot; &lt;nil&gt; }
godef: no declaration found for net.ListenTCP
</code></pre>

<p>注意到这一句:</p>
<pre><code>cannot find identifier for package &quot;C&quot;: cannot find package &quot;C&quot; in any of:
</code></pre>

<p>原来是 <code>net</code> 包里 <code>import C</code> ,然而C并不是一个具体真实存在的包,所以godef无法进行分析,导致找不到定义。</p>

<p>godef的GitHub上作者也发现了同样的问题:<a href="https://github.com/rogpeppe/godef/issues/41">Issue:net.LookupIP fails #41</a></p>

<h2 id="解决方案">解决方案</h2>

<p>在godef的GitHub上看到有人提交了针对这个问题的解决方案:<a href="https://github.com/rogpeppe/godef/pull/44">[master] Special treatment for &ldquo;C&rdquo; package. #44</a></p>

<p>根据这个提交,可以尝试使用如下方法解决:</p>

<ul>
<li><p>找到并打开godef的 <code>go/parser/parser.go</code> 这个文件</p></li>

<li><p>在 <code>1970行</code> 左右添加(代码中+号部分,可以通过搜索定位):</p></li>
</ul>
<pre><code>if declIdent == nil {
 filename := p.fset.Position(path.Pos()).Filename
 name, err := p.pathToName(litToString(path), filepath.Dir(filename))
 + if litToString(path) == &quot;C&quot; {
 + name = &quot;C&quot;
 + }
 if name == &quot;&quot; {
 p.error(path.Pos(), fmt.Sprintf(&quot;cannot find identifier for package %q: %v&quot;, litToString(path), err))
 } else {
</code></pre>

<ul>
<li>重新编译godef</li>
</ul>

<p>如果遇到同样问题的同学不妨试一试以上方式,至少对于我来说,问题得到了解决。</p>

<p>也希望作者能尽快修复这个问题。</p>
</description>
<author>TargetLiu</author>
<pubDate>Tue, 02 Aug 2016 15:33:27 +0000</pubDate>
</item>
<item>
<title>Golang - iris中通过Plugin设置http超时时间(更新:作者已经提供相关设置)</title>
<link>http://http://targetliu.com/2016/7/29/golang-iris-http-timeout.html</link>
<description><blockquote>
<p><a href="https://github.com/kataras/iris">kataras/iris</a>是一款基于<a href="https://github.com/valyala/fasthttp">valyala/fasthttp</a>的web开发框架。注重效率,在<a href="https://github.com/smallnest/go-web-framework-benchmark">Benchmarks</a>对比中也表现出了速度的优势。</p>
</blockquote>

<h2 id="更新作者提供的相关设置选项">更新作者提供的相关设置选项</h2>

<p>根据留言和 <code>iris</code> 最新的<a href="https://github.com/kataras/iris/commit/3c50d26808461e8f28b66e99bb6511a875e8cfb5">提交(3c50d26)</a>,现在只需要使用ListenTo并且配置相关选项既可以设置超时时间(需要 <code>go get -u</code> 升级 <code>iris</code> 到最新版本):</p>
<pre><code class="language-go">iris.ListenTo(config.Server{WriteTimeout: 5* time.Second, ReadTimeout=
5*time.Second, ListeningAddr:&quot;:8080&quot;})
</code></pre>

<!--more-->

<h2 id="通过plugin设置超时时间">通过Plugin设置超时时间</h2>

<p>最近学习GOLANG时阅读到<a href="http://colobu.com/2016/07/01/the-complete-guide-to-golang-net-http-timeouts/">Go net/http 超时机制完全手册</a>,GO自带的 <code>net/http</code> 中可以使用</p>
<pre><code class="language-go">srv := &amp;http.Server{ 
 ReadTimeout: 5 * time.Second,
 WriteTimeout: 10 * time.Second,
}
</code></pre>

<p>来设置超时时间。</p>

<p>同样的,在 <code>valyala/fasthttp</code> 中也可以使用</p>
<pre><code class="language-go">srv := &amp;fasthttp.Server{ 
 ReadTimeout: 5 * time.Second,
 WriteTimeout: 10 * time.Second,
}
</code></pre>

<p>进行设置。</p>

<p>不过在iris中,虽然 <code>iris.Server</code> 是嵌入了 <code>fasthttp.Server</code> 不过iris在 <code>Listen</code> 之前是没有创建的,没法设置超时时间。
阅读iris文档和代码后发现,为给iris中的 <code>fasthttp.Server</code> 也设置超时时间,可以通过 <a href="https://kataras.gitbooks.io/iris/content/plugins.html">iris Plugins</a> 机制进行设置。</p>

<p>iris的Plugins类似于钩子、事物监听、中间件,可以在特定的情况下触发事件。我们只用在Listen之前设置超时即可达到目的了。</p>
<pre><code class="language-go">app.Plugins.PreListen(func(s *iris.Framework) {
 s.Servers.Main().ReadTimeout = 5 * time.Second
 s.Servers.Main().WriteTimeout = 5 * time.Second
})
</code></pre>

<p>同理,在其它无论是基于 <code>net/http</code> 还是 <code>valyala/fasthttp</code> 的框架中都可以看看Server具体是在什么时候创建以及有没有可能通过在Listen之前触发的事件来设置超时时间。</p>
</description>
<author>TargetLiu</author>
<pubDate>Fri, 29 Jul 2016 15:32:01 +0000</pubDate>
</item>
<item>
<title>Lumen中注册使用Artisan Console</title>
<link>http://http://targetliu.com/2016/7/22/lumen-artisan-console.html</link>
<description><p>Laravel中使用 <code>Artisan Console</code> 有详细的介绍。<a href="https://laravel.com/docs/5.2/artisan">Laravel Artisan Console 官方文档</a>,除了创建和注册 <code>Artisan Console</code> 与Lumen稍许不同外,其它内容基本都一样。</p>

<p>但是实际项目中,我更希望用Lumen的 <code>Artisan Console</code> 来执行一些简单的数据迁移、版本升级等操作。</p>

<p>下面就以<a href="https://laravel.com/docs/5.2/artisan">官方文档</a>中的示例来简单说一下Lumen中怎么注册使用 <code>Artisan Console</code></p>

<!--more-->

<h1 id="创建-artisan-console-命令文件">创建 <code>Artisan Console</code>命令文件</h1>

<p>Lumen创建命令只能通过手动创建文件。
在 <code>app/Console/Commands</code> 创建 <code>SendEmails</code> 用来编写命令:</p>
<pre><code class="language-php">&lt;?php

namespace App\Console\Commands;

use App\User;
use App\DripEmailer;
use Illuminate\Console\Command;

class SendEmails extends Command{
 /**
 * 控制台命令名称
 *
 * @var string
 */
 protected $signature = 'email:send {user}';

 /**
 * 控制台命令描述
 *
 * @var string
 */
 protected $description = 'Send drip e-mails to a user';

 /**
 * The drip e-mail service.
 *
 * @var DripEmailer
 */
 protected $drip;

 /**
 * 创建新的命令实例
 *
 * @param DripEmailer $drip
 * @return void
 */
 public function __construct(DripEmailer $drip)
 {
 parent::__construct();
 $this-&gt;drip = $drip;
 }

 /**
 * 执行控制台命令
 *
 * @return mixed
 */
 public function handle()
 {
 $this-&gt;drip-&gt;send(User::find($this-&gt;argument('user')));
 }
}
</code></pre>

<p>命令编写具体方法请查看Laravel的<a href="https://laravel.com/docs/5.2/artisan">官方文档</a> ,Lumen是一样的。</p>

<h1 id="注册命令">注册命令</h1>

<p>Lumen与Laravel最主要的差异就在这里,Lumen需要自己写服务提供者来注册命令。
在 <code>app/Providers</code> 里创建一个新的服务提供者:</p>
<pre><code class="language-php">&lt;?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use App\Console\Commands\SendEmails;

class SendEmailsServiceProvider extends ServiceProvider
{
 /**
 * Indicates if loading of the provider is deferred.
 *
 * @var bool
 */
 protected $defer = true;

 /**
 * Register the service provider.
 *
 * @return void
 */
 public function register()
 {
 $this-&gt;app-&gt;singleton('command.email.send', function () {
 return new SendEmails;
 });

 $this-&gt;commands('command.email.send');
 }

 /**
 * Get the services provided by the provider.
 *
 * @return array
 */
 public function provides()
 {
 return ['command.email.send'];
 }
}

</code></pre>

<p>在 <code>bootstrap/app.php</code> 中注册服务提供者:</p>
<pre><code class="language-php">$app-&gt;register(App\Providers\ SendEmailsServiceProvider::class);
</code></pre>

<p>现在就大功告成,只需要执行 <code>php artisan email:send XXX</code> 就可以使用自己编写的命令了。</p>
</description>
<author>TargetLiu</author>
<pubDate>Fri, 22 Jul 2016 06:52:01 +0000</pubDate>
</item>
<item>
<title>Lumen中使用速度更快的PhpRedis扩展(更新队列驱动)</title>
<link>http://http://targetliu.com/2016/7/19/lumen-phpredis.html</link>
<description><blockquote>
<p>Lumen的确是一款适合做API,速度很快的框架。但是在项目中使用Redis时发现Lumen默认使用的 <code>predis/predis</code> 会拖慢整体速度,特别是在高并发的情况下,所以寻思着使用 <code>PhpRedis</code> 代替,毕竟 <code>PhpRedis</code> 是C语言写的模块,性能上肯定优于 <code>predis</code></p>
</blockquote>

<p>文中例子已经整理成一个 <code>composer</code> 包,文末有简单介绍。</p>

<h2 id="phpredis-for-lumen-5">PhpRedis for Lumen 5.*</h2>

<ul>
<li>2016.7.29:v1.1.0发布,新增队列驱动,缓存驱动移动至 <code>TargetLiu\PHPRedis\Cache</code> ,使用老版本的需要修改 <code>bootstrap/app.php</code> 中缓存驱动加载位置。</li>
</ul>

<p>[TargetLiu/PHPRedis]</p>

<p><a href="https://github.com/TargetLiu/PHPRedis">https://github.com/TargetLiu/PHPRedis</a></p>

<p><a href="https://packagist.org/packages/targetliu/phpredis">https://packagist.org/packages/targetliu/phpredis</a></p>

<hr />

<h2 id="编译安装phpredis">编译安装PhpRedis</h2>

<p>由于 <code>PhpRedis</code> 是C语言写的模块,需要编译安装。安装方法网上一搜一大把,请根据自己的环境选择相应的方法安装即可。</p>

<p>两个可能用得上的链接:</p>

<p><a href="https://pecl.php.net/package/redis">PECL - PhpRedis</a></p>

<p><a href="https://github.com/phpredis/phpredis">GitHub - PhpRedis</a></p>

<!--more-->

<h2 id="lumen中使用phpredis">Lumen中使用PhpRedis</h2>

<p>很简单,只需要在 <code>bootstrap/app.php</code> 中添加下列代码将PhpRedis注入容器即可:</p>
<pre><code class="language-php">$app-&gt;singleton('phpredis', function(){
 $redis = new Redis;
 $redis-&gt;pconnect('127.0.0.1'); //建立连接
 $redis-&gt;select(1); //选择库
 $redis-&gt;auth('xxxx'); //认证
 return $redis;
});
unset($app-&gt;availableBindings['redis']);
</code></pre>

<p>绑定后即可通过 <code>app('phpredis')</code> 直接使用 <code>PhpRedis</code> 了,具体使用方法可以看相应的官方文档。</p>

<h2 id="lumen中为phpredis增加cache驱动">Lumen中为PhpRedis增加Cache驱动</h2>

<p>由于实际使用中更多的将Redis用于缓存,Lumen自带的Redis缓存驱动是基于 <code>predis/predis</code> 实现,我们现在新建一个驱动以支持 <code>Phpredis</code></p>

<p>新增Cache驱动详细方法可以查看 <a href="https://laravel.com/docs/5.2/cache#adding-custom-cache-drivers">官方文档</a>,这里指罗列一些关键的点。更多的内容也可以查看 <a href="https://github.com/TargetLiu/PHPRedis">TargetLiu/PHPRedis</a></p>

<p>我们首先创建一个 <code>ServiceProvider</code> :</p>
<pre><code class="language-php">&lt;?php

namespace App\Providers;

use Illuminate\Support\Facades\Cache;
use Illuminate\Support\ServiceProvider;
use TargetLiu\PHPRedis\PHPRedisStore;

class CacheServiceProvider extends ServiceProvider
{
 /**
 * Perform post-registration booting of services.
 *
 * @return void
 */
 public function boot()
 {
 Cache::extend('phpredis', function ($app) {
 return Cache::repository(new PHPRedisStore($app-&gt;make('phpredis'), $app-&gt;config['cache.prefix']));
 });
 }

 /**
 * Register bindings in the container.
 *
 * @return void
 */
 public function register()
 {
 //
 }
}
</code></pre>

<p>这样就建立一个名为 <code>phpreids</code> 的驱动。再创建一个基于 <code>Illuminate\Contracts\Cache\Store</code> 契约的缓存操作类用以操作 <code>PhpRedis</code></p>
<pre><code class="language-php">&lt;?php

namespace TargetLiu\PHPRedis;

use Illuminate\Contracts\Cache\Store;

class PHPRedisStore implements Store
{

 /**
 * The Redis database connection.
 *
 * @var \Illuminate\Redis\Database
 */
 protected $redis;

 /**
 * A string that should be prepended to keys.
 *
 * @var string
 */
 protected $prefix;

 /**
 * Create a new Redis store.
 *
 * @param \Illuminate\Redis\Database $redis
 * @param string $prefix
 * @return void
 */
 public function __construct($redis, $prefix = '')
 {
 $this-&gt;redis = $redis;
 $this-&gt;setPrefix($prefix);
 }

 /**
 * Retrieve an item from the cache by key.
 *
 * @param string|array $key
 * @return mixed
 */
 public function get($key)
 {
 if (!is_null($value = $this-&gt;connection()-&gt;get($this-&gt;prefix . $key))) {
 return is_numeric($value) ? $value : unserialize($value);
 }
 }

 /**
 * Retrieve multiple items from the cache by key.
 *
 * Items not found in the cache will have a null value.
 *
 * @param array $keys
 * @return array
 */
 public function many(array $keys)
 {
 $return = [];

 $prefixedKeys = array_map(function ($key) {
 return $this-&gt;prefix . $key;
 }, $keys);

 $values = $this-&gt;connection()-&gt;mGet($prefixedKeys);

 foreach ($values as $index =&gt; $value) {
 $return[$keys[$index]] = is_numeric($value) ? $value : unserialize($value);
 }

 return $return;
 }

 /**
 * Store an item in the cache for a given number of minutes.
 *
 * @param string $key
 * @param mixed $value
 * @param int $minutes
 * @return void
 */
 public function put($key, $value, $minutes)
 {
 $value = is_numeric($value) ? $value : serialize($value);

 $this-&gt;connection()-&gt;set($this-&gt;prefix . $key, $value, (int) max(1, $minutes * 60));
 }

 /**
 * Store multiple items in the cache for a given number of minutes.
 *
 * @param array $values
 * @param int $minutes
 * @return void
 */
 public function putMany(array $values, $minutes)
 {
 foreach ($values as $key =&gt; $value) {
 $this-&gt;put($key, $value, $minutes);
 }
 }

 /**
 * Increment the value of an item in the cache.
 *
 * @param string $key
 * @param mixed $value
 * @return int|bool
 */
 public function increment($key, $value = 1)
 {
 return $this-&gt;connection()-&gt;incrBy($this-&gt;prefix . $key, $value);
 }

 /**
 * Decrement the value of an item in the cache.
 *
 * @param string $key
 * @param mixed $value
 * @return int|bool
 */
 public function decrement($key, $value = 1)
 {
 return $this-&gt;connection()-&gt;decrBy($this-&gt;prefix . $key, $value);
 }

 /**
 * Store an item in the cache indefinitely.
 *
 * @param string $key
 * @param mixed $value
 * @return void
 */
 public function forever($key, $value)
 {
 $value = is_numeric($value) ? $value : serialize($value);

 $this-&gt;connection()-&gt;set($this-&gt;prefix . $key, $value);
 }

 /**
 * Remove an item from the cache.
 *
 * @param string $key
 * @return bool
 */
 public function forget($key)
 {
 return (bool) $this-&gt;connection()-&gt;delete($this-&gt;prefix . $key);
 }

 /**
 * Remove all items from the cache.
 *
 * @return void
 */
 public function flush()
 {
 $this-&gt;connection()-&gt;flushDb();
 }

 /**
 * Get the Redis connection instance.
 *
 * @return \Predis\ClientInterface
 */
 public function connection()
 {
 return $this-&gt;redis;
 }

 /**
 * Get the Redis database instance.
 *
 * @return \Illuminate\Redis\Database
 */
 public function getRedis()
 {
 return $this-&gt;redis;
 }

 /**
 * Get the cache key prefix.
 *
 * @return string
 */
 public function getPrefix()
 {
 return $this-&gt;prefix;
 }

 /**
 * Set the cache key prefix.
 *
 * @param string $prefix
 * @return void
 */
 public function setPrefix($prefix)
 {
 $this-&gt;prefix = !empty($prefix) ? $prefix . ':' : '';
 }
}
</code></pre>

<p>通过以上两个步骤基本上就完成了Cache驱动的创建,现在只需要在 <code>bootstrap/app.php</code> 中注入新建的Cache驱动然后配置 <code>.env</code> 中 <code>CACHE_DRIVER = phpredis</code> ,最后再在 <code>config/cache.php</code> 中加入相应的驱动代码即可</p>
<pre><code class="language-php">'phpredis' =&gt; [
 'driver' =&gt; 'phpredis'
],
</code></pre>

<p>Cache的使用请查看Lumen<a href="https://lumen.laravel.com/docs/5.2/cache">官方文档</a></p>

<h2 id="一个基于phpredis的lumen扩展包">一个基于PhpRedis的Lumen扩展包</h2>

<p>[TargetLiu/PHPRedis]</p>

<p><a href="https://github.com/TargetLiu/PHPRedis">https://github.com/TargetLiu/PHPRedis</a></p>

<p><a href="https://packagist.org/packages/targetliu/phpredis">https://packagist.org/packages/targetliu/phpredis</a></p>

<p>安装:<code>composer require targetliu/phpredis</code></p>

<p>安装及使用方法请看 <a href="https://github.com/TargetLiu/PHPRedis#phpredis-for-lumen-5">README</a></p>

<p>这个包只是我做的一个简单示例,引入了 <code>PhpRedis</code> 并做了最简单的缓存驱动。目前支持根据 <code>.env</code> 获取Redis配置、Cache的基本读写等。</p>

<p>Session和Queue可以继续使用Lumen自带的Redis驱动,两者互不影响。下一步如有需要可以继续完善这两部分的驱动。</p>

<p>这个包将根据自己工作需求以及大家的已经进一步完善。</p>

<p>欢迎大家提出意见共同完善。</p>
</description>
<author>TargetLiu</author>
<pubDate>Tue, 19 Jul 2016 16:37:47 +0000</pubDate>
</item>
<item>
<title>Lumen配置文件按需加载出现的坑</title>
<link>http://http://targetliu.com/2016/7/18/lumen-configure.html</link>
<description><h2 id="问题描述">问题描述</h2>

<p>公司一个高并发API需要从Laravel移植到Lumen,由于数据库配置信息是通过远程或者缓存读取后动态配置,所以在中间件时使用到了 <code>Config::set</code> 然而实际运行时发现数据库配置并没有更新。</p>

<blockquote>
<p>由于是从Laravel移植,因此保留了Facades的写法,Lumen中可以通过以下方法使用Facades:</p>

<ul>
<li>取消 <code>bootstarp/app.php</code> 中 <code>$app-&gt;withFacades();</code> 的注释</li>
<li><code>use Illuminate\Support\Facades\XXX</code></li>
</ul>
</blockquote>

<p>另一方面,系统使用 <code>Redis</code> 作为缓存,通过 <code>env</code> 配置 <code>Redis</code> ,配置信息存储在 <code>laravel/lumen-framework/config/database.php</code> 在没有使用数据库先使用缓存的情况下,报没有传配置项的错误。</p>

<!--more-->

<h2 id="问题分析">问题分析</h2>

<p>通过阅读源码 <code>laravel/lumen-framework/src/Application.php</code> 发现,Lumen中的服务都是按需绑定并加载。先来看看 <code>make()</code> 的代码:</p>
<pre><code class="language-php">public function make($abstract, array $parameters = [])
{
 $abstract = $this-&gt;getAlias($this-&gt;normalize($abstract));

 if (array_key_exists($abstract, $this-&gt;availableBindings) &amp;&amp;
 ! array_key_exists($this-&gt;availableBindings[$abstract], $this-&gt;ranServiceBinders)) {
 $this-&gt;{$method = $this-&gt;availableBindings[$abstract]}();

 $this-&gt;ranServiceBinders[$method] = true;
 }

 return parent::make($abstract, $parameters);
}
</code></pre>

<p>Lumen通过数组 <code>$availableBindings</code> 实现了基本服务的按需绑定并加载。在服务按需绑定并加载的时候,使用了类似组件的形式通过 <code>loadComponent()</code> 载入配置项并绑定服务。再来看看 <code>loadComponent()</code> 的代码:</p>
<pre><code class="language-php">public function loadComponent($config, $providers, $return = null)
{
 $this-&gt;configure($config);

 foreach ((array) $providers as $provider) {
 $this-&gt;register($provider);
 }

 return $this-&gt;make($return ?: $config);
}
</code></pre>

<p>如此就释然为什么在中间件以及使用 <code>DB</code> 之前想要动态配置数据库的信息时无法正确的写入配置项了。因为在这个时候 <code>DB</code> 的相关配置文件还没有被载入。你先写入了配置项当使用 <code>DB</code> 的时候会再次载入配置文件中的配置项覆盖动态写入的内容。</p>

<p>同理,使用 <code>Redis</code> 时,由于 <code>Redis</code> 相关配置是写在 <code>database.php</code> 里的,仅仅通过 <code>$app-&gt;register(Illuminate\Redis\RedisServiceProvider::class);</code> 注册是无法获取到配置项,如果在使用 <code>Redis</code> 时之前没有使用 <code>DB</code> 就会报没有传配置项的错误。</p>

<h2 id="解决方案">解决方案</h2>

<p>既然找到了问题所在,要解决起来也是很方便的。只要在修改、使用配置项之前先载入配置文件就可以解决这些问题。比如:</p>

<ul>
<li>使用 <code>app()-&gt;configure('database');</code> 载入所需要的配置文件</li>
<li>在注册绑定服务到服务容器的时候使用 <code>loadComponent</code> 进行注册绑定</li>
</ul>
</description>
<author>TargetLiu</author>
<pubDate>Mon, 18 Jul 2016 15:48:28 +0000</pubDate>
</item>
</channel>
</rss>