@ -0,0 +1,252 @@ |
|||
# 2.7 并发 |
|||
|
|||
有人把Go比作21世纪的C语言,第一是因为Go语言设计简单,第二,21世纪最重要的就是并行程序设计,而Go从语言层面就支持了并行。 |
|||
|
|||
## goroutine |
|||
|
|||
goroutine是Go并行设计的核心。goroutine说到底其实就是协程,但是它比线程更小,十几个goroutine可能体现在底层就是五六个线程,Go语言内部帮你实现了这些goroutine之间的内存共享。执行goroutine只需极少的栈内存(大概是4~5KB),当然会根据相应的数据伸缩。也正因为如此,可同时运行成千上万个并发任务。goroutine比thread更易用、更高效、更轻便。 |
|||
|
|||
goroutine是通过Go的runtime管理的一个线程管理器。goroutine通过`go`关键字实现了,其实就是一个普通的函数。 |
|||
```Go |
|||
|
|||
go hello(a, b, c) |
|||
``` |
|||
通过关键字go就启动了一个goroutine。我们来看一个例子 |
|||
```Go |
|||
|
|||
package main |
|||
|
|||
import ( |
|||
"fmt" |
|||
"runtime" |
|||
) |
|||
|
|||
func say(s string) { |
|||
for i := 0; i < 5; i++ { |
|||
runtime.Gosched() |
|||
fmt.Println(s) |
|||
} |
|||
} |
|||
|
|||
func main() { |
|||
go say("world") //开一个新的Goroutines执行 |
|||
say("hello") //当前Goroutines执行 |
|||
} |
|||
|
|||
// 以上程序执行后将输出: |
|||
// hello |
|||
// world |
|||
// hello |
|||
// world |
|||
// hello |
|||
// world |
|||
// hello |
|||
// world |
|||
// hello |
|||
``` |
|||
我们可以看到go关键字很方便的就实现了并发编程。 |
|||
上面的多个goroutine运行在同一个进程里面,共享内存数据,不过设计上我们要遵循:不要通过共享来通信,而要通过通信来共享。 |
|||
|
|||
> runtime.Gosched()表示让CPU把时间片让给别人,下次某个时候继续恢复执行该goroutine。 |
|||
|
|||
>默认情况下,在Go 1.5将标识并发系统线程个数的runtime.GOMAXPROCS的初始值由1改为了运行环境的CPU核数。 |
|||
|
|||
但在Go 1.5以前调度器仅使用单线程,也就是说只实现了并发。想要发挥多核处理器的并行,需要在我们的程序中显式调用 runtime.GOMAXPROCS(n) 告诉调度器同时使用多个线程。GOMAXPROCS 设置了同时运行逻辑代码的系统线程的最大数量,并返回之前的设置。如果n < 1,不会改变当前设置。 |
|||
|
|||
## channels |
|||
goroutine运行在相同的地址空间,因此访问共享内存必须做好同步。那么goroutine之间如何进行数据的通信呢,Go提供了一个很好的通信机制channel。channel可以与Unix shell 中的双向管道做类比:可以通过它发送或者接收值。这些值只能是特定的类型:channel类型。定义一个channel时,也需要定义发送到channel的值的类型。注意,必须使用make 创建channel: |
|||
```Go |
|||
|
|||
ci := make(chan int) |
|||
cs := make(chan string) |
|||
cf := make(chan interface{}) |
|||
``` |
|||
channel通过操作符`<-`来接收和发送数据 |
|||
```Go |
|||
|
|||
ch <- v // 发送v到channel ch. |
|||
v := <-ch // 从ch中接收数据,并赋值给v |
|||
``` |
|||
我们把这些应用到我们的例子中来: |
|||
```Go |
|||
|
|||
package main |
|||
|
|||
import "fmt" |
|||
|
|||
func sum(a []int, c chan int) { |
|||
total := 0 |
|||
for _, v := range a { |
|||
total += v |
|||
} |
|||
c <- total // send total to c |
|||
} |
|||
|
|||
func main() { |
|||
a := []int{7, 2, 8, -9, 4, 0} |
|||
|
|||
c := make(chan int) |
|||
go sum(a[:len(a)/2], c) |
|||
go sum(a[len(a)/2:], c) |
|||
x, y := <-c, <-c // receive from c |
|||
|
|||
fmt.Println(x, y, x + y) |
|||
} |
|||
``` |
|||
默认情况下,channel接收和发送数据都是阻塞的,除非另一端已经准备好,这样就使得Goroutines同步变的更加的简单,而不需要显式的lock。所谓阻塞,也就是如果读取(value := <-ch)它将会被阻塞,直到有数据接收。其次,任何发送(ch<-5)将会被阻塞,直到数据被读出。无缓冲channel是在多个goroutine之间同步很棒的工具。 |
|||
|
|||
## Buffered Channels |
|||
上面我们介绍了默认的非缓存类型的channel,不过Go也允许指定channel的缓冲大小,很简单,就是channel可以存储多少元素。ch:= make(chan bool, 4),创建了可以存储4个元素的bool 型channel。在这个channel 中,前4个元素可以无阻塞的写入。当写入第5个元素时,代码将会阻塞,直到其他goroutine从channel 中读取一些元素,腾出空间。 |
|||
```Go |
|||
|
|||
ch := make(chan type, value) |
|||
``` |
|||
当 value = 0 时,channel 是无缓冲阻塞读写的,当value > 0 时,channel 有缓冲、是非阻塞的,直到写满 value 个元素才阻塞写入。 |
|||
|
|||
我们看一下下面这个例子,你可以在自己本机测试一下,修改相应的value值 |
|||
```Go |
|||
|
|||
package main |
|||
|
|||
import "fmt" |
|||
|
|||
func main() { |
|||
c := make(chan int, 2)//修改2为1就报错,修改2为3可以正常运行 |
|||
c <- 1 |
|||
c <- 2 |
|||
fmt.Println(<-c) |
|||
fmt.Println(<-c) |
|||
} |
|||
//修改为1报如下的错误: |
|||
//fatal error: all goroutines are asleep - deadlock! |
|||
``` |
|||
## Range和Close |
|||
上面这个例子中,我们需要读取两次c,这样不是很方便,Go考虑到了这一点,所以也可以通过range,像操作slice或者map一样操作缓存类型的channel,请看下面的例子 |
|||
```Go |
|||
|
|||
package main |
|||
|
|||
import ( |
|||
"fmt" |
|||
) |
|||
|
|||
func fibonacci(n int, c chan int) { |
|||
x, y := 1, 1 |
|||
for i := 0; i < n; i++ { |
|||
c <- x |
|||
x, y = y, x + y |
|||
} |
|||
close(c) |
|||
} |
|||
|
|||
func main() { |
|||
c := make(chan int, 10) |
|||
go fibonacci(cap(c), c) |
|||
for i := range c { |
|||
fmt.Println(i) |
|||
} |
|||
} |
|||
``` |
|||
`for i := range c`能够不断的读取channel里面的数据,直到该channel被显式的关闭。上面代码我们看到可以显式的关闭channel,生产者通过内置函数`close`关闭channel。关闭channel之后就无法再发送任何数据了,在消费方可以通过语法`v, ok := <-ch`测试channel是否被关闭。如果ok返回false,那么说明channel已经没有任何数据并且已经被关闭。 |
|||
|
|||
>记住应该在生产者的地方关闭channel,而不是消费的地方去关闭它,这样容易引起panic |
|||
|
|||
>另外记住一点的就是channel不像文件之类的,不需要经常去关闭,只有当你确实没有任何发送数据了,或者你想显式的结束range循环之类的 |
|||
|
|||
## Select |
|||
我们上面介绍的都是只有一个channel的情况,那么如果存在多个channel的时候,我们该如何操作呢,Go里面提供了一个关键字`select`,通过`select`可以监听channel上的数据流动。 |
|||
|
|||
`select`默认是阻塞的,只有当监听的channel中有发送或接收可以进行时才会运行,当多个channel都准备好的时候,select是随机的选择一个执行的。 |
|||
```Go |
|||
|
|||
package main |
|||
|
|||
import "fmt" |
|||
|
|||
func fibonacci(c, quit chan int) { |
|||
x, y := 1, 1 |
|||
for { |
|||
select { |
|||
case c <- x: |
|||
x, y = y, x + y |
|||
case <-quit: |
|||
fmt.Println("quit") |
|||
return |
|||
} |
|||
} |
|||
} |
|||
|
|||
func main() { |
|||
c := make(chan int) |
|||
quit := make(chan int) |
|||
go func() { |
|||
for i := 0; i < 10; i++ { |
|||
fmt.Println(<-c) |
|||
} |
|||
quit <- 0 |
|||
}() |
|||
fibonacci(c, quit) |
|||
} |
|||
``` |
|||
在`select`里面还有default语法,`select`其实就是类似switch的功能,default就是当监听的channel都没有准备好的时候,默认执行的(select不再阻塞等待channel)。 |
|||
```Go |
|||
|
|||
select { |
|||
case i := <-c: |
|||
// use i |
|||
default: |
|||
// 当c阻塞的时候执行这里 |
|||
} |
|||
``` |
|||
## 超时 |
|||
有时候会出现goroutine阻塞的情况,那么我们如何避免整个程序进入阻塞的情况呢?我们可以利用select来设置超时,通过如下的方式实现: |
|||
```Go |
|||
|
|||
func main() { |
|||
c := make(chan int) |
|||
o := make(chan bool) |
|||
go func() { |
|||
for { |
|||
select { |
|||
case v := <- c: |
|||
println(v) |
|||
case <- time.After(5 * time.Second): |
|||
println("timeout") |
|||
o <- true |
|||
break |
|||
} |
|||
} |
|||
}() |
|||
<- o |
|||
} |
|||
``` |
|||
|
|||
## runtime goroutine |
|||
runtime包中有几个处理goroutine的函数: |
|||
|
|||
- Goexit |
|||
|
|||
退出当前执行的goroutine,但是defer函数还会继续调用 |
|||
|
|||
- Gosched |
|||
|
|||
让出当前goroutine的执行权限,调度器安排其他等待的任务运行,并在下次某个时候从该位置恢复执行。 |
|||
|
|||
- NumCPU |
|||
|
|||
返回 CPU 核数量 |
|||
|
|||
- NumGoroutine |
|||
|
|||
返回正在执行和排队的任务总数 |
|||
|
|||
- GOMAXPROCS |
|||
|
|||
用来设置可以并行计算的CPU核数的最大值,并返回之前的值。 |
|||
|
|||
|
|||
|
|||
## links |
|||
* [目录](<preface.md>) |
|||
* 上一章: [interface](<02.6.md>) |
|||
* 下一节: [总结](<02.8.md>) |
@ -0,0 +1,128 @@ |
|||
# 8.3 REST |
|||
RESTful,是目前最为流行的一种互联网软件架构。因为它结构清晰、符合标准、易于理解、扩展方便,所以正得到越来越多网站的采用。本小节我们将来学习它到底是一种什么样的架构?以及在Go里面如何来实现它。 |
|||
## 什么是REST |
|||
REST(REpresentational State Transfer)这个概念,首次出现是在 2000年Roy Thomas Fielding(他是HTTP规范的主要编写者之一)的博士论文中,它指的是一组架构约束条件和原则。满足这些约束条件和原则的应用程序或设计就是RESTful的。 |
|||
|
|||
要理解什么是REST,我们需要理解下面几个概念: |
|||
|
|||
- 资源(Resources) |
|||
REST是"表现层状态转化",其实它省略了主语。"表现层"其实指的是"资源"的"表现层"。 |
|||
|
|||
那么什么是资源呢?就是我们平常上网访问的一张图片、一个文档、一个视频等。这些资源我们通过URI来定位,也就是一个URI表示一个资源。 |
|||
|
|||
- 表现层(Representation) |
|||
|
|||
资源是做一个具体的实体信息,他可以有多种的展现方式。而把实体展现出来就是表现层,例如一个txt文本信息,他可以输出成html、json、xml等格式,一个图片他可以jpg、png等方式展现,这个就是表现层的意思。 |
|||
|
|||
URI确定一个资源,但是如何确定它的具体表现形式呢?应该在HTTP请求的头信息中用Accept和Content-Type字段指定,这两个字段才是对"表现层"的描述。 |
|||
|
|||
- 状态转化(State Transfer) |
|||
|
|||
访问一个网站,就代表了客户端和服务器的一个互动过程。在这个过程中,肯定涉及到数据和状态的变化。而HTTP协议是无状态的,那么这些状态肯定保存在服务器端,所以如果客户端想要通知服务器端改变数据和状态的变化,肯定要通过某种方式来通知它。 |
|||
|
|||
客户端能通知服务器端的手段,只能是HTTP协议。具体来说,就是HTTP协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。它们分别对应四种基本操作:GET用来获取资源,POST用来新建资源(也可以用于更新资源),PUT用来更新资源,DELETE用来删除资源。 |
|||
|
|||
综合上面的解释,我们总结一下什么是RESTful架构: |
|||
|
|||
- (1)每一个URI代表一种资源; |
|||
- (2)客户端和服务器之间,传递这种资源的某种表现层; |
|||
- (3)客户端通过四个HTTP动词,对服务器端资源进行操作,实现"表现层状态转化"。 |
|||
|
|||
|
|||
Web应用要满足REST最重要的原则是:客户端和服务器之间的交互在请求之间是无状态的,即从客户端到服务器的每个请求都必须包含理解请求所必需的信息。如果服务器在请求之间的任何时间点重启,客户端不会得到通知。此外此请求可以由任何可用服务器回答,这十分适合云计算之类的环境。因为是无状态的,所以客户端可以缓存数据以改进性能。 |
|||
|
|||
另一个重要的REST原则是系统分层,这表示组件无法了解除了与它直接交互的层次以外的组件。通过将系统知识限制在单个层,可以限制整个系统的复杂性,从而促进了底层的独立性。 |
|||
|
|||
下图即是REST的架构图: |
|||
|
|||
![](images/8.3.rest2.png?raw=true) |
|||
|
|||
图8.5 REST架构图 |
|||
|
|||
当REST架构的约束条件作为一个整体应用时,将生成一个可以扩展到大量客户端的应用程序。它还降低了客户端和服务器之间的交互延迟。统一界面简化了整个系统架构,改进了子系统之间交互的可见性。REST简化了客户端和服务器的实现,而且对于使用REST开发的应用程序更加容易扩展。 |
|||
|
|||
下图展示了REST的扩展性: |
|||
|
|||
![](images/8.3.rest.png?raw=true) |
|||
|
|||
图8.6 REST的扩展性 |
|||
|
|||
## RESTful的实现 |
|||
Go没有为REST提供直接支持,但是因为RESTful是基于HTTP协议实现的,所以我们可以利用`net/http`包来自己实现,当然需要针对REST做一些改造,REST是根据不同的method来处理相应的资源,目前已经存在的很多自称是REST的应用,其实并没有真正的实现REST,我暂且把这些应用根据实现的method分成几个级别,请看下图: |
|||
|
|||
![](images/8.3.rest3.png?raw=true) |
|||
|
|||
图8.7 REST的level分级 |
|||
|
|||
上图展示了我们目前实现REST的三个level,我们在应用开发的时候也不一定全部按照RESTful的规则全部实现他的方式,因为有些时候完全按照RESTful的方式未必是可行的,RESTful服务充分利用每一个HTTP方法,包括`DELETE`和`PUT`。可有时,HTTP客户端只能发出`GET`和`POST`请求: |
|||
|
|||
- HTML标准只能通过链接和表单支持`GET`和`POST`。在没有Ajax支持的网页浏览器中不能发出`PUT`或`DELETE`命令 |
|||
|
|||
- 有些防火墙会挡住HTTP `PUT`和`DELETE`请求,要绕过这个限制,客户端需要把实际的`PUT`和`DELETE`请求通过 POST 请求穿透过来。RESTful 服务则要负责在收到的 POST 请求中找到原始的 HTTP 方法并还原。 |
|||
|
|||
我们现在可以通过`POST`里面增加隐藏字段`_method`这种方式可以来模拟`PUT`、`DELETE`等方式,但是服务器端需要做转换。我现在的项目里面就按照这种方式来做的REST接口。当然Go语言里面完全按照RESTful来实现是很容易的,我们通过下面的例子来说明如何实现RESTful的应用设计。 |
|||
|
|||
```Go |
|||
|
|||
package main |
|||
|
|||
import ( |
|||
"fmt" |
|||
"log" |
|||
"net/http" |
|||
|
|||
"github.com/julienschmidt/httprouter" |
|||
) |
|||
|
|||
func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { |
|||
fmt.Fprint(w, "Welcome!\n") |
|||
} |
|||
|
|||
func Hello(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { |
|||
fmt.Fprintf(w, "hello, %s!\n", ps.ByName("name")) |
|||
} |
|||
|
|||
func getuser(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { |
|||
uid := ps.ByName("uid") |
|||
fmt.Fprintf(w, "you are get user %s", uid) |
|||
} |
|||
|
|||
func modifyuser(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { |
|||
uid := ps.ByName("uid") |
|||
fmt.Fprintf(w, "you are modify user %s", uid) |
|||
} |
|||
|
|||
func deleteuser(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { |
|||
uid := ps.ByName("uid") |
|||
fmt.Fprintf(w, "you are delete user %s", uid) |
|||
} |
|||
|
|||
func adduser(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { |
|||
// uid := r.FormValue("uid") |
|||
uid := ps.ByName("uid") |
|||
fmt.Fprintf(w, "you are add user %s", uid) |
|||
} |
|||
|
|||
func main() { |
|||
router := httprouter.New() |
|||
router.GET("/", Index) |
|||
router.GET("/hello/:name", Hello) |
|||
|
|||
router.GET("/user/:uid", getuser) |
|||
router.POST("/adduser/:uid", adduser) |
|||
router.DELETE("/deluser/:uid", deleteuser) |
|||
router.PUT("/moduser/:uid", modifyuser) |
|||
|
|||
log.Fatal(http.ListenAndServe(":8080", router)) |
|||
} |
|||
|
|||
``` |
|||
上面的代码演示了如何编写一个REST的应用,我们访问的资源是用户,我们通过不同的method来访问不同的函数,这里使用了第三方库`github.com/julienschmidt/httprouter`,在前面章节我们介绍过如何实现自定义的路由器,这个库实现了自定义路由和方便的路由规则映射,通过它,我们可以很方便的实现REST的架构。通过上面的代码可知,REST就是根据不同的method访问同一个资源的时候实现不同的逻辑处理。 |
|||
|
|||
## 总结 |
|||
REST是一种架构风格,汲取了WWW的成功经验:无状态,以资源为中心,充分利用HTTP协议和URI协议,提供统一的接口定义,使得它作为一种设计Web服务的方法而变得流行。在某种意义上,通过强调URI和HTTP等早期Internet标准,REST是对大型应用程序服务器时代之前的Web方式的回归。目前Go对于REST的支持还是很简单的,通过实现自定义的路由规则,我们就可以为不同的method实现不同的handle,这样就实现了REST的架构。 |
|||
|
|||
## links |
|||
* [目录](<preface.md>) |
|||
* 上一节: [WebSocket](<08.2.md>) |
|||
* 下一节: [RPC](<08.4.md>) |
@ -0,0 +1,410 @@ |
|||
# 8.4 RPC |
|||
前面几个小节我们介绍了如何基于Socket和HTTP来编写网络应用,通过学习我们了解了Socket和HTTP采用的是类似"信息交换"模式,即客户端发送一条信息到服务端,然后(一般来说)服务器端都会返回一定的信息以表示响应。客户端和服务端之间约定了交互信息的格式,以便双方都能够解析交互所产生的信息。但是很多独立的应用并没有采用这种模式,而是采用类似常规的函数调用的方式来完成想要的功能。 |
|||
|
|||
RPC就是想实现函数调用模式的网络化。客户端就像调用本地函数一样,然后客户端把这些参数打包之后通过网络传递到服务端,服务端解包到处理过程中执行,然后执行的结果反馈给客户端。 |
|||
|
|||
RPC(Remote Procedure Call Protocol)——远程过程调用协议,是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。它假定某些传输协议的存在,如TCP或UDP,以便为通信程序之间携带信息数据。通过它可以使函数调用模式网络化。在OSI网络通信模型中,RPC跨越了传输层和应用层。RPC使得开发包括网络分布式多程序在内的应用程序更加容易。 |
|||
|
|||
## RPC工作原理 |
|||
|
|||
![](images/8.4.rpc.png?raw=true) |
|||
|
|||
图8.8 RPC工作流程图 |
|||
|
|||
运行时,一次客户机对服务器的RPC调用,其内部操作大致有如下十步: |
|||
|
|||
- 1.调用客户端句柄;执行传送参数 |
|||
- 2.调用本地系统内核发送网络消息 |
|||
- 3.消息传送到远程主机 |
|||
- 4.服务器句柄得到消息并取得参数 |
|||
- 5.执行远程过程 |
|||
- 6.执行的过程将结果返回服务器句柄 |
|||
- 7.服务器句柄返回结果,调用远程系统内核 |
|||
- 8.消息传回本地主机 |
|||
- 9.客户句柄由内核接收消息 |
|||
- 10.客户接收句柄返回的数据 |
|||
|
|||
## Go RPC |
|||
Go标准包中已经提供了对RPC的支持,而且支持三个级别的RPC:TCP、HTTP、JSONRPC。但Go的RPC包是独一无二的RPC,它和传统的RPC系统不同,它只支持Go开发的服务器与客户端之间的交互,因为在内部,它们采用了Gob来编码。 |
|||
|
|||
Go RPC的函数只有符合下面的条件才能被远程访问,不然会被忽略,详细的要求如下: |
|||
|
|||
- 函数必须是导出的(首字母大写) |
|||
- 必须有两个导出类型的参数, |
|||
- 第一个参数是接收的参数,第二个参数是返回给客户端的参数,第二个参数必须是指针类型的 |
|||
- 函数还要有一个返回值error |
|||
|
|||
举个例子,正确的RPC函数格式如下: |
|||
|
|||
func (t *T) MethodName(argType T1, replyType *T2) error |
|||
|
|||
T、T1和T2类型必须能被`encoding/gob`包编解码。 |
|||
|
|||
任何的RPC都需要通过网络来传递数据,Go RPC可以利用HTTP和TCP来传递数据,利用HTTP的好处是可以直接复用`net/http`里面的一些函数。详细的例子请看下面的实现 |
|||
|
|||
### HTTP RPC |
|||
http的服务端代码实现如下: |
|||
|
|||
```Go |
|||
|
|||
package main |
|||
|
|||
import ( |
|||
"errors" |
|||
"fmt" |
|||
"net/http" |
|||
"net/rpc" |
|||
) |
|||
|
|||
type Args struct { |
|||
A, B int |
|||
} |
|||
|
|||
type Quotient struct { |
|||
Quo, Rem int |
|||
} |
|||
|
|||
type Arith int |
|||
|
|||
func (t *Arith) Multiply(args *Args, reply *int) error { |
|||
*reply = args.A * args.B |
|||
return nil |
|||
} |
|||
|
|||
func (t *Arith) Divide(args *Args, quo *Quotient) error { |
|||
if args.B == 0 { |
|||
return errors.New("divide by zero") |
|||
} |
|||
quo.Quo = args.A / args.B |
|||
quo.Rem = args.A % args.B |
|||
return nil |
|||
} |
|||
|
|||
func main() { |
|||
|
|||
arith := new(Arith) |
|||
rpc.Register(arith) |
|||
rpc.HandleHTTP() |
|||
|
|||
err := http.ListenAndServe(":1234", nil) |
|||
if err != nil { |
|||
fmt.Println(err.Error()) |
|||
} |
|||
} |
|||
|
|||
``` |
|||
通过上面的例子可以看到,我们注册了一个Arith的RPC服务,然后通过`rpc.HandleHTTP`函数把该服务注册到了HTTP协议上,然后我们就可以利用http的方式来传递数据了。 |
|||
|
|||
请看下面的客户端代码: |
|||
|
|||
```Go |
|||
|
|||
package main |
|||
|
|||
import ( |
|||
"fmt" |
|||
"log" |
|||
"net/rpc" |
|||
"os" |
|||
) |
|||
|
|||
type Args struct { |
|||
A, B int |
|||
} |
|||
|
|||
type Quotient struct { |
|||
Quo, Rem int |
|||
} |
|||
|
|||
func main() { |
|||
if len(os.Args) != 2 { |
|||
fmt.Println("Usage: ", os.Args[0], "server") |
|||
os.Exit(1) |
|||
} |
|||
serverAddress := os.Args[1] |
|||
|
|||
client, err := rpc.DialHTTP("tcp", serverAddress+":1234") |
|||
if err != nil { |
|||
log.Fatal("dialing:", err) |
|||
} |
|||
// Synchronous call |
|||
args := Args{17, 8} |
|||
var reply int |
|||
err = client.Call("Arith.Multiply", args, &reply) |
|||
if err != nil { |
|||
log.Fatal("arith error:", err) |
|||
} |
|||
fmt.Printf("Arith: %d*%d=%d\n", args.A, args.B, reply) |
|||
|
|||
var quot Quotient |
|||
err = client.Call("Arith.Divide", args, ") |
|||
if err != nil { |
|||
log.Fatal("arith error:", err) |
|||
} |
|||
fmt.Printf("Arith: %d/%d=%d remainder %d\n", args.A, args.B, quot.Quo, quot.Rem) |
|||
|
|||
} |
|||
|
|||
``` |
|||
我们把上面的服务端和客户端的代码分别编译,然后先把服务端开启,然后开启客户端,输入代码,就会输出如下信息: |
|||
```Go |
|||
|
|||
$ ./http_c localhost |
|||
Arith: 17*8=136 |
|||
Arith: 17/8=2 remainder 1 |
|||
|
|||
``` |
|||
通过上面的调用可以看到参数和返回值是我们定义的struct类型,在服务端我们把它们当做调用函数的参数的类型,在客户端作为`client.Call`的第2,3两个参数的类型。客户端最重要的就是这个Call函数,它有3个参数,第1个要调用的函数的名字,第2个是要传递的参数,第3个要返回的参数(注意是指针类型),通过上面的代码例子我们可以发现,使用Go的RPC实现相当的简单,方便。 |
|||
### TCP RPC |
|||
上面我们实现了基于HTTP协议的RPC,接下来我们要实现基于TCP协议的RPC,服务端的实现代码如下所示: |
|||
```Go |
|||
|
|||
package main |
|||
|
|||
import ( |
|||
"errors" |
|||
"fmt" |
|||
"net" |
|||
"net/rpc" |
|||
"os" |
|||
) |
|||
|
|||
type Args struct { |
|||
A, B int |
|||
} |
|||
|
|||
type Quotient struct { |
|||
Quo, Rem int |
|||
} |
|||
|
|||
type Arith int |
|||
|
|||
func (t *Arith) Multiply(args *Args, reply *int) error { |
|||
*reply = args.A * args.B |
|||
return nil |
|||
} |
|||
|
|||
func (t *Arith) Divide(args *Args, quo *Quotient) error { |
|||
if args.B == 0 { |
|||
return errors.New("divide by zero") |
|||
} |
|||
quo.Quo = args.A / args.B |
|||
quo.Rem = args.A % args.B |
|||
return nil |
|||
} |
|||
|
|||
func main() { |
|||
|
|||
arith := new(Arith) |
|||
rpc.Register(arith) |
|||
|
|||
tcpAddr, err := net.ResolveTCPAddr("tcp", ":1234") |
|||
checkError(err) |
|||
|
|||
listener, err := net.ListenTCP("tcp", tcpAddr) |
|||
checkError(err) |
|||
|
|||
for { |
|||
conn, err := listener.Accept() |
|||
if err != nil { |
|||
continue |
|||
} |
|||
rpc.ServeConn(conn) |
|||
} |
|||
|
|||
} |
|||
|
|||
func checkError(err error) { |
|||
if err != nil { |
|||
fmt.Println("Fatal error ", err.Error()) |
|||
os.Exit(1) |
|||
} |
|||
} |
|||
|
|||
``` |
|||
上面这个代码和http的服务器相比,不同在于:在此处我们采用了TCP协议,然后需要自己控制连接,当有客户端连接上来后,我们需要把这个连接交给rpc来处理。 |
|||
|
|||
如果你留心了,你会发现这它是一个阻塞型的单用户的程序,如果想要实现多并发,那么可以使用goroutine来实现,我们前面在socket小节的时候已经介绍过如何处理goroutine。 |
|||
下面展现了TCP实现的RPC客户端: |
|||
|
|||
```Go |
|||
|
|||
package main |
|||
|
|||
import ( |
|||
"fmt" |
|||
"log" |
|||
"net/rpc" |
|||
"os" |
|||
) |
|||
|
|||
type Args struct { |
|||
A, B int |
|||
} |
|||
|
|||
type Quotient struct { |
|||
Quo, Rem int |
|||
} |
|||
|
|||
func main() { |
|||
if len(os.Args) != 2 { |
|||
fmt.Println("Usage: ", os.Args[0], "server:port") |
|||
os.Exit(1) |
|||
} |
|||
service := os.Args[1] |
|||
|
|||
client, err := rpc.Dial("tcp", service) |
|||
if err != nil { |
|||
log.Fatal("dialing:", err) |
|||
} |
|||
// Synchronous call |
|||
args := Args{17, 8} |
|||
var reply int |
|||
err = client.Call("Arith.Multiply", args, &reply) |
|||
if err != nil { |
|||
log.Fatal("arith error:", err) |
|||
} |
|||
fmt.Printf("Arith: %d*%d=%d\n", args.A, args.B, reply) |
|||
|
|||
var quot Quotient |
|||
err = client.Call("Arith.Divide", args, ") |
|||
if err != nil { |
|||
log.Fatal("arith error:", err) |
|||
} |
|||
fmt.Printf("Arith: %d/%d=%d remainder %d\n", args.A, args.B, quot.Quo, quot.Rem) |
|||
|
|||
} |
|||
|
|||
``` |
|||
这个客户端代码和http的客户端代码对比,唯一的区别一个是DialHTTP,一个是Dial(tcp),其他处理一模一样。 |
|||
|
|||
### JSON RPC |
|||
JSON RPC是数据编码采用了JSON,而不是gob编码,其他和上面介绍的RPC概念一模一样,下面我们来演示一下,如何使用Go提供的json-rpc标准包,请看服务端代码的实现: |
|||
|
|||
```Go |
|||
|
|||
package main |
|||
|
|||
import ( |
|||
"errors" |
|||
"fmt" |
|||
"net" |
|||
"net/rpc" |
|||
"net/rpc/jsonrpc" |
|||
"os" |
|||
) |
|||
|
|||
type Args struct { |
|||
A, B int |
|||
} |
|||
|
|||
type Quotient struct { |
|||
Quo, Rem int |
|||
} |
|||
|
|||
type Arith int |
|||
|
|||
func (t *Arith) Multiply(args *Args, reply *int) error { |
|||
*reply = args.A * args.B |
|||
return nil |
|||
} |
|||
|
|||
func (t *Arith) Divide(args *Args, quo *Quotient) error { |
|||
if args.B == 0 { |
|||
return errors.New("divide by zero") |
|||
} |
|||
quo.Quo = args.A / args.B |
|||
quo.Rem = args.A % args.B |
|||
return nil |
|||
} |
|||
|
|||
func main() { |
|||
|
|||
arith := new(Arith) |
|||
rpc.Register(arith) |
|||
|
|||
tcpAddr, err := net.ResolveTCPAddr("tcp", ":1234") |
|||
checkError(err) |
|||
|
|||
listener, err := net.ListenTCP("tcp", tcpAddr) |
|||
checkError(err) |
|||
|
|||
for { |
|||
conn, err := listener.Accept() |
|||
if err != nil { |
|||
continue |
|||
} |
|||
jsonrpc.ServeConn(conn) |
|||
} |
|||
|
|||
} |
|||
|
|||
func checkError(err error) { |
|||
if err != nil { |
|||
fmt.Println("Fatal error ", err.Error()) |
|||
os.Exit(1) |
|||
} |
|||
} |
|||
|
|||
``` |
|||
通过示例我们可以看出 json-rpc是基于TCP协议实现的,目前它还不支持HTTP方式。 |
|||
|
|||
请看客户端的实现代码: |
|||
```Go |
|||
|
|||
package main |
|||
|
|||
import ( |
|||
"fmt" |
|||
"log" |
|||
"net/rpc/jsonrpc" |
|||
"os" |
|||
) |
|||
|
|||
type Args struct { |
|||
A, B int |
|||
} |
|||
|
|||
type Quotient struct { |
|||
Quo, Rem int |
|||
} |
|||
|
|||
func main() { |
|||
if len(os.Args) != 2 { |
|||
fmt.Println("Usage: ", os.Args[0], "server:port") |
|||
log.Fatal(1) |
|||
} |
|||
service := os.Args[1] |
|||
|
|||
client, err := jsonrpc.Dial("tcp", service) |
|||
if err != nil { |
|||
log.Fatal("dialing:", err) |
|||
} |
|||
// Synchronous call |
|||
args := Args{17, 8} |
|||
var reply int |
|||
err = client.Call("Arith.Multiply", args, &reply) |
|||
if err != nil { |
|||
log.Fatal("arith error:", err) |
|||
} |
|||
fmt.Printf("Arith: %d*%d=%d\n", args.A, args.B, reply) |
|||
|
|||
var quot Quotient |
|||
err = client.Call("Arith.Divide", args, ") |
|||
if err != nil { |
|||
log.Fatal("arith error:", err) |
|||
} |
|||
fmt.Printf("Arith: %d/%d=%d remainder %d\n", args.A, args.B, quot.Quo, quot.Rem) |
|||
|
|||
} |
|||
|
|||
``` |
|||
## 总结 |
|||
Go已经提供了对RPC的良好支持,通过上面HTTP、TCP、JSON RPC的实现,我们就可以很方便的开发很多分布式的Web应用,我想作为读者的你已经领会到这一点。但遗憾的是目前Go尚未提供对SOAP RPC的支持,欣慰的是现在已经有第三方的开源实现了。 |
|||
|
|||
|
|||
|
|||
## links |
|||
* [目录](<preface.md>) |
|||
* 上一节: [REST](<08.3.md>) |
|||
* 下一节: [小结](<08.5.md>) |
@ -0,0 +1,6 @@ |
|||
# 8.5 小结 |
|||
这一章我们介绍了目前流行的几种主要的网络应用开发方式,第一小节介绍了网络编程中的基础:Socket编程,因为现在网络正在朝云的方向快速进化,作为这一技术演进的基石的的socket知识,作为开发者的你,是必须要掌握的。第二小节介绍了正愈发流行的HTML5中一个重要的特性WebSocket,通过它,服务器可以实现主动的push消息,以简化以前ajax轮询的模式。第三小节介绍了REST编写模式,这种模式特别适合来开发网络应用API,目前移动应用的快速发展,我觉得将来会是一个潮流。第四小节介绍了Go实现的RPC相关知识,对于上面四种开发方式,Go都已经提供了良好的支持,net包及其子包,是所有涉及到网络编程的工具的所在地。如果你想更加深入的了解相关实现细节,可以尝试阅读这个包下面的源码。 |
|||
## links |
|||
* [目录](<preface.md>) |
|||
* 上一节: [RPC](<08.4.md>) |
|||
* 下一章: [安全与加密](<09.0.md>) |
@ -0,0 +1,78 @@ |
|||
# 9.2 确保输入过滤 |
|||
过滤用户数据是Web应用安全的基础。它是验证数据合法性的过程。通过对所有的输入数据进行过滤,可以避免恶意数据在程序中被误信或误用。大多数Web应用的漏洞都是因为没有对用户输入的数据进行恰当过滤所引起的。 |
|||
|
|||
我们介绍的过滤数据分成三个步骤: |
|||
|
|||
- 1、识别数据,搞清楚需要过滤的数据来自于哪里 |
|||
- 2、过滤数据,弄明白我们需要什么样的数据 |
|||
- 3、区分已过滤及被污染数据,如果存在攻击数据那么保证过滤之后可以让我们使用更安全的数据 |
|||
|
|||
## 识别数据 |
|||
“识别数据”作为第一步是因为在你不知道“数据是什么,它来自于哪里”的前提下,你也就不能正确地过滤它。这里的数据是指所有源自非代码内部提供的数据。例如:所有来自客户端的数据,但客户端并不是唯一的外部数据源,数据库和第三方提供的接口数据等也可以是外部数据源。 |
|||
|
|||
由用户输入的数据我们通过Go非常容易识别,Go通过`r.ParseForm`之后,把用户POST和GET的数据全部放在了`r.Form`里面。其它的输入要难识别得多,例如,`r.Header`中的很多元素是由客户端所操纵的。常常很难确认其中的哪些元素组成了输入,所以,最好的方法是把里面所有的数据都看成是用户输入。(例如`r.Header.Get("Accept-Charset")`这样的也看做是用户输入,虽然这些大多数是浏览器操纵的) |
|||
|
|||
## 过滤数据 |
|||
在知道数据来源之后,就可以过滤它了。过滤是一个有点正式的术语,它在平时表述中有很多同义词,如验证、清洁及净化。尽管这些术语表面意义不同,但它们都是指的同一个处理:防止非法数据进入你的应用。 |
|||
|
|||
过滤数据有很多种方法,其中有一些安全性较差。最好的方法是把过滤看成是一个检查的过程,在你使用数据之前都检查一下看它们是否是符合合法数据的要求。而且不要试图好心地去纠正非法数据,而要让用户按你制定的规则去输入数据。历史证明了试图纠正非法数据往往会导致安全漏洞。这里举个例子:“最近建设银行系统升级之后,如果密码后面两位是0,只要输入前面四位就能登录系统”,这是一个非常严重的漏洞。 |
|||
|
|||
过滤数据主要采用如下一些库来操作: |
|||
|
|||
- strconv包下面的字符串转化相关函数,因为从Request中的`r.Form`返回的是字符串,而有些时候我们需要将之转化成整/浮点数,`Atoi`、`ParseBool`、`ParseFloat`、`ParseInt`等函数就可以派上用场了。 |
|||
- string包下面的一些过滤函数`Trim`、`ToLower`、`ToTitle`等函数,能够帮助我们按照指定的格式获取信息。 |
|||
- regexp包用来处理一些复杂的需求,例如判定输入是否是Email、生日之类。 |
|||
|
|||
过滤数据除了检查验证之外,在特殊时候,还可以采用白名单。即假定你正在检查的数据都是非法的,除非能证明它是合法的。使用这个方法,如果出现错误,只会导致把合法的数据当成是非法的,而不会是相反,尽管我们不想犯任何错误,但这样总比把非法数据当成合法数据要安全得多。 |
|||
|
|||
## 区分过滤数据 |
|||
如果完成了上面的两步,数据过滤的工作就基本完成了,但是在编写Web应用的时候我们还需要区分已过滤和被污染数据,因为这样可以保证过滤数据的完整性,而不影响输入的数据。我们约定把所有经过过滤的数据放入一个叫全局的Map变量中(CleanMap)。这时需要用两个重要的步骤来防止被污染数据的注入: |
|||
- 每个请求都要初始化CleanMap为一个空Map。 |
|||
- 加入检查及阻止来自外部数据源的变量命名为CleanMap。 |
|||
|
|||
接下来,让我们通过一个例子来巩固这些概念,请看下面这个表单 |
|||
```html |
|||
|
|||
<form action="/whoami" method="POST"> |
|||
我是谁: |
|||
<select name="name"> |
|||
<option value="astaxie">astaxie</option> |
|||
<option value="herry">herry</option> |
|||
<option value="marry">marry</option> |
|||
</select> |
|||
<input type="submit" /> |
|||
</form> |
|||
|
|||
``` |
|||
在处理这个表单的编程逻辑中,非常容易犯的错误是认为只能提交三个选择中的一个。其实攻击者可以模拟POST操作,递交`name=attack`这样的数据,所以在此时我们需要做类似白名单的处理 |
|||
```Go |
|||
|
|||
r.ParseForm() |
|||
name := r.Form.Get("name") |
|||
CleanMap := make(map[string]interface{}, 0) |
|||
if name == "astaxie" || name == "herry" || name == "marry" { |
|||
CleanMap["name"] = name |
|||
} |
|||
|
|||
``` |
|||
上面代码中我们初始化了一个CleanMap的变量,当判断获取的name是`astaxie`、`herry`、`marry`三个中的一个之后 |
|||
,我们把数据存储到了CleanMap之中,这样就可以确保CleanMap["name"]中的数据是合法的,从而在代码的其它部分使用它。当然我们还可以在else部分增加非法数据的处理,一种可能是再次显示表单并提示错误。但是不要试图为了友好而输出被污染的数据。 |
|||
|
|||
上面的方法对于过滤一组已知的合法值的数据很有效,但是对于过滤有一组已知合法字符组成的数据时就没有什么帮助。例如,你可能需要一个用户名只能由字母及数字组成: |
|||
```Go |
|||
|
|||
r.ParseForm() |
|||
username := r.Form.Get("username") |
|||
CleanMap := make(map[string]interface{}, 0) |
|||
if ok, _ := regexp.MatchString("^[a-zA-Z0-9]+$", username); ok { |
|||
CleanMap["username"] = username |
|||
} |
|||
|
|||
``` |
|||
## 总结 |
|||
数据过滤在Web安全中起到一个基石的作用,大多数的安全问题都是由于没有过滤数据和验证数据引起的,例如前面小节的CSRF攻击,以及接下来将要介绍的XSS攻击、SQL注入等都是没有认真地过滤数据引起的,因此我们需要特别重视这部分的内容。 |
|||
|
|||
## links |
|||
* [目录](<preface.md>) |
|||
* 上一节: [预防CSRF攻击](<09.1.md>) |
|||
* 下一节: [避免XSS攻击](<09.3.md>) |
@ -0,0 +1,9 @@ |
|||
# 9.7 小结 |
|||
这一章主要介绍了如:CSRF攻击、XSS攻击、SQL注入攻击等一些Web应用中典型的攻击手法,它们都是由于应用对用户的输入没有很好的过滤引起的,所以除了介绍攻击的方法外,我们也介绍了了如何有效的进行数据过滤,以防止这些攻击的发生的方法。然后针对日异严重的密码泄漏事件,介绍了在设计Web应用中可采用的从基本到专家的加密方案。最后针对敏感数据的加解密简要介绍了,Go语言提供三种对称加密算法:base64、aes和des的实现。 |
|||
|
|||
编写这一章的目的是希望读者能够在意识里面加强安全概念,在编写Web应用的时候多留心一点,以使我们编写的Web应用能远离黑客们的攻击。Go语言在支持防攻击方面已经提供大量的工具包,我们可以充分的利用这些包来做出一个安全的Web应用。 |
|||
|
|||
## links |
|||
* [目录](<preface.md>) |
|||
* 上一节: [加密和解密数据](<09.6.md>) |
|||
* 下一节: [国际化和本地化](<10.0.md>) |
@ -0,0 +1,25 @@ |
|||
# 10 国际化和本地化 |
|||
为了适应经济的全球一体化,作为开发者,我们需要开发出支持多国语言、国际化的Web应用,即同样的页面在不同的语言环境下需要显示不同的效果,也就是说应用程序在运行时能够根据请求所来自的地域与语言的不同而显示不同的用户界面。这样,当需要在应用程序中添加对新的语言的支持时,无需修改应用程序的代码,只需要增加语言包即可实现。 |
|||
|
|||
国际化与本地化(Internationalization and localization,通常用i18n和L10N表示),国际化是将针对某个地区设计的程序进行重构,以使它能够在更多地区使用,本地化是指在一个面向国际化的程序中增加对新地区的支持。 |
|||
|
|||
目前,Go语言的标准包没有提供对i18n的支持,但有一些比较简单的第三方实现,这一章我们将实现一个go-i18n库,用来支持Go语言的i18n。 |
|||
|
|||
所谓的国际化:就是根据特定的locale信息,提取与之相应的字符串或其它一些东西(比如时间和货币的格式)等等。这涉及到三个问题: |
|||
|
|||
1、如何确定locale。 |
|||
|
|||
2、如何保存与locale相关的字符串或其它信息。 |
|||
|
|||
3、如何根据locale提取字符串和其它相应的信息。 |
|||
|
|||
在第一小节里,我们将介绍如何设置正确的locale以便让访问站点的用户能够获得与其语言相应的页面。第二小节将介绍如何处理或存储字符串、货币、时间日期等与locale相关的信息,第三小节将介绍如何实现国际化站点,即如何根据不同locale返回不同合适的内容。通过这三个小节的学习,我们将获得一个完整的i18n方案。 |
|||
|
|||
## 目录 |
|||
|
|||
![](images/navi10.png?raw=true) |
|||
|
|||
## links |
|||
* [目录](<preface.md>) |
|||
* 上一章: [第九章总结](<09.7.md>) |
|||
* 下一节: [设置默认地区](<10.1.md>) |
@ -0,0 +1,7 @@ |
|||
# 11.4 小结 |
|||
本章我们通过三个小节分别介绍了Go语言中如何处理错误,如何设计错误处理,然后第二小节介绍了如何通过GDB来调试程序,通过GDB我们可以单步调试、可以查看变量、修改变量、打印执行过程等,最后我们介绍了如何利用Go语言自带的轻量级框架`testing`来编写单元测试和压力测试,使用`go test`就可以方便的执行这些测试,使得我们将来代码升级修改之后很方便的进行回归测试。这一章也许对于你编写程序逻辑没有任何帮助,但是对于你编写出来的程序代码保持高质量是至关重要的,因为一个好的Web应用必定有良好的错误处理机制(错误提示的友好、可扩展性)、有好的单元测试和压力测试以保证上线之后代码能够保持良好的性能和按预期的运行。 |
|||
|
|||
## links |
|||
* [目录](<preface.md>) |
|||
* 上一节: [Go怎么写测试用例](<11.3.md>) |
|||
* 下一节: [部署与维护](<12.0.md>) |
@ -0,0 +1,11 @@ |
|||
# 12 部署与维护 |
|||
到目前为止,我们前面已经介绍了如何开发程序、调试程序以及测试程序,正如人们常说的:开发最后的10%需要花费90%的时间,所以这一章我们将强调这最后的10%部分,要真正成为让人信任并使用的优秀应用,需要考虑到一些细节,以上所说的10%就是指这些小细节。 |
|||
|
|||
本章我们将通过四个小节来介绍这些小细节的处理,第一小节介绍如何在生产服务上记录程序产生的日志,如何记录日志,第二小节介绍发生错误时我们的程序如何处理,如何保证尽量少的影响到用户的访问,第三小节介绍如何来部署Go的独立程序,由于目前Go程序还无法像C那样写成daemon,那么我们如何管理这样的进程程序后台运行呢?第四小节将介绍应用数据的备份和恢复,尽量保证应用在崩溃的情况能够保持数据的完整性。 |
|||
## 目录 |
|||
![](images/navi12.png?raw=true) |
|||
|
|||
## links |
|||
* [目录](<preface.md>) |
|||
* 上一章: [第十一章总结](<11.4.md>) |
|||
* 下一节: [应用日志](<12.1.md>) |
@ -0,0 +1,174 @@ |
|||
# 12.4 备份和恢复 |
|||
这小节我们要讨论应用程序管理的另一个方面:生产服务器上数据的备份和恢复。我们经常会遇到生产服务器的网络断了、硬盘坏了、操作系统崩溃、或者数据库不可用了等各种异常情况,所以维护人员需要对生产服务器上的应用和数据做好异地灾备,冷备热备的准备。在接下来的介绍中,讲解了如何备份应用、如何备份/恢复Mysql数据库和redis数据库。 |
|||
|
|||
## 应用备份 |
|||
在大多数集群环境下,Web应用程序基本不需要备份,因为这个其实就是一个代码副本,我们在本地开发环境中,或者版本控制系统中已经保持这些代码。但是很多时候,一些开发的站点需要用户来上传文件,那么我们需要对这些用户上传的文件进行备份。目前其实有一种合适的做法就是把和网站相关的需要存储的文件存储到云储存,这样即使系统崩溃,只要我们的文件还在云存储上,至少数据不会丢失。 |
|||
|
|||
如果我们没有采用云储存的情况下,如何做到网站的备份呢?这里我们介绍一个文件同步工具rsync:rsync能够实现网站的备份,不同系统的文件的同步,如果是windows的话,需要windows版本cwrsync。 |
|||
|
|||
### rsync安装 |
|||
rysnc的官方网站:http://rsync.samba.org/ 可以从上面获取最新版本的源码。当然,因为rsync是一款非常有用的软件,所以很多Linux的发行版本都将它收录在内了。 |
|||
|
|||
软件包安装 |
|||
|
|||
# sudo apt-get install rsync 注:在debian、ubuntu 等在线安装方法; |
|||
# yum install rsync 注:Fedora、Redhat、CentOS 等在线安装方法; |
|||
# rpm -ivh rsync 注:Fedora、Redhat、CentOS 等rpm包安装方法; |
|||
|
|||
其它Linux发行版,请用相应的软件包管理方法来安装。源码包安装 |
|||
|
|||
tar xvf rsync-xxx.tar.gz |
|||
cd rsync-xxx |
|||
./configure --prefix=/usr ;make ;make install 注:在用源码包编译安装之前,您得安装gcc等编译工具才行; |
|||
|
|||
### rsync配置 |
|||
rsync主要有以下三个配置文件rsyncd.conf(主配置文件)、rsyncd.secrets(密码文件)、rsyncd.motd(rysnc服务器信息)。 |
|||
|
|||
关于这几个文件的配置大家可以参考官方网站或者其他介绍rsync的网站,下面介绍服务器端和客户端如何开启 |
|||
|
|||
- 服务端开启: |
|||
|
|||
#/usr/bin/rsync --daemon --config=/etc/rsyncd.conf |
|||
|
|||
--daemon参数方式,是让rsync以服务器模式运行。把rsync加入开机启动 |
|||
|
|||
echo 'rsync --daemon' >> /etc/rc.d/rc.local |
|||
|
|||
设置rsync密码 |
|||
|
|||
echo '你的用户名:你的密码' > /etc/rsyncd.secrets |
|||
chmod 600 /etc/rsyncd.secrets |
|||
|
|||
|
|||
- 客户端同步: |
|||
|
|||
客户端可以通过如下命令同步服务器上的文件: |
|||
|
|||
rsync -avzP --delete --password-file=rsyncd.secrets 用户名@192.168.145.5::www /var/rsync/backup |
|||
|
|||
这条命令,简要的说明一下几个要点: |
|||
|
|||
1. -avzP是啥,读者可以使用--help查看 |
|||
2. --delete 是为了比如A上删除了一个文件,同步的时候,B会自动删除相对应的文件 |
|||
3. --password-file 客户端中/etc/rsyncd.secrets设置的密码,要和服务端的 /etc/rsyncd.secrets 中的密码一样,这样cron运行的时候,就不需要密码了 |
|||
4. 这条命令中的"用户名"为服务端的 /etc/rsyncd.secrets中的用户名 |
|||
5. 这条命令中的 192.168.145.5 为服务端的IP地址 |
|||
6. ::www,注意是2个 : 号,www为服务端的配置文件 /etc/rsyncd.conf 中的[www],意思是根据服务端上的/etc/rsyncd.conf来同步其中的[www]段内容,一个 : 号的时候,用于不根据配置文件,直接同步指定目录。 |
|||
|
|||
为了让同步实时性,可以设置crontab,保持rsync每分钟同步,当然用户也可以根据文件的重要程度设置不同的同步频率。 |
|||
|
|||
|
|||
## MySQL备份 |
|||
应用数据库目前还是MySQL为主流,目前MySQL的备份有两种方式:热备份和冷备份,热备份目前主要是采用master/slave方式(master/slave方式的同步目前主要用于数据库读写分离,也可以用于热备份数据),关于如何配置这方面的资料,大家可以找到很多。冷备份的话就是数据有一定的延迟,但是可以保证该时间段之前的数据完整,例如有些时候可能我们的误操作引起了数据的丢失,那么master/slave模式是无法找回丢失数据的,但是通过冷备份可以部分恢复数据。 |
|||
|
|||
冷备份一般使用shell脚本来实现定时备份数据库,然后通过上面介绍rsync同步非本地机房的一台服务器。 |
|||
|
|||
下面这个是定时备份mysql的备份脚本,我们使用了mysqldump程序,这个命令可以把数据库导出到一个文件中。 |
|||
|
|||
#!/bin/bash |
|||
|
|||
# 以下配置信息请自己修改 |
|||
mysql_user="USER" #MySQL备份用户 |
|||
mysql_password="PASSWORD" #MySQL备份用户的密码 |
|||
mysql_host="localhost" |
|||
mysql_port="3306" |
|||
mysql_charset="utf8" #MySQL编码 |
|||
backup_db_arr=("db1" "db2") #要备份的数据库名称,多个用空格分开隔开 如("db1" "db2" "db3") |
|||
backup_location=/var/www/mysql #备份数据存放位置,末尾请不要带"/",此项可以保持默认,程序会自动创建文件夹 |
|||
expire_backup_delete="ON" #是否开启过期备份删除 ON为开启 OFF为关闭 |
|||
expire_days=3 #过期时间天数 默认为三天,此项只有在expire_backup_delete开启时有效 |
|||
|
|||
# 本行开始以下不需要修改 |
|||
backup_time=`date +%Y%m%d%H%M` #定义备份详细时间 |
|||
backup_Ymd=`date +%Y-%m-%d` #定义备份目录中的年月日时间 |
|||
backup_3ago=`date -d '3 days ago' +%Y-%m-%d` #3天之前的日期 |
|||
backup_dir=$backup_location/$backup_Ymd #备份文件夹全路径 |
|||
welcome_msg="Welcome to use MySQL backup tools!" #欢迎语 |
|||
|
|||
# 判断MYSQL是否启动,mysql没有启动则备份退出 |
|||
mysql_ps=`ps -ef |grep mysql |wc -l` |
|||
mysql_listen=`netstat -an |grep LISTEN |grep $mysql_port|wc -l` |
|||
if [ [$mysql_ps == 0] -o [$mysql_listen == 0] ]; then |
|||
echo "ERROR:MySQL is not running! backup stop!" |
|||
exit |
|||
else |
|||
echo $welcome_msg |
|||
fi |
|||
|
|||
# 连接到mysql数据库,无法连接则备份退出 |
|||
mysql -h$mysql_host -P$mysql_port -u$mysql_user -p$mysql_password <<end |
|||
use mysql; |
|||
select host,user from user where user='root' and host='localhost'; |
|||
exit |
|||
end |
|||
|
|||
flag=`echo $?` |
|||
if [ $flag != "0" ]; then |
|||
echo "ERROR:Can't connect mysql server! backup stop!" |
|||
exit |
|||
else |
|||
echo "MySQL connect ok! Please wait......" |
|||
# 判断有没有定义备份的数据库,如果定义则开始备份,否则退出备份 |
|||
if [ "$backup_db_arr" != "" ];then |
|||
#dbnames=$(cut -d ',' -f1-5 $backup_database) |
|||
#echo "arr is (${backup_db_arr[@]})" |
|||
for dbname in ${backup_db_arr[@]} |
|||
do |
|||
echo "database $dbname backup start..." |
|||
`mkdir -p $backup_dir` |
|||
`mysqldump -h$mysql_host -P$mysql_port -u$mysql_user -p$mysql_password $dbname --default-character-set=$mysql_charset | gzip > $backup_dir/$dbname-$backup_time.sql.gz` |
|||
flag=`echo $?` |
|||
if [ $flag == "0" ];then |
|||
echo "database $dbname success backup to $backup_dir/$dbname-$backup_time.sql.gz" |
|||
else |
|||
echo "database $dbname backup fail!" |
|||
fi |
|||
|
|||
done |
|||
else |
|||
echo "ERROR:No database to backup! backup stop" |
|||
exit |
|||
fi |
|||
# 如果开启了删除过期备份,则进行删除操作 |
|||
if [ "$expire_backup_delete" == "ON" -a "$backup_location" != "" ];then |
|||
#`find $backup_location/ -type d -o -type f -ctime +$expire_days -exec rm -rf {} \;` |
|||
`find $backup_location/ -type d -mtime +$expire_days | xargs rm -rf` |
|||
echo "Expired backup data delete complete!" |
|||
fi |
|||
echo "All database backup success! Thank you!" |
|||
exit |
|||
fi |
|||
|
|||
修改shell脚本的属性: |
|||
|
|||
chmod 600 /root/mysql_backup.sh |
|||
chmod +x /root/mysql_backup.sh |
|||
|
|||
设置好属性之后,把命令加入crontab,我们设置了每天00:00定时自动备份,然后把备份的脚本目录/var/www/mysql设置为rsync同步目录。 |
|||
|
|||
00 00 * * * /root/mysql_backup.sh |
|||
|
|||
## MySQL恢复 |
|||
前面介绍MySQL备份分为热备份和冷备份,热备份主要的目的是为了能够实时的恢复,例如应用服务器出现了硬盘故障,那么我们可以通过修改配置文件把数据库的读取和写入改成slave,这样就可以尽量少时间的中断服务。 |
|||
|
|||
但是有时候我们需要通过冷备份的SQL来进行数据恢复,既然有了数据库的备份,就可以通过命令导入: |
|||
|
|||
mysql -u username -p databse < backup.sql |
|||
|
|||
可以看到,导出和导入数据库数据都是相当简单,不过如果还需要管理权限,或者其他的一些字符集的设置的话,可能会稍微复杂一些,但是这些都是可以通过一些命令来完成的。 |
|||
|
|||
## redis备份 |
|||
redis是目前我们使用最多的NoSQL,它的备份也分为两种:热备份和冷备份,redis也支持master/slave模式,所以我们的热备份可以通过这种方式实现,相应的配置大家可以参考官方的文档配置,相当的简单。我们这里介绍冷备份的方式:redis其实会定时的把内存里面的缓存数据保存到数据库文件里面,我们备份只要备份相应的文件就可以,就是利用前面介绍的rsync备份到非本地机房就可以实现。 |
|||
|
|||
## redis恢复 |
|||
redis的恢复分为热备份恢复和冷备份恢复,热备份恢复的目的和方法同MySQL的恢复一样,只要修改应用的相应的数据库连接即可。 |
|||
|
|||
但是有时候我们需要根据冷备份来恢复数据,redis的冷备份恢复其实就是只要把保存的数据库文件copy到redis的工作目录,然后启动redis就可以了,redis在启动的时候会自动加载数据库文件到内存中,启动的速度根据数据库的文件大小来决定。 |
|||
|
|||
## 小结 |
|||
本小节介绍了我们的应用部分的备份和恢复,即如何做好灾备,包括文件的备份、数据库的备份。同时也介绍了使用rsync同步不同系统的文件,MySQL数据库和redis数据库的备份和恢复,希望通过本小节的介绍,能够给作为开发的你对于线上产品的灾备方案提供一个参考方案。 |
|||
|
|||
## links |
|||
* [目录](<preface.md>) |
|||
* 上一章: [应用部署](<12.3.md>) |
|||
* 下一节: [小结](<12.5.md>) |
@ -0,0 +1,171 @@ |
|||
<!-- {% raw %} --> |
|||
# 13.3 controller设计 |
|||
|
|||
传统的MVC框架大多数是基于Action设计的后缀式映射,然而,现在Web流行REST风格的架构。尽管使用Filter或者rewrite能够通过URL重写实现REST风格的URL,但是为什么不直接设计一个全新的REST风格的 MVC框架呢?本小节就是基于这种思路来讲述如何从头设计一个基于REST风格的MVC框架中的controller,最大限度地简化Web应用的开发,甚至编写一行代码就可以实现“Hello, world”。 |
|||
|
|||
## controller作用 |
|||
MVC设计模式是目前Web应用开发中最常见的架构模式,通过分离 Model(模型)、View(视图)和 Controller(控制器),可以更容易实现易于扩展的用户界面(UI)。Model指后台返回的数据;View指需要渲染的页面,通常是模板页面,渲染后的内容通常是HTML;Controller指Web开发人员编写的处理不同URL的控制器,如前面小节讲述的路由就是URL请求转发到控制器的过程,controller在整个的MVC框架中起到了一个核心的作用,负责处理业务逻辑,因此控制器是整个框架中必不可少的一部分,Model和View对于有些业务需求是可以不写的,例如没有数据处理的逻辑处理,没有页面输出的302调整之类的就不需要Model和View,但是controller这一环节是必不可少的。 |
|||
|
|||
## beego的REST设计 |
|||
前面小节介绍了路由实现了注册struct的功能,而struct中实现了REST方式,因此我们需要设计一个用于逻辑处理controller的基类,这里主要设计了两个类型,一个struct、一个interface |
|||
```Go |
|||
|
|||
type Controller struct { |
|||
Ct *Context |
|||
Tpl *template.Template |
|||
Data map[interface{}]interface{} |
|||
ChildName string |
|||
TplNames string |
|||
Layout []string |
|||
TplExt string |
|||
} |
|||
|
|||
type ControllerInterface interface { |
|||
Init(ct *Context, cn string) //初始化上下文和子类名称 |
|||
Prepare() //开始执行之前的一些处理 |
|||
Get() //method=GET的处理 |
|||
Post() //method=POST的处理 |
|||
Delete() //method=DELETE的处理 |
|||
Put() //method=PUT的处理 |
|||
Head() //method=HEAD的处理 |
|||
Patch() //method=PATCH的处理 |
|||
Options() //method=OPTIONS的处理 |
|||
Finish() //执行完成之后的处理 |
|||
Render() error //执行完method对应的方法之后渲染页面 |
|||
} |
|||
``` |
|||
那么前面介绍的路由add函数的时候是定义了ControllerInterface类型,因此,只要我们实现这个接口就可以,所以我们的基类Controller实现如下的方法: |
|||
```Go |
|||
|
|||
func (c *Controller) Init(ct *Context, cn string) { |
|||
c.Data = make(map[interface{}]interface{}) |
|||
c.Layout = make([]string, 0) |
|||
c.TplNames = "" |
|||
c.ChildName = cn |
|||
c.Ct = ct |
|||
c.TplExt = "tpl" |
|||
} |
|||
|
|||
func (c *Controller) Prepare() { |
|||
|
|||
} |
|||
|
|||
func (c *Controller) Finish() { |
|||
|
|||
} |
|||
|
|||
func (c *Controller) Get() { |
|||
http.Error(c.Ct.ResponseWriter, "Method Not Allowed", 405) |
|||
} |
|||
|
|||
func (c *Controller) Post() { |
|||
http.Error(c.Ct.ResponseWriter, "Method Not Allowed", 405) |
|||
} |
|||
|
|||
func (c *Controller) Delete() { |
|||
http.Error(c.Ct.ResponseWriter, "Method Not Allowed", 405) |
|||
} |
|||
|
|||
func (c *Controller) Put() { |
|||
http.Error(c.Ct.ResponseWriter, "Method Not Allowed", 405) |
|||
} |
|||
|
|||
func (c *Controller) Head() { |
|||
http.Error(c.Ct.ResponseWriter, "Method Not Allowed", 405) |
|||
} |
|||
|
|||
func (c *Controller) Patch() { |
|||
http.Error(c.Ct.ResponseWriter, "Method Not Allowed", 405) |
|||
} |
|||
|
|||
func (c *Controller) Options() { |
|||
http.Error(c.Ct.ResponseWriter, "Method Not Allowed", 405) |
|||
} |
|||
|
|||
func (c *Controller) Render() error { |
|||
if len(c.Layout) > 0 { |
|||
var filenames []string |
|||
for _, file := range c.Layout { |
|||
filenames = append(filenames, path.Join(ViewsPath, file)) |
|||
} |
|||
t, err := template.ParseFiles(filenames...) |
|||
if err != nil { |
|||
Trace("template ParseFiles err:", err) |
|||
} |
|||
err = t.ExecuteTemplate(c.Ct.ResponseWriter, c.TplNames, c.Data) |
|||
if err != nil { |
|||
Trace("template Execute err:", err) |
|||
} |
|||
} else { |
|||
if c.TplNames == "" { |
|||
c.TplNames = c.ChildName + "/" + c.Ct.Request.Method + "." + c.TplExt |
|||
} |
|||
t, err := template.ParseFiles(path.Join(ViewsPath, c.TplNames)) |
|||
if err != nil { |
|||
Trace("template ParseFiles err:", err) |
|||
} |
|||
err = t.Execute(c.Ct.ResponseWriter, c.Data) |
|||
if err != nil { |
|||
Trace("template Execute err:", err) |
|||
} |
|||
} |
|||
return nil |
|||
} |
|||
|
|||
func (c *Controller) Redirect(url string, code int) { |
|||
c.Ct.Redirect(code, url) |
|||
} |
|||
``` |
|||
上面的controller基类已经实现了接口定义的函数,通过路由根据url执行相应的controller的原则,会依次执行如下: |
|||
```Go |
|||
|
|||
Init() 初始化 |
|||
Prepare() 执行之前的初始化,每个继承的子类可以来实现该函数 |
|||
method() 根据不同的method执行不同的函数:GET、POST、PUT、HEAD等,子类来实现这些函数,如果没实现,那么默认都是403 |
|||
Render() 可选,根据全局变量AutoRender来判断是否执行 |
|||
Finish() 执行完之后执行的操作,每个继承的子类可以来实现该函数 |
|||
``` |
|||
## 应用指南 |
|||
上面beego框架中完成了controller基类的设计,那么我们在我们的应用中可以这样来设计我们的方法: |
|||
```Go |
|||
|
|||
package controllers |
|||
|
|||
import ( |
|||
"github.com/astaxie/beego" |
|||
) |
|||
|
|||
type MainController struct { |
|||
beego.Controller |
|||
} |
|||
|
|||
func (this *MainController) Get() { |
|||
this.Data["Username"] = "astaxie" |
|||
this.Data["Email"] = "astaxie@gmail.com" |
|||
this.TplNames = "index.tpl" |
|||
} |
|||
``` |
|||
上面的方式我们实现了子类MainController,实现了Get方法,那么如果用户通过其他的方式(POST/HEAD等)来访问该资源都将返回405,而如果是Get来访问,因为我们设置了AutoRender=true,那么在执行完Get方法之后会自动执行Render函数,就会显示如下界面: |
|||
|
|||
![](images/13.4.beego.png?raw=true) |
|||
|
|||
index.tpl的代码如下所示,我们可以看到数据的设置和显示都是相当的简单方便: |
|||
```html |
|||
|
|||
<!DOCTYPE html> |
|||
<html> |
|||
<head> |
|||
<title>beego welcome template</title> |
|||
</head> |
|||
<body> |
|||
<h1>Hello, world!{{.Username}},{{.Email}}</h1> |
|||
</body> |
|||
</html> |
|||
|
|||
``` |
|||
|
|||
## links |
|||
* [目录](<preface.md>) |
|||
* 上一章: [自定义路由器设计](<13.2.md>) |
|||
* 下一节: [日志和配置设计](<13.4.md>) |
|||
<!-- {% endraw %} --> |
@ -0,0 +1,256 @@ |
|||
# 13.4 日志和配置设计 |
|||
|
|||
## 日志和配置的重要性 |
|||
前面已经介绍过日志在我们程序开发中起着很重要的作用,通过日志我们可以记录调试我们的信息,当初介绍过一个日志系统seelog,根据不同的level输出不同的日志,这个对于程序开发和程序部署来说至关重要。我们可以在程序开发中设置level低一点,部署的时候把level设置高,这样我们开发中的调试信息可以屏蔽掉。 |
|||
|
|||
配置模块对于应用部署牵涉到服务器不同的一些配置信息非常有用,例如一些数据库配置信息、监听端口、监听地址等都是可以通过配置文件来配置,这样我们的应用程序就具有很强的灵活性,可以通过配置文件的配置部署在不同的机器上,可以连接不同的数据库之类的。 |
|||
|
|||
## beego的日志设计 |
|||
beego的日志设计部署思路来自于seelog,根据不同的level来记录日志,但是beego设计的日志系统比较轻量级,采用了系统的log.Logger接口,默认输出到os.Stdout,用户可以实现这个接口然后通过beego.SetLogger设置自定义的输出,详细的实现如下所示: |
|||
|
|||
```Go |
|||
|
|||
// Log levels to control the logging output. |
|||
const ( |
|||
LevelTrace = iota |
|||
LevelDebug |
|||
LevelInfo |
|||
LevelWarning |
|||
LevelError |
|||
LevelCritical |
|||
) |
|||
|
|||
// logLevel controls the global log level used by the logger. |
|||
var level = LevelTrace |
|||
|
|||
// LogLevel returns the global log level and can be used in |
|||
// own implementations of the logger interface. |
|||
func Level() int { |
|||
return level |
|||
} |
|||
|
|||
// SetLogLevel sets the global log level used by the simple |
|||
// logger. |
|||
func SetLevel(l int) { |
|||
level = l |
|||
} |
|||
``` |
|||
上面这一段实现了日志系统的日志分级,默认的级别是Trace,用户通过SetLevel可以设置不同的分级。 |
|||
```Go |
|||
|
|||
// logger references the used application logger. |
|||
var BeeLogger = log.New(os.Stdout, "", log.Ldate|log.Ltime) |
|||
|
|||
// SetLogger sets a new logger. |
|||
func SetLogger(l *log.Logger) { |
|||
BeeLogger = l |
|||
} |
|||
|
|||
// Trace logs a message at trace level. |
|||
func Trace(v ...interface{}) { |
|||
if level <= LevelTrace { |
|||
BeeLogger.Printf("[T] %v\n", v) |
|||
} |
|||
} |
|||
|
|||
// Debug logs a message at debug level. |
|||
func Debug(v ...interface{}) { |
|||
if level <= LevelDebug { |
|||
BeeLogger.Printf("[D] %v\n", v) |
|||
} |
|||
} |
|||
|
|||
// Info logs a message at info level. |
|||
func Info(v ...interface{}) { |
|||
if level <= LevelInfo { |
|||
BeeLogger.Printf("[I] %v\n", v) |
|||
} |
|||
} |
|||
|
|||
// Warning logs a message at warning level. |
|||
func Warn(v ...interface{}) { |
|||
if level <= LevelWarning { |
|||
BeeLogger.Printf("[W] %v\n", v) |
|||
} |
|||
} |
|||
|
|||
// Error logs a message at error level. |
|||
func Error(v ...interface{}) { |
|||
if level <= LevelError { |
|||
BeeLogger.Printf("[E] %v\n", v) |
|||
} |
|||
} |
|||
|
|||
// Critical logs a message at critical level. |
|||
func Critical(v ...interface{}) { |
|||
if level <= LevelCritical { |
|||
BeeLogger.Printf("[C] %v\n", v) |
|||
} |
|||
} |
|||
``` |
|||
上面这一段代码默认初始化了一个BeeLogger对象,默认输出到os.Stdout,用户可以通过beego.SetLogger来设置实现了logger的接口输出。这里面实现了六个函数: |
|||
|
|||
- Trace(一般的记录信息,举例如下:) |
|||
- "Entered parse function validation block" |
|||
- "Validation: entered second 'if'" |
|||
- "Dictionary 'Dict' is empty. Using default value" |
|||
- Debug(调试信息,举例如下:) |
|||
- "Web page requested: http://somesite.com Params='...'" |
|||
- "Response generated. Response size: 10000. Sending." |
|||
- "New file received. Type:PNG Size:20000" |
|||
- Info(打印信息,举例如下:) |
|||
- "Web server restarted" |
|||
- "Hourly statistics: Requested pages: 12345 Errors: 123 ..." |
|||
- "Service paused. Waiting for 'resume' call" |
|||
- Warn(警告信息,举例如下:) |
|||
- "Cache corrupted for file='test.file'. Reading from back-end" |
|||
- "Database 192.168.0.7/DB not responding. Using backup 192.168.0.8/DB" |
|||
- "No response from statistics server. Statistics not sent" |
|||
- Error(错误信息,举例如下:) |
|||
- "Internal error. Cannot process request #12345 Error:...." |
|||
- "Cannot perform login: credentials DB not responding" |
|||
- Critical(致命错误,举例如下:) |
|||
- "Critical panic received: .... Shutting down" |
|||
- "Fatal error: ... App is shutting down to prevent data corruption or loss" |
|||
|
|||
可以看到每个函数里面都有对level的判断,所以如果我们在部署的时候设置了level=LevelWarning,那么Trace、Debug、Info这三个函数都不会有任何的输出,以此类推。 |
|||
|
|||
## beego的配置设计 |
|||
配置信息的解析,beego实现了一个key=value的配置文件读取,类似ini配置文件的格式,就是一个文件解析的过程,然后把解析的数据保存到map中,最后在调用的时候通过几个string、int之类的函数调用返回相应的值,具体的实现请看下面: |
|||
|
|||
首先定义了一些ini配置文件的一些全局性常量 : |
|||
```Go |
|||
|
|||
var ( |
|||
bComment = []byte{'#'} |
|||
bEmpty = []byte{} |
|||
bEqual = []byte{'='} |
|||
bDQuote = []byte{'"'} |
|||
) |
|||
``` |
|||
定义了配置文件的格式: |
|||
```Go |
|||
|
|||
// A Config represents the configuration. |
|||
type Config struct { |
|||
filename string |
|||
comment map[int][]string // id: []{comment, key...}; id 1 is for main comment. |
|||
data map[string]string // key: value |
|||
offset map[string]int64 // key: offset; for editing. |
|||
sync.RWMutex |
|||
} |
|||
``` |
|||
定义了解析文件的函数,解析文件的过程是打开文件,然后一行一行的读取,解析注释、空行和key=value数据: |
|||
```Go |
|||
|
|||
// ParseFile creates a new Config and parses the file configuration from the |
|||
// named file. |
|||
func LoadConfig(name string) (*Config, error) { |
|||
file, err := os.Open(name) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
|
|||
cfg := &Config{ |
|||
file.Name(), |
|||
make(map[int][]string), |
|||
make(map[string]string), |
|||
make(map[string]int64), |
|||
sync.RWMutex{}, |
|||
} |
|||
cfg.Lock() |
|||
defer cfg.Unlock() |
|||
defer file.Close() |
|||
|
|||
var comment bytes.Buffer |
|||
buf := bufio.NewReader(file) |
|||
|
|||
for nComment, off := 0, int64(1); ; { |
|||
line, _, err := buf.ReadLine() |
|||
if err == io.EOF { |
|||
break |
|||
} |
|||
if bytes.Equal(line, bEmpty) { |
|||
continue |
|||
} |
|||
|
|||
off += int64(len(line)) |
|||
|
|||
if bytes.HasPrefix(line, bComment) { |
|||
line = bytes.TrimLeft(line, "#") |
|||
line = bytes.TrimLeftFunc(line, unicode.IsSpace) |
|||
comment.Write(line) |
|||
comment.WriteByte('\n') |
|||
continue |
|||
} |
|||
if comment.Len() != 0 { |
|||
cfg.comment[nComment] = []string{comment.String()} |
|||
comment.Reset() |
|||
nComment++ |
|||
} |
|||
|
|||
val := bytes.SplitN(line, bEqual, 2) |
|||
if bytes.HasPrefix(val[1], bDQuote) { |
|||
val[1] = bytes.Trim(val[1], `"`) |
|||
} |
|||
|
|||
key := strings.TrimSpace(string(val[0])) |
|||
cfg.comment[nComment-1] = append(cfg.comment[nComment-1], key) |
|||
cfg.data[key] = strings.TrimSpace(string(val[1])) |
|||
cfg.offset[key] = off |
|||
} |
|||
return cfg, nil |
|||
} |
|||
``` |
|||
下面实现了一些读取配置文件的函数,返回的值确定为bool、int、float64或string: |
|||
```Go |
|||
|
|||
// Bool returns the boolean value for a given key. |
|||
func (c *Config) Bool(key string) (bool, error) { |
|||
return strconv.ParseBool(c.data[key]) |
|||
} |
|||
|
|||
// Int returns the integer value for a given key. |
|||
func (c *Config) Int(key string) (int, error) { |
|||
return strconv.Atoi(c.data[key]) |
|||
} |
|||
|
|||
// Float returns the float value for a given key. |
|||
func (c *Config) Float(key string) (float64, error) { |
|||
return strconv.ParseFloat(c.data[key], 64) |
|||
} |
|||
|
|||
// String returns the string value for a given key. |
|||
func (c *Config) String(key string) string { |
|||
return c.data[key] |
|||
} |
|||
``` |
|||
## 应用指南 |
|||
下面这个函数是我一个应用中的例子,用来获取远程url地址的json数据,实现如下: |
|||
```Go |
|||
|
|||
func GetJson() { |
|||
resp, err := http.Get(beego.AppConfig.String("url")) |
|||
if err != nil { |
|||
beego.Critical("http get info error") |
|||
return |
|||
} |
|||
defer resp.Body.Close() |
|||
body, err := ioutil.ReadAll(resp.Body) |
|||
err = json.Unmarshal(body, &AllInfo) |
|||
if err != nil { |
|||
beego.Critical("error:", err) |
|||
} |
|||
} |
|||
``` |
|||
函数中调用了框架的日志函数`beego.Critical`函数用来报错,调用了`beego.AppConfig.String("url")`用来获取配置文件中的信息,配置文件的信息如下(app.conf): |
|||
```Go |
|||
|
|||
appname = hs |
|||
url ="http://www.api.com/api.html" |
|||
|
|||
``` |
|||
## links |
|||
* [目录](<preface.md>) |
|||
* 上一章: [controller设计](<13.3.md>) |
|||
* 下一节: [实现博客的增删改](<13.5.md>) |
@ -0,0 +1,277 @@ |
|||
<!-- {% raw %} --> |
|||
# 13.5 实现博客的增删改 |
|||
|
|||
前面介绍了beego框架实现的整体构思以及部分实现的伪代码,这小节介绍通过beego建立一个博客系统,包括博客浏览、添加、修改、删除等操作。 |
|||
## 博客目录 |
|||
博客目录如下所示: |
|||
|
|||
. |
|||
├── controllers |
|||
│ ├── delete.go |
|||
│ ├── edit.go |
|||
│ ├── index.go |
|||
│ ├── new.go |
|||
│ └── view.go |
|||
├── main.go |
|||
├── models |
|||
│ └── model.go |
|||
└── views |
|||
├── edit.tpl |
|||
├── index.tpl |
|||
├── layout.tpl |
|||
├── new.tpl |
|||
└── view.tpl |
|||
|
|||
## 博客路由 |
|||
博客主要的路由规则如下所示: |
|||
```Go |
|||
|
|||
//显示博客首页 |
|||
beego.Router("/", &controllers.IndexController{}) |
|||
//查看博客详细信息 |
|||
beego.Router("/view/:id([0-9]+)", &controllers.ViewController{}) |
|||
//新建博客博文 |
|||
beego.Router("/new", &controllers.NewController{}) |
|||
//删除博文 |
|||
beego.Router("/delete/:id([0-9]+)", &controllers.DeleteController{}) |
|||
//编辑博文 |
|||
beego.Router("/edit/:id([0-9]+)", &controllers.EditController{}) |
|||
|
|||
``` |
|||
## 数据库结构 |
|||
数据库设计最简单的博客信息 |
|||
```sql |
|||
|
|||
CREATE TABLE entries ( |
|||
id INT AUTO_INCREMENT, |
|||
title TEXT, |
|||
content TEXT, |
|||
created DATETIME, |
|||
primary key (id) |
|||
); |
|||
``` |
|||
## 控制器 |
|||
IndexController: |
|||
|
|||
```Go |
|||
|
|||
type IndexController struct { |
|||
beego.Controller |
|||
} |
|||
|
|||
func (this *IndexController) Get() { |
|||
this.Data["blogs"] = models.GetAll() |
|||
this.Layout = "layout.tpl" |
|||
this.TplName = "index.tpl" |
|||
} |
|||
``` |
|||
ViewController: |
|||
|
|||
```Go |
|||
|
|||
type ViewController struct { |
|||
beego.Controller |
|||
} |
|||
|
|||
func (this *ViewController) Get() { |
|||
id, _ := strconv.Atoi(this.Ctx.Input.Params()[":id"]) |
|||
this.Data["Post"] = models.GetBlog(id) |
|||
this.Layout = "layout.tpl" |
|||
this.TplName = "view.tpl" |
|||
} |
|||
``` |
|||
NewController |
|||
```Go |
|||
|
|||
type NewController struct { |
|||
beego.Controller |
|||
} |
|||
|
|||
func (this *NewController) Get() { |
|||
this.Layout = "layout.tpl" |
|||
this.TplName = "new.tpl" |
|||
} |
|||
|
|||
func (this *NewController) Post() { |
|||
inputs := this.Input() |
|||
var blog models.Blog |
|||
blog.Title = inputs.Get("title") |
|||
blog.Content = inputs.Get("content") |
|||
blog.Created = time.Now() |
|||
models.SaveBlog(blog) |
|||
this.Ctx.Redirect(302, "/") |
|||
} |
|||
``` |
|||
EditController |
|||
```Go |
|||
|
|||
type EditController struct { |
|||
beego.Controller |
|||
} |
|||
|
|||
func (this *EditController) Get() { |
|||
id, _ := strconv.Atoi(this.Ctx.Input.Params()[":id"]) |
|||
this.Data["Post"] = models.GetBlog(id) |
|||
this.Layout = "layout.tpl" |
|||
this.TplName = "edit.tpl" |
|||
} |
|||
|
|||
func (this *EditController) Post() { |
|||
inputs := this.Input() |
|||
var blog models.Blog |
|||
blog.Id, _ = strconv.Atoi(inputs.Get("id")) |
|||
blog.Title = inputs.Get("title") |
|||
blog.Content = inputs.Get("content") |
|||
blog.Created = time.Now() |
|||
models.SaveBlog(blog) |
|||
this.Ctx.Redirect(302, "/") |
|||
} |
|||
``` |
|||
DeleteController |
|||
```Go |
|||
|
|||
type DeleteController struct { |
|||
beego.Controller |
|||
} |
|||
|
|||
func (this *DeleteController) Get() { |
|||
id, _ := strconv.Atoi(this.Ctx.Input.Params()[":id"]) |
|||
blog := models.GetBlog(id) |
|||
this.Data["Post"] = blog |
|||
models.DelBlog(blog) |
|||
this.Ctx.Redirect(302, "/") |
|||
} |
|||
``` |
|||
## model层 |
|||
```Go |
|||
|
|||
package models |
|||
|
|||
import ( |
|||
"database/sql" |
|||
"github.com/astaxie/beedb" |
|||
_ "github.com/ziutek/mymysql/godrv" |
|||
"time" |
|||
) |
|||
|
|||
type Blog struct { |
|||
Id int `PK` |
|||
Title string |
|||
Content string |
|||
Created time.Time |
|||
} |
|||
|
|||
func GetLink() beedb.Model { |
|||
db, err := sql.Open("mymysql", "blog/astaxie/123456") |
|||
if err != nil { |
|||
panic(err) |
|||
} |
|||
orm := beedb.New(db) |
|||
return orm |
|||
} |
|||
|
|||
func GetAll() (blogs []Blog) { |
|||
db := GetLink() |
|||
db.FindAll(&blogs) |
|||
return |
|||
} |
|||
|
|||
func GetBlog(id int) (blog Blog) { |
|||
db := GetLink() |
|||
db.Where("id=?", id).Find(&blog) |
|||
return |
|||
} |
|||
|
|||
func SaveBlog(blog Blog) (bg Blog) { |
|||
db := GetLink() |
|||
db.Save(&blog) |
|||
return bg |
|||
} |
|||
|
|||
func DelBlog(blog Blog) { |
|||
db := GetLink() |
|||
db.Delete(&blog) |
|||
return |
|||
} |
|||
``` |
|||
|
|||
## view层 |
|||
|
|||
layout.tpl |
|||
```html |
|||
|
|||
<html> |
|||
<head> |
|||
<title>My Blog</title> |
|||
<style> |
|||
#menu { |
|||
width: 200px; |
|||
float: right; |
|||
} |
|||
</style> |
|||
</head> |
|||
<body> |
|||
|
|||
<ul id="menu"> |
|||
<li><a href="/">Home</a></li> |
|||
<li><a href="/new">New Post</a></li> |
|||
</ul> |
|||
|
|||
{{.LayoutContent}} |
|||
|
|||
</body> |
|||
</html> |
|||
``` |
|||
|
|||
index.tpl |
|||
```html |
|||
|
|||
<h1>Blog posts</h1> |
|||
|
|||
<ul> |
|||
{{range .blogs}} |
|||
<li> |
|||
<a href="/view/{{.Id}}">{{.Title}}</a> |
|||
from {{.Created}} |
|||
<a href="/edit/{{.Id}}">Edit</a> |
|||
<a href="/delete/{{.Id}}">Delete</a> |
|||
</li> |
|||
{{end}} |
|||
</ul> |
|||
``` |
|||
view.tpl |
|||
```html |
|||
|
|||
<h1>{{.Post.Title}}</h1> |
|||
{{.Post.Created}}<br/> |
|||
|
|||
{{.Post.Content}} |
|||
``` |
|||
new.tpl |
|||
```html |
|||
|
|||
<h1>New Blog Post</h1> |
|||
<form action="" method="post"> |
|||
标题:<input type="text" name="title"><br> |
|||
内容:<textarea name="content" colspan="3" rowspan="10"></textarea> |
|||
<input type="submit"> |
|||
</form> |
|||
``` |
|||
edit.tpl |
|||
```html |
|||
|
|||
<h1>Edit {{.Post.Title}}</h1> |
|||
|
|||
<h1>New Blog Post</h1> |
|||
<form action="" method="post"> |
|||
标题:<input type="text" name="title" value="{{.Post.Title}}"><br> |
|||
内容:<textarea name="content" colspan="3" rowspan="10">{{.Post.Content}}</textarea> |
|||
<input type="hidden" name="id" value="{{.Post.Id}}"> |
|||
<input type="submit"> |
|||
</form> |
|||
``` |
|||
## links |
|||
* [目录](<preface.md>) |
|||
* 上一章: [日志和配置设计](<13.4.md>) |
|||
* 下一节: [小结](<13.6.md>) |
|||
<!-- {% endraw %} --> |
@ -0,0 +1,7 @@ |
|||
# 13.6 小结 |
|||
这一章我们主要介绍了如何实现一个基础的Go语言框架,框架包含有路由设计,由于Go内置的http包中路由的一些不足点,我们设计了动态路由规则,然后介绍了MVC模式中的Controller设计,controller实现了REST的实现,这个主要思路来源于tornado框架,然后设计实现了模板的layout以及自动化渲染等技术,主要采用了Go内置的模板引擎,最后我们介绍了一些辅助的日志、配置等信息的设计,通过这些设计我们实现了一个基础的框架beego,目前该框架已经开源在GitHub,最后我们通过beego实现了一个博客系统,通过实例代码详细的展现了如何快速的开发一个站点。 |
|||
|
|||
## links |
|||
* [目录](<preface.md>) |
|||
* 上一章: [实现博客的增删改](<13.5.md>) |
|||
* 下一节: [扩展Web框架](<14.0.md>) |
@ -0,0 +1,114 @@ |
|||
# 14.2 Session支持 |
|||
第六章的时候我们介绍过如何在Go语言中使用session,也实现了一个sessionManger,beego框架基于sessionManager实现了方便的session处理功能。 |
|||
|
|||
## session集成 |
|||
beego中主要有以下的全局变量来控制session处理: |
|||
```Go |
|||
|
|||
//related to session |
|||
SessionOn bool // 是否开启session模块,默认不开启 |
|||
SessionProvider string // session后端提供处理模块,默认是sessionManager支持的memory |
|||
SessionName string // 客户端保存的cookies的名称 |
|||
SessionGCMaxLifetime int64 // cookies有效期 |
|||
|
|||
GlobalSessions *session.Manager //全局session控制器 |
|||
``` |
|||
当然上面这些变量需要初始化值,也可以按照下面的代码来配合配置文件以设置这些值: |
|||
```Go |
|||
|
|||
if ar, err := AppConfig.Bool("sessionon"); err != nil { |
|||
SessionOn = false |
|||
} else { |
|||
SessionOn = ar |
|||
} |
|||
if ar := AppConfig.String("sessionprovider"); ar == "" { |
|||
SessionProvider = "memory" |
|||
} else { |
|||
SessionProvider = ar |
|||
} |
|||
if ar := AppConfig.String("sessionname"); ar == "" { |
|||
SessionName = "beegosessionID" |
|||
} else { |
|||
SessionName = ar |
|||
} |
|||
if ar, err := AppConfig.Int("sessiongcmaxlifetime"); err != nil && ar != 0 { |
|||
int64val, _ := strconv.ParseInt(strconv.Itoa(ar), 10, 64) |
|||
SessionGCMaxLifetime = int64val |
|||
} else { |
|||
SessionGCMaxLifetime = 3600 |
|||
} |
|||
``` |
|||
在beego.Run函数中增加如下代码: |
|||
```Go |
|||
|
|||
if SessionOn { |
|||
GlobalSessions, _ = session.NewManager(SessionProvider, SessionName, SessionGCMaxLifetime) |
|||
go GlobalSessions.GC() |
|||
} |
|||
``` |
|||
这样只要SessionOn设置为true,那么就会默认开启session功能,独立开一个goroutine来处理session。 |
|||
|
|||
为了方便我们在自定义Controller中快速使用session,作者在`beego.Controller`中提供了如下方法: |
|||
```Go |
|||
|
|||
func (c *Controller) StartSession() (sess session.Session) { |
|||
sess = GlobalSessions.SessionStart(c.Ctx.ResponseWriter, c.Ctx.Request) |
|||
return |
|||
} |
|||
``` |
|||
## session使用 |
|||
通过上面的代码我们可以看到,beego框架简单地继承了session功能,那么在项目中如何使用呢? |
|||
|
|||
首先我们需要在应用的main入口处开启session: |
|||
```Go |
|||
|
|||
beego.SessionOn = true |
|||
``` |
|||
|
|||
然后我们就可以在控制器的相应方法中如下所示的使用session了: |
|||
```Go |
|||
|
|||
func (this *MainController) Get() { |
|||
var intcount int |
|||
sess := this.StartSession() |
|||
count := sess.Get("count") |
|||
if count == nil { |
|||
intcount = 0 |
|||
} else { |
|||
intcount = count.(int) |
|||
} |
|||
intcount = intcount + 1 |
|||
sess.Set("count", intcount) |
|||
this.Data["Username"] = "astaxie" |
|||
this.Data["Email"] = "astaxie@gmail.com" |
|||
this.Data["Count"] = intcount |
|||
this.TplNames = "index.tpl" |
|||
} |
|||
``` |
|||
上面的代码展示了如何在控制逻辑中使用session,主要分两个步骤: |
|||
|
|||
1. 获取session对象 |
|||
|
|||
```Go |
|||
|
|||
//获取对象,类似PHP中的session_start() |
|||
sess := this.StartSession() |
|||
``` |
|||
|
|||
2. 使用session进行一般的session值操作 |
|||
|
|||
```Go |
|||
|
|||
//获取session值,类似PHP中的$_SESSION["count"] |
|||
sess.Get("count") |
|||
|
|||
//设置session值 |
|||
sess.Set("count", intcount) |
|||
``` |
|||
从上面代码可以看出基于beego框架开发的应用中使用session相当方便,基本上和PHP中调用`session_start()`类似。 |
|||
|
|||
|
|||
## links |
|||
* [目录](<preface.md>) |
|||
* 上一节: [静态文件支持](<14.1.md>) |
|||
* 下一节: [表单及验证支持](<14.3.md>) |
@ -0,0 +1,287 @@ |
|||
<!-- {% raw %} --> |
|||
# 14.3 表单及验证支持 |
|||
在Web开发中对于这样的一个流程可能很眼熟: |
|||
|
|||
- 打开一个网页显示出表单。 |
|||
- 用户填写并提交了表单。 |
|||
- 如果用户提交了一些无效的信息,或者可能漏掉了一个必填项,表单将会连同用户的数据和错误问题的描述信息返回。 |
|||
- 用户再次填写,继续上一步过程,直到提交了一个有效的表单。 |
|||
|
|||
在接收端,脚本必须: |
|||
|
|||
- 检查用户递交的表单数据。 |
|||
- 验证数据是否为正确的类型,合适的标准。例如,如果一个用户名被提交,它必须被验证是否只包含了允许的字符。它必须有一个最小长度,不能超过最大长度。用户名不能与已存在的他人用户名重复,甚至是一个保留字等。 |
|||
- 过滤数据并清理不安全字符,保证逻辑处理中接收的数据是安全的。 |
|||
- 如果需要,预格式化数据(数据需要清除空白或者经过HTML编码等等。) |
|||
- 准备好数据,插入数据库。 |
|||
|
|||
尽管上面的过程并不是很复杂,但是通常情况下需要编写很多代码,而且为了显示错误信息,在网页中经常要使用多种不同的控制结构。创建表单验证虽简单,实施起来实在枯燥无味。 |
|||
|
|||
## 表单和验证 |
|||
对于开发者来说,一般开发过程都是相当复杂,而且大多是在重复一样的工作。假设一个场景项目中忽然需要增加一个表单数据,那么局部代码的整个流程都需要修改。我们知道Go里面struct是常用的一个数据结构,因此beego的form采用了struct来处理表单信息。 |
|||
|
|||
首先定义一个开发Web应用时相对应的struct,一个字段对应一个form元素,通过struct的tag来定义相应的元素信息和验证信息,如下所示: |
|||
```Go |
|||
|
|||
type User struct{ |
|||
Username string `form:text,valid:required` |
|||
Nickname string `form:text,valid:required` |
|||
Age int `form:text,valid:required|numeric` |
|||
Email string `form:text,valid:required|valid_email` |
|||
Introduce string `form:textarea` |
|||
} |
|||
``` |
|||
定义好struct之后接下来在controller中这样操作 |
|||
```Go |
|||
|
|||
func (this *AddController) Get() { |
|||
this.Data["form"] = beego.Form(&User{}) |
|||
this.Layout = "admin/layout.html" |
|||
this.TplNames = "admin/add.tpl" |
|||
} |
|||
``` |
|||
在模板中这样显示表单 |
|||
```html |
|||
|
|||
<h1>New Blog Post</h1> |
|||
<form action="" method="post"> |
|||
{{.form.render()}} |
|||
</form> |
|||
``` |
|||
上面我们定义好了整个的第一步,从struct到显示表单的过程,接下来就是用户填写信息,服务器端接收数据然后验证,最后插入数据库。 |
|||
```Go |
|||
|
|||
func (this *AddController) Post() { |
|||
var user User |
|||
form := this.GetInput(&user) |
|||
if !form.Validates() { |
|||
return |
|||
} |
|||
models.UserInsert(&user) |
|||
this.Ctx.Redirect(302, "/admin/index") |
|||
} |
|||
``` |
|||
## 表单类型 |
|||
以下列表列出来了对应的form元素信息: |
|||
<table cellpadding="0" cellspacing="1" border="0" style="width:100%" class="tableborder"> |
|||
<tbody><tr> |
|||
<th>名称</th> |
|||
<th>参数</th> |
|||
<th>功能描述</th> |
|||
</tr> |
|||
|
|||
<tr> |
|||
<td class="td"><strong>text</strong></td> |
|||
<td class="td">No</td> |
|||
<td class="td">textbox输入框</td> |
|||
</tr> |
|||
|
|||
<tr> |
|||
<td class="td"><strong>button</strong></td> |
|||
<td class="td">No</td> |
|||
<td class="td">按钮</td> |
|||
</tr> |
|||
|
|||
<tr> |
|||
<td class="td"><strong>checkbox</strong></td> |
|||
<td class="td">No</td> |
|||
<td class="td">多选择框</td> |
|||
</tr> |
|||
|
|||
<tr> |
|||
<td class="td"><strong>dropdown</strong></td> |
|||
<td class="td">No</td> |
|||
<td class="td">下拉选择框</td> |
|||
</tr> |
|||
|
|||
<tr> |
|||
<td class="td"><strong>file</strong></td> |
|||
<td class="td">No</td> |
|||
<td class="td">文件上传</td> |
|||
</tr> |
|||
|
|||
<tr> |
|||
<td class="td"><strong>hidden</strong></td> |
|||
<td class="td">No</td> |
|||
<td class="td">隐藏元素</td> |
|||
</tr> |
|||
|
|||
<tr> |
|||
<td class="td"><strong>password</strong></td> |
|||
<td class="td">No</td> |
|||
<td class="td">密码输入框</td> |
|||
</tr> |
|||
|
|||
<tr> |
|||
<td class="td"><strong>radio</strong></td> |
|||
<td class="td">No</td> |
|||
<td class="td">单选框</td> |
|||
</tr> |
|||
|
|||
<tr> |
|||
<td class="td"><strong>textarea</strong></td> |
|||
<td class="td">No</td> |
|||
<td class="td">文本输入框</td> |
|||
</tr> |
|||
|
|||
</tbody></table> |
|||
|
|||
|
|||
## 表单验证 |
|||
以下列表将列出可被使用的原生规则 |
|||
<table cellpadding="0" cellspacing="1" border="0" style="width:100%" class="tableborder"> |
|||
<tbody><tr> |
|||
<th>规则</th> |
|||
<th>参数</th> |
|||
<th>描述</th> |
|||
<th>举例</th> |
|||
</tr> |
|||
|
|||
<tr> |
|||
<td class="td"><strong>required</strong></td> |
|||
<td class="td">No</td> |
|||
<td class="td">如果元素为空,则返回FALSE</td> |
|||
<td class="td"> </td> |
|||
</tr> |
|||
|
|||
<tr> |
|||
<td class="td"><strong>matches</strong></td> |
|||
<td class="td">Yes</td> |
|||
<td class="td">如果表单元素的值与参数中对应的表单字段的值不相等,则返回FALSE</td> |
|||
<td class="td">matches[form_item]</td> |
|||
</tr> |
|||
|
|||
<tr> |
|||
<td class="td"><strong>is_unique</strong></td> |
|||
<td class="td">Yes</td> |
|||
<td class="td">如果表单元素的值与指定数据表栏位有重复,则返回False(译者注:比如is_unique[User.Email],那么验证类会去查找User表中Email栏位有没有与表单元素一样的值,如存重复,则返回false,这样开发者就不必另写Callback验证代码。)</td> |
|||
<td class="td">is_unique[table.field]</td> |
|||
</tr> |
|||
|
|||
<tr> |
|||
<td class="td"><strong>min_length</strong></td> |
|||
<td class="td">Yes</td> |
|||
<td class="td">如果表单元素值的字符长度少于参数中定义的数字,则返回FALSE</td> |
|||
<td class="td">min_length[6]</td> |
|||
</tr> |
|||
|
|||
<tr> |
|||
<td class="td"><strong>max_length</strong></td> |
|||
<td class="td">Yes</td> |
|||
<td class="td">如果表单元素值的字符长度大于参数中定义的数字,则返回FALSE</td> |
|||
<td class="td">max_length[12]</td> |
|||
</tr> |
|||
|
|||
<tr> |
|||
<td class="td"><strong>exact_length</strong></td> |
|||
<td class="td">Yes</td> |
|||
<td class="td">如果表单元素值的字符长度与参数中定义的数字不符,则返回FALSE</td> |
|||
<td class="td">exact_length[8]</td> |
|||
</tr> |
|||
|
|||
<tr> |
|||
<td class="td"><strong>greater_than</strong></td> |
|||
<td class="td">Yes</td> |
|||
<td class="td">如果表单元素值是非数字类型,或小于参数定义的值,则返回FALSE</td> |
|||
<td class="td">greater_than[8]</td> |
|||
</tr> |
|||
|
|||
<tr> |
|||
<td class="td"><strong>less_than</strong></td> |
|||
<td class="td">Yes</td> |
|||
<td class="td">如果表单元素值是非数字类型,或大于参数定义的值,则返回FALSE</td> |
|||
<td class="td">less_than[8]</td> |
|||
</tr> |
|||
|
|||
<tr> |
|||
<td class="td"><strong>alpha</strong></td> |
|||
<td class="td">No</td> |
|||
<td class="td">如果表单元素值中包含除字母以外的其他字符,则返回FALSE</td> |
|||
<td class="td"> </td> |
|||
</tr> |
|||
|
|||
<tr> |
|||
<td class="td"><strong>alpha_numeric</strong></td> |
|||
<td class="td">No</td> |
|||
<td class="td">如果表单元素值中包含除字母和数字以外的其他字符,则返回FALSE</td> |
|||
<td class="td"> </td> |
|||
</tr> |
|||
|
|||
<tr> |
|||
<td class="td"><strong>alpha_dash</strong></td> |
|||
<td class="td">No</td> |
|||
<td class="td">如果表单元素值中包含除字母/数字/下划线/破折号以外的其他字符,则返回FALSE</td> |
|||
<td class="td"> </td> |
|||
</tr> |
|||
|
|||
<tr> |
|||
<td class="td"><strong>numeric</strong></td> |
|||
<td class="td">No</td> |
|||
<td class="td">如果表单元素值中包含除数字以外的字符,则返回 FALSE</td> |
|||
<td class="td"> </td> |
|||
</tr> |
|||
|
|||
<tr> |
|||
<td class="td"><strong>integer</strong></td> |
|||
<td class="td">No</td> |
|||
<td class="td">如果表单元素中包含除整数以外的字符,则返回FALSE</td> |
|||
<td class="td"> </td> |
|||
</tr> |
|||
|
|||
<tr> |
|||
<td class="td"><strong>decimal</strong></td> |
|||
<td class="td">Yes</td> |
|||
<td class="td">如果表单元素中输入(非小数)不完整的值,则返回FALSE</td> |
|||
<td class="td"> </td> |
|||
</tr> |
|||
|
|||
<tr> |
|||
<td class="td"><strong>is_natural</strong></td> |
|||
<td class="td">No</td> |
|||
<td class="td">如果表单元素值中包含了非自然数的其他数值 (其他数值不包括零),则返回FALSE。自然数形如:0,1,2,3....等等。</td> |
|||
<td class="td"> </td> |
|||
</tr> |
|||
|
|||
<tr> |
|||
<td class="td"><strong>is_natural_no_zero</strong></td> |
|||
<td class="td">No</td> |
|||
<td class="td">如果表单元素值包含了非自然数的其他数值 (其他数值包括零),则返回FALSE。非零的自然数:1,2,3.....等等。</td> |
|||
<td class="td"> </td> |
|||
</tr> |
|||
|
|||
<tr> |
|||
<td class="td"><strong>valid_email</strong></td> |
|||
<td class="td">No</td> |
|||
<td class="td">如果表单元素值包含不合法的email地址,则返回FALSE</td> |
|||
<td class="td"> </td> |
|||
</tr> |
|||
|
|||
<tr> |
|||
<td class="td"><strong>valid_emails</strong></td> |
|||
<td class="td">No</td> |
|||
<td class="td">如果表单元素值中任何一个值包含不合法的email地址(地址之间用英文逗号分割),则返回FALSE。</td> |
|||
<td class="td"> </td> |
|||
</tr> |
|||
|
|||
<tr> |
|||
<td class="td"><strong>valid_ip</strong></td> |
|||
<td class="td">No</td> |
|||
<td class="td">如果表单元素的值不是一个合法的IP地址,则返回FALSE。</td> |
|||
<td class="td"> </td> |
|||
</tr> |
|||
|
|||
<tr> |
|||
<td class="td"><strong>valid_base64</strong></td> |
|||
<td class="td">No</td> |
|||
<td class="td">如果表单元素的值包含除了base64 编码字符之外的其他字符,则返回FALSE。</td> |
|||
<td class="td"> </td> |
|||
</tr> |
|||
|
|||
</tbody></table> |
|||
|
|||
|
|||
## links |
|||
* [目录](<preface.md>) |
|||
* 上一节: [Session支持](<14.2.md>) |
|||
* 下一节: [用户认证](<14.4.md>) |
|||
<!-- {% endraw %} --> |
After Width: | Height: | Size: 46 KiB |
After Width: | Height: | Size: 193 KiB |
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 31 KiB |
After Width: | Height: | Size: 97 KiB |
After Width: | Height: | Size: 78 KiB |
After Width: | Height: | Size: 65 KiB |
After Width: | Height: | Size: 47 KiB |
After Width: | Height: | Size: 83 KiB |
After Width: | Height: | Size: 54 KiB |
After Width: | Height: | Size: 37 KiB |
@ -0,0 +1 @@ |
|||
MsgBox"(第一行文字。)"&chr(10)&"(提示框第二行文字)",33,"(提示框标题)" |
@ -0,0 +1,4 @@ |
|||
E:\vivib\OneDrive\F |
|||
orOther\GoCode\xuexi |
|||
\读取文件\2019612005233.tx |
|||
t |
@ -0,0 +1,165 @@ |
|||
package hzb |
|||
|
|||
import ( |
|||
"database/sql" |
|||
"encoding/json" |
|||
_ "github.com/go-sql-driver/mysql" |
|||
"os" |
|||
) |
|||
|
|||
type A01 struct { |
|||
A0000 string `json:"a0000"` // 读取flag属性 A0000
|
|||
A0101 string `json:"a0101"` // 读取flag属性 A0101
|
|||
A0102 string `json:"a0102"` // 读取flag属性 A0102
|
|||
A0104 string `json:"a0104"` // 读取flag属性 A0104
|
|||
A0107 string `json:"a0107"` // 读取flag属性 A0107
|
|||
A0111A string `json:"a0111a"` // 读取flag属性 A0111A
|
|||
A0114A string `json:"a0114a"` // 读取flag属性 A0114A
|
|||
A0115A string `json:"a0115a"` // 读取flag属性 A0115A
|
|||
A0117 string `json:"a0117"` // 读取flag属性 A0117
|
|||
A0128 string `json:"a0128"` // 读取flag属性 A0128
|
|||
A0134 string `json:"a0134"` // 读取flag属性 A0134
|
|||
A0140 string `json:"a0140"` // 读取flag属性 A0140
|
|||
A0141 string `json:"a0141"` // 读取flag属性 A0141
|
|||
A0144 string `json:"a0144"` // 读取flag属性 A0144
|
|||
A3921 string `json:"a3921"` // 读取flag属性 A3921
|
|||
A3927 string `json:"a3927"` // 读取flag属性 A3927
|
|||
A0160 string `json:"a0160"` // 读取flag属性 A0160
|
|||
A0163 string `json:"a0163"` // 读取flag属性 A0163
|
|||
A0165 string `json:"a0165"` // 读取flag属性 A0165
|
|||
A0184 string `json:"a0184"` // 读取flag属性 A0184
|
|||
A0187A string `json:"a0187a"` // 读取flag属性 A0187A
|
|||
A0192 string `json:"a0192"` // 读取flag属性 A0192
|
|||
A0192A string `json:"a0192a"` // 读取flag属性 A0192A
|
|||
A0221 string `json:"a0221"` // 读取flag属性 A0221
|
|||
A0288 string `json:"a0288"` // 读取flag属性 A0288
|
|||
A0192D string `json:"a0192d"` // 读取flag属性 A0192D
|
|||
A0192C string `json:"a0192c"` // 读取flag属性 A0192C
|
|||
A0196 string `json:"a0196"` // 读取flag属性 A0196
|
|||
A0197 string `json:"a0197"` // 读取flag属性 A0197
|
|||
A0195 string `json:"a0195"` // 读取flag属性 A0195
|
|||
A1701 string `json:"a1701"` // 读取flag属性 A1701
|
|||
A14Z101 string `json:"a14z101"` // 读取flag属性 A14Z101
|
|||
A15Z101 string `json:"a15z101"` // 读取flag属性 A15Z101
|
|||
A0120 string `json:"a0120"` // 读取flag属性 A0120
|
|||
A0121 string `json:"a0121"` // 读取flag属性 A0121
|
|||
A2949 string `json:"a2949"` // 读取flag属性 A2949
|
|||
A0122 string `json:"a0122"` // 读取flag属性 A0122
|
|||
A0104A string `json:"a0104a"` // 读取flag属性 A0104A
|
|||
A0111 string `json:"a0111"` // 读取flag属性 A0111
|
|||
A0114 string `json:"a0114"` // 读取flag属性 A0114
|
|||
A0117A string `json:"a0117a"` // 读取flag属性 A0117A
|
|||
A0128B string `json:"a0128b"` // 读取flag属性 A0128B
|
|||
A0141D string `json:"a0141d"` // 读取flag属性 A0141D
|
|||
A0144B string `json:"a0144b"` // 读取flag属性 A0144B
|
|||
A0144C string `json:"a0144c"` // 读取flag属性 A0144C
|
|||
A0148 string `json:"a0148"` // 读取flag属性 A0148
|
|||
A0148C string `json:"a0148c"` // 读取flag属性 A0148C
|
|||
A0149 string `json:"a0149"` // 读取flag属性 A0149
|
|||
A0151 string `json:"a0151"` // 读取flag属性 A0151
|
|||
A0153 string `json:"a0153"` // 读取flag属性 A0153
|
|||
A0157 string `json:"a0157"` // 读取flag属性 A0157
|
|||
A0158 string `json:"a0158"` // 读取flag属性 A0158
|
|||
A0159 string `json:"a0159"` // 读取flag属性 A0159
|
|||
A015A string `json:"a015a"` // 读取flag属性 A015A
|
|||
A0161 string `json:"a0161"` // 读取flag属性 A0161
|
|||
A0162 string `json:"a0162"` // 读取flag属性 A0162
|
|||
A0180 string `json:"a0180"` // 读取flag属性 A0180
|
|||
A0191 string `json:"a0191"` // 读取flag属性 A0191
|
|||
A0192B string `json:"a0192b"` // 读取flag属性 A0192B
|
|||
A0193 string `json:"a0193"` // 读取flag属性 A0193
|
|||
A0194U string `json:"a0194u"` // 读取flag属性 A0194U
|
|||
A0198 string `json:"a0198"` // 读取flag属性 A0198
|
|||
A0199 string `json:"a0199"` // 读取flag属性 A0199
|
|||
A01K01 string `json:"a01k01"` // 读取flag属性 A01K01
|
|||
A01K02 string `json:"a01k02"` // 读取flag属性 A01K02
|
|||
CBDRESULT string `json:"cbdresult"` // 读取flag属性 CBDRESULT
|
|||
CBDW string `json:"cbdw"` // 读取flag属性 CBDW
|
|||
ISVALID string `json:"isvalid"` // 读取flag属性 ISVALID
|
|||
NL string `json:"nl"` // 读取flag属性 NL
|
|||
NMZW string `json:"nmzw"` // 读取flag属性 NMZW
|
|||
NRZW string `json:"nrzw"` // 读取flag属性 NRZW
|
|||
ORGID string `json:"orgid"` // 读取flag属性 ORGID
|
|||
QRZXL string `json:"qrzxl"` // 读取flag属性 QRZXL
|
|||
QRZXLXX string `json:"qrzxlxx"` // 读取flag属性 QRZXLXX
|
|||
QRZXW string `json:"qrzxw"` // 读取flag属性 QRZXW
|
|||
QRZXWXX string `json:"qrzxwxx"` // 读取flag属性 QRZXWXX
|
|||
RMLY string `json:"rmly"` // 读取flag属性 RMLY
|
|||
STATUS string `json:"status"` // 读取flag属性 STATUS
|
|||
TBR string `json:"tbr"` // 读取flag属性 TBR
|
|||
TBRJG string `json:"tbrjg"` // 读取flag属性 TBRJG
|
|||
USERLOG string `json:"userlog"` // 读取flag属性 USERLOG
|
|||
XGR string `json:"xgr"` // 读取flag属性 XGR
|
|||
ZZXL string `json:"zzxl"` // 读取flag属性 ZZXL
|
|||
ZZXLXX string `json:"zzxlxx"` // 读取flag属性 ZZXLXX
|
|||
ZZXW string `json:"zzxw"` // 读取flag属性 ZZXW
|
|||
ZZXWXX string `json:"zzxwxx"` // 读取flag属性 ZZXWXX
|
|||
A0155 string `json:"a0155"` // 读取flag属性 A0155
|
|||
AGE string `json:"age"` // 读取flag属性 AGE
|
|||
JSNLSJ string `json:"jsnlsj"` // 读取flag属性 JSNLSJ
|
|||
RESULTSORTID string `json:"resultsortid"` // 读取flag属性 RESULTSORTID
|
|||
TBSJ string `json:"tbsj"` // 读取flag属性 TBSJ
|
|||
XGSJ string `json:"xgsj"` // 读取flag属性 XGSJ
|
|||
SORTID string `json:"sortid"` // 读取flag属性 SORTID
|
|||
A0194 string `json:"a0194"` // 读取flag属性 A0194
|
|||
A0192E string `json:"a0192e"` // 读取flag属性 A0192E
|
|||
A0192F string `json:"a0192f"` // 读取flag属性 A0192F
|
|||
TORGID string `json:"torgid"` // 读取flag属性 TORGID
|
|||
TORDER string `json:"torder"` // 读取flag属性 TORDER
|
|||
ZGXL string `json:"zgxl"` // 读取flag属性 ZGXL
|
|||
ZGXLXX string `json:"zgxlxx"` // 读取flag属性 ZGXLXX
|
|||
ZGXW string `json:"zgxw"` // 读取flag属性 ZGXW
|
|||
ZGXWXX string `json:"zgxwxx"` // 读取flag属性 ZGXWXX
|
|||
TCSJSHOW string `json:"tcsjshow"` // 读取flag属性 TCSJSHOW
|
|||
TCFSSHOW string `json:"tcfsshow"` // 读取flag属性 TCFSSHOW
|
|||
FSJ string `json:"fsj"` // 读取flag属性 FSJ
|
|||
A0123 string `json:"a0123"` // 读取flag属性 A0123
|
|||
A15Z191 string `json:"a15z191"` // 读取flag属性 A15Z191
|
|||
A0192X string `json:"a0192x"` // 读取flag属性 A0192X
|
|||
|
|||
} |
|||
|
|||
func InsertA01(path, fileName string, db *sql.DB) { |
|||
db.Exec("truncate table A01;") |
|||
var sqlStr string = "INSERT INTO A01 (A0000,A0101,A0102,A0104,A0107,A0111A,A0114A,A0115A,A0117,A0128,A0134,A0140,A0141,A0144,A3921,A3927,A0160,A0163,A0165,A0184,A0187A,A0192,A0192A,A0221,A0288,A0192D,A0192C,A0196,A0197,A0195,A1701,A14Z101,A15Z101,A0120,A0121,A2949,A0122,A0104A,A0111,A0114,A0117A,A0128B,A0141D,A0144B,A0144C,A0148,A0148C,A0149,A0151,A0153,A0157,A0158,A0159,A015A,A0161,A0162,A0180,A0191,A0192B,A0193,A0194U,A0198,A0199,A01K01,A01K02,CBDRESULT,CBDW,ISVALID,NL,NMZW,NRZW,ORGID,QRZXL,QRZXLXX,QRZXW,QRZXWXX,RMLY,STATUS,TBR,TBRJG,USERLOG,XGR,ZZXL,ZZXLXX,ZZXW,ZZXWXX,A0155,AGE,JSNLSJ,RESULTSORTID,TBSJ,XGSJ,SORTID,A0194,A0192E,A0192F,TORGID,TORDER,ZGXL,ZGXLXX,ZGXW,ZGXWXX,TCSJSHOW,TCFSSHOW,FSJ,A0123,A15Z191,A0192X) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?);" |
|||
|
|||
pathStr := path + fileName |
|||
v := []A01{} |
|||
GetObjListMap(pathStr, &v) |
|||
stmt, err := db.Prepare(sqlStr) |
|||
if err != nil { |
|||
return |
|||
} |
|||
println("开始插入A01...") |
|||
for _, r := range v { |
|||
stmt.Exec(r.A0000, r.A0101, r.A0102, r.A0104, r.A0107, r.A0111A, r.A0114A, r.A0115A, r.A0117, r.A0128, r.A0134, r.A0140, r.A0141, r.A0144, r.A3921, r.A3927, r.A0160, r.A0163, r.A0165, r.A0184, r.A0187A, r.A0192, r.A0192A, r.A0221, r.A0288, r.A0192D, r.A0192C, r.A0196, r.A0197, r.A0195, r.A1701, r.A14Z101, r.A15Z101, r.A0120, r.A0121, r.A2949, r.A0122, r.A0104A, r.A0111, r.A0114, r.A0117A, r.A0128B, r.A0141D, r.A0144B, r.A0144C, r.A0148, r.A0148C, r.A0149, r.A0151, r.A0153, r.A0157, r.A0158, r.A0159, r.A015A, r.A0161, r.A0162, r.A0180, r.A0191, r.A0192B, r.A0193, r.A0194U, r.A0198, r.A0199, r.A01K01, r.A01K02, r.CBDRESULT, r.CBDW, r.ISVALID, r.NL, r.NMZW, r.NRZW, r.ORGID, r.QRZXL, r.QRZXLXX, r.QRZXW, r.QRZXWXX, r.RMLY, r.STATUS, r.TBR, r.TBRJG, r.USERLOG, r.XGR, r.ZZXL, r.ZZXLXX, r.ZZXW, r.ZZXWXX, r.A0155, r.AGE, r.JSNLSJ, r.RESULTSORTID, r.TBSJ, r.XGSJ, r.SORTID, r.A0194, r.A0192E, r.A0192F, r.TORGID, r.TORDER, r.ZGXL, r.ZGXLXX, r.ZGXW, r.ZGXWXX, r.TCSJSHOW, r.TCFSSHOW, r.FSJ, r.A0123, r.A15Z191, r.A0192X) |
|||
} |
|||
stmt.Close() |
|||
} |
|||
func QueryA01(sqlStr string, db *sql.DB) []A01 { |
|||
v := []A01{} |
|||
rows, err := db.Query(sqlStr) |
|||
if err != nil { |
|||
return nil |
|||
} |
|||
println("开始查询A01...") |
|||
for rows.Next() { |
|||
r := A01{} |
|||
err = rows.Scan(&r.A0000, &r.A0101, &r.A0102, &r.A0104, &r.A0107, &r.A0111A, &r.A0114A, &r.A0115A, &r.A0117, &r.A0128, &r.A0134, &r.A0140, &r.A0141, &r.A0144, &r.A3921, &r.A3927, &r.A0160, &r.A0163, &r.A0165, &r.A0184, &r.A0187A, &r.A0192, &r.A0192A, &r.A0221, &r.A0288, &r.A0192D, &r.A0192C, &r.A0196, &r.A0197, &r.A0195, &r.A1701, &r.A14Z101, &r.A15Z101, &r.A0120, &r.A0121, &r.A2949, &r.A0122, &r.A0104A, &r.A0111, &r.A0114, &r.A0117A, &r.A0128B, &r.A0141D, &r.A0144B, &r.A0144C, &r.A0148, &r.A0148C, &r.A0149, &r.A0151, &r.A0153, &r.A0157, &r.A0158, &r.A0159, &r.A015A, &r.A0161, &r.A0162, &r.A0180, &r.A0191, &r.A0192B, &r.A0193, &r.A0194U, &r.A0198, &r.A0199, &r.A01K01, &r.A01K02, &r.CBDRESULT, &r.CBDW, &r.ISVALID, &r.NL, &r.NMZW, &r.NRZW, &r.ORGID, &r.QRZXL, &r.QRZXLXX, &r.QRZXW, &r.QRZXWXX, &r.RMLY, &r.STATUS, &r.TBR, &r.TBRJG, &r.USERLOG, &r.XGR, &r.ZZXL, &r.ZZXLXX, &r.ZZXW, &r.ZZXWXX, &r.A0155, &r.AGE, &r.JSNLSJ, &r.RESULTSORTID, &r.TBSJ, &r.XGSJ, &r.SORTID, &r.A0194, &r.A0192E, &r.A0192F, &r.TORGID, &r.TORDER, &r.ZGXL, &r.ZGXLXX, &r.ZGXW, &r.ZGXWXX, &r.TCSJSHOW, &r.TCFSSHOW, &r.FSJ, &r.A0123, &r.A15Z191, &r.A0192X) |
|||
if err != nil { |
|||
println(err) |
|||
} |
|||
v = append(v, r) |
|||
} |
|||
|
|||
return v |
|||
} |
|||
func MakeA01Json(p, sqlA01 string, db *sql.DB) { |
|||
A01s := QueryA01(sqlA01, db) |
|||
if f, err := os.Create(p + "A01.json"); err == nil { |
|||
if s, err := json.Marshal(A01s); err == nil { |
|||
f.WriteString(string(s)) |
|||
f.Close() |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,117 @@ |
|||
package hzb |
|||
|
|||
import ( |
|||
"database/sql" |
|||
"encoding/json" |
|||
_ "github.com/go-sql-driver/mysql" |
|||
"os" |
|||
) |
|||
|
|||
type A02 struct { |
|||
A0000 string `json:"a0000"` // 读取flag属性 A0000
|
|||
A0200 string `json:"a0200"` // 读取flag属性 A0200
|
|||
A0201A string `json:"a0201a"` // 读取flag属性 A0201A
|
|||
A0201B string `json:"a0201b"` // 读取flag属性 A0201B
|
|||
A0201D string `json:"a0201d"` // 读取flag属性 A0201D
|
|||
A0201E string `json:"a0201e"` // 读取flag属性 A0201E
|
|||
A0215A string `json:"a0215a"` // 读取flag属性 A0215A
|
|||
A0219 string `json:"a0219"` // 读取flag属性 A0219
|
|||
A0223 string `json:"a0223"` // 读取flag属性 A0223
|
|||
A0225 string `json:"a0225"` // 读取flag属性 A0225
|
|||
A0243 string `json:"a0243"` // 读取flag属性 A0243
|
|||
A0245 string `json:"a0245"` // 读取flag属性 A0245
|
|||
A0247 string `json:"a0247"` // 读取flag属性 A0247
|
|||
A0251B string `json:"a0251b"` // 读取flag属性 A0251B
|
|||
A0255 string `json:"a0255"` // 读取flag属性 A0255
|
|||
A0265 string `json:"a0265"` // 读取flag属性 A0265
|
|||
A0267 string `json:"a0267"` // 读取flag属性 A0267
|
|||
A0272 string `json:"a0272"` // 读取flag属性 A0272
|
|||
A0281 string `json:"a0281"` // 读取flag属性 A0281
|
|||
A0221T string `json:"a0221t"` // 读取flag属性 A0221T
|
|||
B0238 string `json:"b0238"` // 读取flag属性 B0238
|
|||
B0239 string `json:"b0239"` // 读取flag属性 B0239
|
|||
A0221A string `json:"a0221a"` // 读取flag属性 A0221A
|
|||
WAGE_USED string `json:"wage_used"` // 读取flag属性 WAGE_USED
|
|||
UPDATED string `json:"updated"` // 读取flag属性 UPDATED
|
|||
A4907 string `json:"a4907"` // 读取flag属性 A4907
|
|||
A4904 string `json:"a4904"` // 读取flag属性 A4904
|
|||
A4901 string `json:"a4901"` // 读取flag属性 A4901
|
|||
A0299 string `json:"a0299"` // 读取flag属性 A0299
|
|||
A0295 string `json:"a0295"` // 读取flag属性 A0295
|
|||
A0289 string `json:"a0289"` // 读取flag属性 A0289
|
|||
A0288 string `json:"a0288"` // 读取flag属性 A0288
|
|||
A0284 string `json:"a0284"` // 读取flag属性 A0284
|
|||
A0277 string `json:"a0277"` // 读取flag属性 A0277
|
|||
A0271 string `json:"a0271"` // 读取flag属性 A0271
|
|||
A0259 string `json:"a0259"` // 读取flag属性 A0259
|
|||
A0256C string `json:"a0256c"` // 读取flag属性 A0256C
|
|||
A0256B string `json:"a0256b"` // 读取flag属性 A0256B
|
|||
A0256A string `json:"a0256a"` // 读取flag属性 A0256A
|
|||
A0256 string `json:"a0256"` // 读取flag属性 A0256
|
|||
A0251 string `json:"a0251"` // 读取flag属性 A0251
|
|||
A0229 string `json:"a0229"` // 读取flag属性 A0229
|
|||
A0222 string `json:"a0222"` // 读取flag属性 A0222
|
|||
A0221W string `json:"a0221w"` // 读取flag属性 A0221W
|
|||
A0221 string `json:"a0221"` // 读取flag属性 A0221
|
|||
A0219W string `json:"a0219w"` // 读取flag属性 A0219W
|
|||
A0216A string `json:"a0216a"` // 读取flag属性 A0216A
|
|||
A0215B string `json:"a0215b"` // 读取flag属性 A0215B
|
|||
A0209 string `json:"a0209"` // 读取flag属性 A0209
|
|||
A0207 string `json:"a0207"` // 读取flag属性 A0207
|
|||
A0204 string `json:"a0204"` // 读取flag属性 A0204
|
|||
A0201C string `json:"a0201c"` // 读取flag属性 A0201C
|
|||
A0201 string `json:"a0201"` // 读取flag属性 A0201
|
|||
A0279 string `json:"a0279"` // 读取flag属性 A0279
|
|||
V0201B string `json:"v0201b"` // 读取flag属性 V0201B
|
|||
A02001 string `json:"a02001"` // 读取flag属性 A02001
|
|||
A02002 string `json:"a02002"` // 读取flag属性 A02002
|
|||
A02003 string `json:"a02003"` // 读取flag属性 A02003
|
|||
A02004 string `json:"a02004"` // 读取flag属性 A02004
|
|||
A02005 string `json:"a02005"` // 读取flag属性 A02005
|
|||
|
|||
} |
|||
|
|||
func InsertA02(path, fileName string, db *sql.DB) { |
|||
db.Exec("truncate table A02;") |
|||
var sqlStr string = "INSERT INTO A02 (A0000,A0200,A0201A,A0201B,A0201D,A0201E,A0215A,A0219,A0223,A0225,A0243,A0245,A0247,A0251B,A0255,A0265,A0267,A0272,A0281,A0221T,B0238,B0239,A0221A,WAGE_USED,UPDATED,A4907,A4904,A4901,A0299,A0295,A0289,A0288,A0284,A0277,A0271,A0259,A0256C,A0256B,A0256A,A0256,A0251,A0229,A0222,A0221W,A0221,A0219W,A0216A,A0215B,A0209,A0207,A0204,A0201C,A0201,A0279,V0201B,A02001,A02002,A02003,A02004,A02005) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?);" |
|||
|
|||
pathStr := path + fileName |
|||
v := []A02{} |
|||
GetObjListMap(pathStr, &v) |
|||
stmt, err := db.Prepare(sqlStr) |
|||
if err != nil { |
|||
return |
|||
} |
|||
println("开始插入A02...") |
|||
for _, r := range v { |
|||
stmt.Exec(r.A0000, r.A0200, r.A0201A, r.A0201B, r.A0201D, r.A0201E, r.A0215A, r.A0219, r.A0223, r.A0225, r.A0243, r.A0245, r.A0247, r.A0251B, r.A0255, r.A0265, r.A0267, r.A0272, r.A0281, r.A0221T, r.B0238, r.B0239, r.A0221A, r.WAGE_USED, r.UPDATED, r.A4907, r.A4904, r.A4901, r.A0299, r.A0295, r.A0289, r.A0288, r.A0284, r.A0277, r.A0271, r.A0259, r.A0256C, r.A0256B, r.A0256A, r.A0256, r.A0251, r.A0229, r.A0222, r.A0221W, r.A0221, r.A0219W, r.A0216A, r.A0215B, r.A0209, r.A0207, r.A0204, r.A0201C, r.A0201, r.A0279, r.V0201B, r.A02001, r.A02002, r.A02003, r.A02004, r.A02005) |
|||
} |
|||
stmt.Close() |
|||
} |
|||
func QueryA02(sqlStr string, db *sql.DB) []A02 { |
|||
v := []A02{} |
|||
rows, err := db.Query(sqlStr) |
|||
if err != nil { |
|||
return nil |
|||
} |
|||
println("开始查询A02...") |
|||
for rows.Next() { |
|||
r := A02{} |
|||
err = rows.Scan(&r.A0000, &r.A0200, &r.A0201A, &r.A0201B, &r.A0201D, &r.A0201E, &r.A0215A, &r.A0219, &r.A0223, &r.A0225, &r.A0243, &r.A0245, &r.A0247, &r.A0251B, &r.A0255, &r.A0265, &r.A0267, &r.A0272, &r.A0281, &r.A0221T, &r.B0238, &r.B0239, &r.A0221A, &r.WAGE_USED, &r.UPDATED, &r.A4907, &r.A4904, &r.A4901, &r.A0299, &r.A0295, &r.A0289, &r.A0288, &r.A0284, &r.A0277, &r.A0271, &r.A0259, &r.A0256C, &r.A0256B, &r.A0256A, &r.A0256, &r.A0251, &r.A0229, &r.A0222, &r.A0221W, &r.A0221, &r.A0219W, &r.A0216A, &r.A0215B, &r.A0209, &r.A0207, &r.A0204, &r.A0201C, &r.A0201, &r.A0279, &r.V0201B, &r.A02001, &r.A02002, &r.A02003, &r.A02004, &r.A02005) |
|||
if err != nil { |
|||
println(err) |
|||
} |
|||
v = append(v, r) |
|||
} |
|||
|
|||
return v |
|||
} |
|||
func MakeA02Json(p, sqlA02 string, db *sql.DB) { |
|||
A02s := QueryA02(sqlA02, db) |
|||
if f, err := os.Create(p + "A02.json"); err == nil { |
|||
if s, err := json.Marshal(A02s); err == nil { |
|||
f.WriteString(string(s)) |
|||
f.Close() |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,70 @@ |
|||
package hzb |
|||
|
|||
import ( |
|||
"database/sql" |
|||
"encoding/json" |
|||
_ "github.com/go-sql-driver/mysql" |
|||
"os" |
|||
) |
|||
|
|||
type A05 struct { |
|||
A0000 string `json:"a0000"` // 读取flag属性 A0000
|
|||
A0500 string `json:"a0500"` // 读取flag属性 A0500
|
|||
A0531 string `json:"a0531"` // 读取flag属性 A0531
|
|||
A0501B string `json:"a0501b"` // 读取flag属性 A0501B
|
|||
A0504 string `json:"a0504"` // 读取flag属性 A0504
|
|||
A0511 string `json:"a0511"` // 读取flag属性 A0511
|
|||
A0517 string `json:"a0517"` // 读取flag属性 A0517
|
|||
A0524 string `json:"a0524"` // 读取flag属性 A0524
|
|||
A0525 string `json:"a0525"` // 读取flag属性 A0525
|
|||
A0526 string `json:"a0526"` // 读取flag属性 A0526
|
|||
A0528B string `json:"a0528b"` // 读取flag属性 A0528B
|
|||
A0530 string `json:"a0530"` // 读取flag属性 A0530
|
|||
A0532 string `json:"a0532"` // 读取flag属性 A0532
|
|||
|
|||
} |
|||
|
|||
func InsertA05(path, fileName string, db *sql.DB) { |
|||
db.Exec("truncate table A05;") |
|||
var sqlStr string = "INSERT INTO A05 (A0000,A0500,A0531,A0501B,A0504,A0511,A0517,A0524,A0525,A0526,A0528B,A0530,A0532) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?);" |
|||
|
|||
pathStr := path + fileName |
|||
v := []A05{} |
|||
GetObjListMap(pathStr, &v) |
|||
stmt, err := db.Prepare(sqlStr) |
|||
if err != nil { |
|||
return |
|||
} |
|||
println("开始插入A05...") |
|||
for _, r := range v { |
|||
stmt.Exec(r.A0000, r.A0500, r.A0531, r.A0501B, r.A0504, r.A0511, r.A0517, r.A0524, r.A0525, r.A0526, r.A0528B, r.A0530, r.A0532) |
|||
} |
|||
stmt.Close() |
|||
} |
|||
func QueryA05(sqlStr string, db *sql.DB) []A05 { |
|||
v := []A05{} |
|||
rows, err := db.Query(sqlStr) |
|||
if err != nil { |
|||
return nil |
|||
} |
|||
println("开始查询A05...") |
|||
for rows.Next() { |
|||
r := A05{} |
|||
err = rows.Scan(&r.A0000, &r.A0500, &r.A0531, &r.A0501B, &r.A0504, &r.A0511, &r.A0517, &r.A0524, &r.A0525, &r.A0526, &r.A0528B, &r.A0530, &r.A0532) |
|||
if err != nil { |
|||
println(err) |
|||
} |
|||
v = append(v, r) |
|||
} |
|||
|
|||
return v |
|||
} |
|||
func MakeA05Json(p, sqlA05 string, db *sql.DB) { |
|||
A05s := QueryA05(sqlA05, db) |
|||
if f, err := os.Create(p + "A05.json"); err == nil { |
|||
if s, err := json.Marshal(A05s); err == nil { |
|||
f.WriteString(string(s)) |
|||
f.Close() |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,178 @@ |
|||
package hzb |
|||
|
|||
import ( |
|||
"database/sql" |
|||
"encoding/xml" |
|||
_ "github.com/go-sql-driver/mysql" |
|||
"os" |
|||
) |
|||
|
|||
type A01Table struct { |
|||
XMLName xml.Name `xml:"xml"` // 指定最外层的标签为config
|
|||
DATA A01SData `xml:"data"` // 读取smtpServer配置项,并将结果保存到SmtpServer变量中
|
|||
} |
|||
type A01SData struct { |
|||
Row []A01 `xml:"row"` // 读取receivers标签下的内容,以结构方式获取
|
|||
} |
|||
type A01 struct { |
|||
A0000 string `xml:"A0000,attr"` // 读取flag属性 A0000
|
|||
A0101 string `xml:"A0101,attr"` // 读取flag属性 A0101
|
|||
A0102 string `xml:"A0102,attr"` // 读取flag属性 A0102
|
|||
A0104 string `xml:"A0104,attr"` // 读取flag属性 A0104
|
|||
A0107 string `xml:"A0107,attr"` // 读取flag属性 A0107
|
|||
A0111A string `xml:"A0111A,attr"` // 读取flag属性 A0111A
|
|||
A0114A string `xml:"A0114A,attr"` // 读取flag属性 A0114A
|
|||
A0115A string `xml:"A0115A,attr"` // 读取flag属性 A0115A
|
|||
A0117 string `xml:"A0117,attr"` // 读取flag属性 A0117
|
|||
A0128 string `xml:"A0128,attr"` // 读取flag属性 A0128
|
|||
A0134 string `xml:"A0134,attr"` // 读取flag属性 A0134
|
|||
A0140 string `xml:"A0140,attr"` // 读取flag属性 A0140
|
|||
A0141 string `xml:"A0141,attr"` // 读取flag属性 A0141
|
|||
A0144 string `xml:"A0144,attr"` // 读取flag属性 A0144
|
|||
A3921 string `xml:"A3921,attr"` // 读取flag属性 A3921
|
|||
A3927 string `xml:"A3927,attr"` // 读取flag属性 A3927
|
|||
A0160 string `xml:"A0160,attr"` // 读取flag属性 A0160
|
|||
A0163 string `xml:"A0163,attr"` // 读取flag属性 A0163
|
|||
A0165 string `xml:"A0165,attr"` // 读取flag属性 A0165
|
|||
A0184 string `xml:"A0184,attr"` // 读取flag属性 A0184
|
|||
A0187A string `xml:"A0187A,attr"` // 读取flag属性 A0187A
|
|||
A0192 string `xml:"A0192,attr"` // 读取flag属性 A0192
|
|||
A0192A string `xml:"A0192A,attr"` // 读取flag属性 A0192A
|
|||
A0221 string `xml:"A0221,attr"` // 读取flag属性 A0221
|
|||
A0288 string `xml:"A0288,attr"` // 读取flag属性 A0288
|
|||
A0192D string `xml:"A0192D,attr"` // 读取flag属性 A0192D
|
|||
A0192C string `xml:"A0192C,attr"` // 读取flag属性 A0192C
|
|||
A0196 string `xml:"A0196,attr"` // 读取flag属性 A0196
|
|||
A0197 string `xml:"A0197,attr"` // 读取flag属性 A0197
|
|||
A0195 string `xml:"A0195,attr"` // 读取flag属性 A0195
|
|||
A1701 string `xml:"A1701,attr"` // 读取flag属性 A1701
|
|||
A14Z101 string `xml:"A14Z101,attr"` // 读取flag属性 A14Z101
|
|||
A15Z101 string `xml:"A15Z101,attr"` // 读取flag属性 A15Z101
|
|||
A0120 string `xml:"A0120,attr"` // 读取flag属性 A0120
|
|||
A0121 string `xml:"A0121,attr"` // 读取flag属性 A0121
|
|||
A2949 string `xml:"A2949,attr"` // 读取flag属性 A2949
|
|||
A0122 string `xml:"A0122,attr"` // 读取flag属性 A0122
|
|||
A0104A string `xml:"A0104A,attr"` // 读取flag属性 A0104A
|
|||
A0111 string `xml:"A0111,attr"` // 读取flag属性 A0111
|
|||
A0114 string `xml:"A0114,attr"` // 读取flag属性 A0114
|
|||
A0117A string `xml:"A0117A,attr"` // 读取flag属性 A0117A
|
|||
A0128B string `xml:"A0128B,attr"` // 读取flag属性 A0128B
|
|||
A0141D string `xml:"A0141D,attr"` // 读取flag属性 A0141D
|
|||
A0144B string `xml:"A0144B,attr"` // 读取flag属性 A0144B
|
|||
A0144C string `xml:"A0144C,attr"` // 读取flag属性 A0144C
|
|||
A0148 string `xml:"A0148,attr"` // 读取flag属性 A0148
|
|||
A0148C string `xml:"A0148C,attr"` // 读取flag属性 A0148C
|
|||
A0149 string `xml:"A0149,attr"` // 读取flag属性 A0149
|
|||
A0151 string `xml:"A0151,attr"` // 读取flag属性 A0151
|
|||
A0153 string `xml:"A0153,attr"` // 读取flag属性 A0153
|
|||
A0157 string `xml:"A0157,attr"` // 读取flag属性 A0157
|
|||
A0158 string `xml:"A0158,attr"` // 读取flag属性 A0158
|
|||
A0159 string `xml:"A0159,attr"` // 读取flag属性 A0159
|
|||
A015A string `xml:"A015A,attr"` // 读取flag属性 A015A
|
|||
A0161 string `xml:"A0161,attr"` // 读取flag属性 A0161
|
|||
A0162 string `xml:"A0162,attr"` // 读取flag属性 A0162
|
|||
A0180 string `xml:"A0180,attr"` // 读取flag属性 A0180
|
|||
A0191 string `xml:"A0191,attr"` // 读取flag属性 A0191
|
|||
A0192B string `xml:"A0192B,attr"` // 读取flag属性 A0192B
|
|||
A0193 string `xml:"A0193,attr"` // 读取flag属性 A0193
|
|||
A0194U string `xml:"A0194U,attr"` // 读取flag属性 A0194U
|
|||
A0198 string `xml:"A0198,attr"` // 读取flag属性 A0198
|
|||
A0199 string `xml:"A0199,attr"` // 读取flag属性 A0199
|
|||
A01K01 string `xml:"A01K01,attr"` // 读取flag属性 A01K01
|
|||
A01K02 string `xml:"A01K02,attr"` // 读取flag属性 A01K02
|
|||
CBDRESULT string `xml:"CBDRESULT,attr"` // 读取flag属性 CBDRESULT
|
|||
CBDW string `xml:"CBDW,attr"` // 读取flag属性 CBDW
|
|||
ISVALID string `xml:"ISVALID,attr"` // 读取flag属性 ISVALID
|
|||
NL string `xml:"NL,attr"` // 读取flag属性 NL
|
|||
NMZW string `xml:"NMZW,attr"` // 读取flag属性 NMZW
|
|||
NRZW string `xml:"NRZW,attr"` // 读取flag属性 NRZW
|
|||
ORGID string `xml:"ORGID,attr"` // 读取flag属性 ORGID
|
|||
QRZXL string `xml:"QRZXL,attr"` // 读取flag属性 QRZXL
|
|||
QRZXLXX string `xml:"QRZXLXX,attr"` // 读取flag属性 QRZXLXX
|
|||
QRZXW string `xml:"QRZXW,attr"` // 读取flag属性 QRZXW
|
|||
QRZXWXX string `xml:"QRZXWXX,attr"` // 读取flag属性 QRZXWXX
|
|||
RMLY string `xml:"RMLY,attr"` // 读取flag属性 RMLY
|
|||
STATUS string `xml:"STATUS,attr"` // 读取flag属性 STATUS
|
|||
TBR string `xml:"TBR,attr"` // 读取flag属性 TBR
|
|||
TBRJG string `xml:"TBRJG,attr"` // 读取flag属性 TBRJG
|
|||
USERLOG string `xml:"USERLOG,attr"` // 读取flag属性 USERLOG
|
|||
XGR string `xml:"XGR,attr"` // 读取flag属性 XGR
|
|||
ZZXL string `xml:"ZZXL,attr"` // 读取flag属性 ZZXL
|
|||
ZZXLXX string `xml:"ZZXLXX,attr"` // 读取flag属性 ZZXLXX
|
|||
ZZXW string `xml:"ZZXW,attr"` // 读取flag属性 ZZXW
|
|||
ZZXWXX string `xml:"ZZXWXX,attr"` // 读取flag属性 ZZXWXX
|
|||
A0155 string `xml:"A0155,attr"` // 读取flag属性 A0155
|
|||
AGE string `xml:"AGE,attr"` // 读取flag属性 AGE
|
|||
JSNLSJ string `xml:"JSNLSJ,attr"` // 读取flag属性 JSNLSJ
|
|||
RESULTSORTID string `xml:"RESULTSORTID,attr"` // 读取flag属性 RESULTSORTID
|
|||
TBSJ string `xml:"TBSJ,attr"` // 读取flag属性 TBSJ
|
|||
XGSJ string `xml:"XGSJ,attr"` // 读取flag属性 XGSJ
|
|||
SORTID string `xml:"SORTID,attr"` // 读取flag属性 SORTID
|
|||
A0194 string `xml:"A0194,attr"` // 读取flag属性 A0194
|
|||
A0192E string `xml:"A0192E,attr"` // 读取flag属性 A0192E
|
|||
A0192F string `xml:"A0192F,attr"` // 读取flag属性 A0192F
|
|||
TORGID string `xml:"TORGID,attr"` // 读取flag属性 TORGID
|
|||
TORDER string `xml:"TORDER,attr"` // 读取flag属性 TORDER
|
|||
ZGXL string `xml:"ZGXL,attr"` // 读取flag属性 ZGXL
|
|||
ZGXLXX string `xml:"ZGXLXX,attr"` // 读取flag属性 ZGXLXX
|
|||
ZGXW string `xml:"ZGXW,attr"` // 读取flag属性 ZGXW
|
|||
ZGXWXX string `xml:"ZGXWXX,attr"` // 读取flag属性 ZGXWXX
|
|||
TCSJSHOW string `xml:"TCSJSHOW,attr"` // 读取flag属性 TCSJSHOW
|
|||
TCFSSHOW string `xml:"TCFSSHOW,attr"` // 读取flag属性 TCFSSHOW
|
|||
FSJ string `xml:"FSJ,attr"` // 读取flag属性 FSJ
|
|||
A0123 string `xml:"A0123,attr"` // 读取flag属性 A0123
|
|||
A15Z191 string `xml:"A15Z191,attr"` // 读取flag属性 A15Z191
|
|||
A0192X string `xml:"A0192X,attr"` // 读取flag属性 A0192X
|
|||
|
|||
} |
|||
|
|||
func InsertA01(path, fileName string, db *sql.DB) { |
|||
db.Exec("truncate table A01;") |
|||
var sqlStr string = "INSERT INTO A01 (A0000,A0101,A0102,A0104,A0107,A0111A,A0114A,A0115A,A0117,A0128,A0134,A0140,A0141,A0144,A3921,A3927,A0160,A0163,A0165,A0184,A0187A,A0192,A0192A,A0221,A0288,A0192D,A0192C,A0196,A0197,A0195,A1701,A14Z101,A15Z101,A0120,A0121,A2949,A0122,A0104A,A0111,A0114,A0117A,A0128B,A0141D,A0144B,A0144C,A0148,A0148C,A0149,A0151,A0153,A0157,A0158,A0159,A015A,A0161,A0162,A0180,A0191,A0192B,A0193,A0194U,A0198,A0199,A01K01,A01K02,CBDRESULT,CBDW,ISVALID,NL,NMZW,NRZW,ORGID,QRZXL,QRZXLXX,QRZXW,QRZXWXX,RMLY,STATUS,TBR,TBRJG,USERLOG,XGR,ZZXL,ZZXLXX,ZZXW,ZZXWXX,A0155,AGE,JSNLSJ,RESULTSORTID,TBSJ,XGSJ,SORTID,A0194,A0192E,A0192F,TORGID,TORDER,ZGXL,ZGXLXX,ZGXW,ZGXWXX,TCSJSHOW,TCFSSHOW,FSJ,A0123,A15Z191,A0192X) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?);" |
|||
|
|||
pathStr := path + fileName |
|||
v := A01Table{} |
|||
GetObjListMap(pathStr, &v) |
|||
c := v.DATA |
|||
stmt, err := db.Prepare(sqlStr) |
|||
if err != nil { |
|||
return |
|||
} |
|||
println("开始执行A01...") |
|||
for _, r := range c.Row { |
|||
stmt.Exec(r.A0000, r.A0101, r.A0102, r.A0104, r.A0107, r.A0111A, r.A0114A, r.A0115A, r.A0117, r.A0128, r.A0134, r.A0140, r.A0141, r.A0144, r.A3921, r.A3927, r.A0160, r.A0163, r.A0165, r.A0184, r.A0187A, r.A0192, r.A0192A, r.A0221, r.A0288, r.A0192D, r.A0192C, r.A0196, r.A0197, r.A0195, r.A1701, r.A14Z101, r.A15Z101, r.A0120, r.A0121, r.A2949, r.A0122, r.A0104A, r.A0111, r.A0114, r.A0117A, r.A0128B, r.A0141D, r.A0144B, r.A0144C, r.A0148, r.A0148C, r.A0149, r.A0151, r.A0153, r.A0157, r.A0158, r.A0159, r.A015A, r.A0161, r.A0162, r.A0180, r.A0191, r.A0192B, r.A0193, r.A0194U, r.A0198, r.A0199, r.A01K01, r.A01K02, r.CBDRESULT, r.CBDW, r.ISVALID, r.NL, r.NMZW, r.NRZW, r.ORGID, r.QRZXL, r.QRZXLXX, r.QRZXW, r.QRZXWXX, r.RMLY, r.STATUS, r.TBR, r.TBRJG, r.USERLOG, r.XGR, r.ZZXL, r.ZZXLXX, r.ZZXW, r.ZZXWXX, r.A0155, r.AGE, r.JSNLSJ, r.RESULTSORTID, r.TBSJ, r.XGSJ, r.SORTID, r.A0194, r.A0192E, r.A0192F, r.TORGID, r.TORDER, r.ZGXL, r.ZGXLXX, r.ZGXW, r.ZGXWXX, r.TCSJSHOW, r.TCFSSHOW, r.FSJ, r.A0123, r.A15Z191, r.A0192X) |
|||
} |
|||
stmt.Close() |
|||
} |
|||
func QueryA01(sqlStr string, db *sql.DB) []A01 { |
|||
v := []A01{} |
|||
rows, err := db.Query(sqlStr) |
|||
if err != nil { |
|||
return nil |
|||
} |
|||
println("开始查询A01...") |
|||
for rows.Next() { |
|||
r := A01{} |
|||
err = rows.Scan(&r.A0000, &r.A0101, &r.A0102, &r.A0104, &r.A0107, &r.A0111A, &r.A0114A, &r.A0115A, &r.A0117, &r.A0128, &r.A0134, &r.A0140, &r.A0141, &r.A0144, &r.A3921, &r.A3927, &r.A0160, &r.A0163, &r.A0165, &r.A0184, &r.A0187A, &r.A0192, &r.A0192A, &r.A0221, &r.A0288, &r.A0192D, &r.A0192C, &r.A0196, &r.A0197, &r.A0195, &r.A1701, &r.A14Z101, &r.A15Z101, &r.A0120, &r.A0121, &r.A2949, &r.A0122, &r.A0104A, &r.A0111, &r.A0114, &r.A0117A, &r.A0128B, &r.A0141D, &r.A0144B, &r.A0144C, &r.A0148, &r.A0148C, &r.A0149, &r.A0151, &r.A0153, &r.A0157, &r.A0158, &r.A0159, &r.A015A, &r.A0161, &r.A0162, &r.A0180, &r.A0191, &r.A0192B, &r.A0193, &r.A0194U, &r.A0198, &r.A0199, &r.A01K01, &r.A01K02, &r.CBDRESULT, &r.CBDW, &r.ISVALID, &r.NL, &r.NMZW, &r.NRZW, &r.ORGID, &r.QRZXL, &r.QRZXLXX, &r.QRZXW, &r.QRZXWXX, &r.RMLY, &r.STATUS, &r.TBR, &r.TBRJG, &r.USERLOG, &r.XGR, &r.ZZXL, &r.ZZXLXX, &r.ZZXW, &r.ZZXWXX, &r.A0155, &r.AGE, &r.JSNLSJ, &r.RESULTSORTID, &r.TBSJ, &r.XGSJ, &r.SORTID, &r.A0194, &r.A0192E, &r.A0192F, &r.TORGID, &r.TORDER, &r.ZGXL, &r.ZGXLXX, &r.ZGXW, &r.ZGXWXX, &r.TCSJSHOW, &r.TCFSSHOW, &r.FSJ, &r.A0123, &r.A15Z191, &r.A0192X) |
|||
if err != nil { |
|||
println(err) |
|||
} |
|||
v = append(v, r) |
|||
} |
|||
|
|||
return v |
|||
} |
|||
func MakeA01Xml(p, sql string, db *sql.DB) { |
|||
|
|||
as := QueryA01(sql, db) |
|||
a := A01SData{} |
|||
a.Row = as |
|||
b := A01Table{} |
|||
b.DATA = a |
|||
if f, err := os.Create(p + "A01.xml"); err == nil { |
|||
if s, err := xml.Marshal(b); err == nil { |
|||
f.WriteString(string(s)) |
|||
f.Close() |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,130 @@ |
|||
package hzb |
|||
|
|||
import ( |
|||
"database/sql" |
|||
"encoding/xml" |
|||
_ "github.com/go-sql-driver/mysql" |
|||
"os" |
|||
) |
|||
|
|||
type A02Table struct { |
|||
XMLName xml.Name `xml:"xml"` // 指定最外层的标签为config
|
|||
DATA A02SData `xml:"data"` // 读取smtpServer配置项,并将结果保存到SmtpServer变量中
|
|||
} |
|||
type A02SData struct { |
|||
Row []A02 `xml:"row"` // 读取receivers标签下的内容,以结构方式获取
|
|||
} |
|||
type A02 struct { |
|||
A0000 string `xml:"A0000,attr"` // 读取flag属性 A0000
|
|||
A0200 string `xml:"A0200,attr"` // 读取flag属性 A0200
|
|||
A0201A string `xml:"A0201A,attr"` // 读取flag属性 A0201A
|
|||
A0201B string `xml:"A0201B,attr"` // 读取flag属性 A0201B
|
|||
A0201D string `xml:"A0201D,attr"` // 读取flag属性 A0201D
|
|||
A0201E string `xml:"A0201E,attr"` // 读取flag属性 A0201E
|
|||
A0215A string `xml:"A0215A,attr"` // 读取flag属性 A0215A
|
|||
A0219 string `xml:"A0219,attr"` // 读取flag属性 A0219
|
|||
A0223 string `xml:"A0223,attr"` // 读取flag属性 A0223
|
|||
A0225 string `xml:"A0225,attr"` // 读取flag属性 A0225
|
|||
A0243 string `xml:"A0243,attr"` // 读取flag属性 A0243
|
|||
A0245 string `xml:"A0245,attr"` // 读取flag属性 A0245
|
|||
A0247 string `xml:"A0247,attr"` // 读取flag属性 A0247
|
|||
A0251B string `xml:"A0251B,attr"` // 读取flag属性 A0251B
|
|||
A0255 string `xml:"A0255,attr"` // 读取flag属性 A0255
|
|||
A0265 string `xml:"A0265,attr"` // 读取flag属性 A0265
|
|||
A0267 string `xml:"A0267,attr"` // 读取flag属性 A0267
|
|||
A0272 string `xml:"A0272,attr"` // 读取flag属性 A0272
|
|||
A0281 string `xml:"A0281,attr"` // 读取flag属性 A0281
|
|||
A0221T string `xml:"A0221T,attr"` // 读取flag属性 A0221T
|
|||
B0238 string `xml:"B0238,attr"` // 读取flag属性 B0238
|
|||
B0239 string `xml:"B0239,attr"` // 读取flag属性 B0239
|
|||
A0221A string `xml:"A0221A,attr"` // 读取flag属性 A0221A
|
|||
WAGE_USED string `xml:"WAGE_USED,attr"` // 读取flag属性 WAGE_USED
|
|||
UPDATED string `xml:"UPDATED,attr"` // 读取flag属性 UPDATED
|
|||
A4907 string `xml:"A4907,attr"` // 读取flag属性 A4907
|
|||
A4904 string `xml:"A4904,attr"` // 读取flag属性 A4904
|
|||
A4901 string `xml:"A4901,attr"` // 读取flag属性 A4901
|
|||
A0299 string `xml:"A0299,attr"` // 读取flag属性 A0299
|
|||
A0295 string `xml:"A0295,attr"` // 读取flag属性 A0295
|
|||
A0289 string `xml:"A0289,attr"` // 读取flag属性 A0289
|
|||
A0288 string `xml:"A0288,attr"` // 读取flag属性 A0288
|
|||
A0284 string `xml:"A0284,attr"` // 读取flag属性 A0284
|
|||
A0277 string `xml:"A0277,attr"` // 读取flag属性 A0277
|
|||
A0271 string `xml:"A0271,attr"` // 读取flag属性 A0271
|
|||
A0259 string `xml:"A0259,attr"` // 读取flag属性 A0259
|
|||
A0256C string `xml:"A0256C,attr"` // 读取flag属性 A0256C
|
|||
A0256B string `xml:"A0256B,attr"` // 读取flag属性 A0256B
|
|||
A0256A string `xml:"A0256A,attr"` // 读取flag属性 A0256A
|
|||
A0256 string `xml:"A0256,attr"` // 读取flag属性 A0256
|
|||
A0251 string `xml:"A0251,attr"` // 读取flag属性 A0251
|
|||
A0229 string `xml:"A0229,attr"` // 读取flag属性 A0229
|
|||
A0222 string `xml:"A0222,attr"` // 读取flag属性 A0222
|
|||
A0221W string `xml:"A0221W,attr"` // 读取flag属性 A0221W
|
|||
A0221 string `xml:"A0221,attr"` // 读取flag属性 A0221
|
|||
A0219W string `xml:"A0219W,attr"` // 读取flag属性 A0219W
|
|||
A0216A string `xml:"A0216A,attr"` // 读取flag属性 A0216A
|
|||
A0215B string `xml:"A0215B,attr"` // 读取flag属性 A0215B
|
|||
A0209 string `xml:"A0209,attr"` // 读取flag属性 A0209
|
|||
A0207 string `xml:"A0207,attr"` // 读取flag属性 A0207
|
|||
A0204 string `xml:"A0204,attr"` // 读取flag属性 A0204
|
|||
A0201C string `xml:"A0201C,attr"` // 读取flag属性 A0201C
|
|||
A0201 string `xml:"A0201,attr"` // 读取flag属性 A0201
|
|||
A0279 string `xml:"A0279,attr"` // 读取flag属性 A0279
|
|||
V0201B string `xml:"V0201B,attr"` // 读取flag属性 V0201B
|
|||
A02001 string `xml:"A02001,attr"` // 读取flag属性 A02001
|
|||
A02002 string `xml:"A02002,attr"` // 读取flag属性 A02002
|
|||
A02003 string `xml:"A02003,attr"` // 读取flag属性 A02003
|
|||
A02004 string `xml:"A02004,attr"` // 读取flag属性 A02004
|
|||
A02005 string `xml:"A02005,attr"` // 读取flag属性 A02005
|
|||
|
|||
} |
|||
|
|||
func InsertA02(path, fileName string, db *sql.DB) { |
|||
db.Exec("truncate table A02;") |
|||
var sqlStr string = "INSERT INTO A02 (A0000,A0200,A0201A,A0201B,A0201D,A0201E,A0215A,A0219,A0223,A0225,A0243,A0245,A0247,A0251B,A0255,A0265,A0267,A0272,A0281,A0221T,B0238,B0239,A0221A,WAGE_USED,UPDATED,A4907,A4904,A4901,A0299,A0295,A0289,A0288,A0284,A0277,A0271,A0259,A0256C,A0256B,A0256A,A0256,A0251,A0229,A0222,A0221W,A0221,A0219W,A0216A,A0215B,A0209,A0207,A0204,A0201C,A0201,A0279,V0201B,A02001,A02002,A02003,A02004,A02005) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?);" |
|||
|
|||
pathStr := path + fileName |
|||
v := A02Table{} |
|||
GetObjListMap(pathStr, &v) |
|||
c := v.DATA |
|||
stmt, err := db.Prepare(sqlStr) |
|||
if err != nil { |
|||
return |
|||
} |
|||
println("开始执行A02...") |
|||
for _, r := range c.Row { |
|||
stmt.Exec(r.A0000, r.A0200, r.A0201A, r.A0201B, r.A0201D, r.A0201E, r.A0215A, r.A0219, r.A0223, r.A0225, r.A0243, r.A0245, r.A0247, r.A0251B, r.A0255, r.A0265, r.A0267, r.A0272, r.A0281, r.A0221T, r.B0238, r.B0239, r.A0221A, r.WAGE_USED, r.UPDATED, r.A4907, r.A4904, r.A4901, r.A0299, r.A0295, r.A0289, r.A0288, r.A0284, r.A0277, r.A0271, r.A0259, r.A0256C, r.A0256B, r.A0256A, r.A0256, r.A0251, r.A0229, r.A0222, r.A0221W, r.A0221, r.A0219W, r.A0216A, r.A0215B, r.A0209, r.A0207, r.A0204, r.A0201C, r.A0201, r.A0279, r.V0201B, r.A02001, r.A02002, r.A02003, r.A02004, r.A02005) |
|||
} |
|||
stmt.Close() |
|||
} |
|||
func QueryA02(sqlStr string, db *sql.DB) []A02 { |
|||
v := []A02{} |
|||
rows, err := db.Query(sqlStr) |
|||
if err != nil { |
|||
return nil |
|||
} |
|||
println("开始查询A02...") |
|||
for rows.Next() { |
|||
r := A02{} |
|||
err = rows.Scan(&r.A0000, &r.A0200, &r.A0201A, &r.A0201B, &r.A0201D, &r.A0201E, &r.A0215A, &r.A0219, &r.A0223, &r.A0225, &r.A0243, &r.A0245, &r.A0247, &r.A0251B, &r.A0255, &r.A0265, &r.A0267, &r.A0272, &r.A0281, &r.A0221T, &r.B0238, &r.B0239, &r.A0221A, &r.WAGE_USED, &r.UPDATED, &r.A4907, &r.A4904, &r.A4901, &r.A0299, &r.A0295, &r.A0289, &r.A0288, &r.A0284, &r.A0277, &r.A0271, &r.A0259, &r.A0256C, &r.A0256B, &r.A0256A, &r.A0256, &r.A0251, &r.A0229, &r.A0222, &r.A0221W, &r.A0221, &r.A0219W, &r.A0216A, &r.A0215B, &r.A0209, &r.A0207, &r.A0204, &r.A0201C, &r.A0201, &r.A0279, &r.V0201B, &r.A02001, &r.A02002, &r.A02003, &r.A02004, &r.A02005) |
|||
if err != nil { |
|||
println(err) |
|||
} |
|||
v = append(v, r) |
|||
} |
|||
|
|||
return v |
|||
} |
|||
func MakeA02Xml(p, sql string, db *sql.DB) { |
|||
|
|||
as := QueryA02(sql, db) |
|||
a := A02SData{} |
|||
a.Row = as |
|||
b := A02Table{} |
|||
b.DATA = a |
|||
if f, err := os.Create(p + "A02.xml"); err == nil { |
|||
if s, err := xml.Marshal(b); err == nil { |
|||
f.WriteString(string(s)) |
|||
f.Close() |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,56 @@ |
|||
package main |
|||
|
|||
import "fmt" |
|||
|
|||
func main() { |
|||
fmt.Println("\n ================== 数组类型 方式一 = 开始 ================== ") |
|||
|
|||
balance1 := [5]float32{1000.0, 2.0, 3.4, 7.0, 50.0} |
|||
printSZ(balance1) |
|||
|
|||
fmt.Println("\n ================== 数组类型 方式二 = 开始 ================== ") |
|||
balance2 := [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0} |
|||
printSZ(balance2) |
|||
|
|||
fmt.Println("\n指定位置查询值:", balance2[2]) |
|||
|
|||
fmt.Println("\n ================== 数组类型 方式三 = 开始 ================== ") |
|||
var balance3 [5]float32 |
|||
balance3 = getSZ(balance3) |
|||
printSZ(balance3) |
|||
|
|||
fmt.Println("\n ================== 多维数组类型 方式四 = 开始 ================== ") |
|||
var balance4 = [5][2]int{{0, 0}, {1, 2}, {2, 4}, {3, 6}, {4, 8}} |
|||
printDWSZ(balance4) |
|||
} |
|||
func printDWSZ(balance4 [5][2]int) { |
|||
var i, j int |
|||
for i = 0; i < 5; i++ { |
|||
for j = 0; j < 2; j++ { |
|||
fmt.Printf("\n第%d行的第%d列的值为:", i, j, balance4[i][j]) |
|||
} |
|||
} |
|||
} |
|||
func getSZ(balance3 [5]float32) [5]float32 { |
|||
var j int |
|||
i := len(balance3) |
|||
for j = 0; j < i; j++ { |
|||
balance3[j] = 100 + float32(j) |
|||
} |
|||
return balance3 |
|||
} |
|||
|
|||
/* 实现数组的输出 */ |
|||
func printSZ(balance [5]float32) { |
|||
var j int |
|||
i := len(balance) |
|||
for j = 0; j < i; j++ { |
|||
fmt.Printf("\n第%d个元素值为:", j, balance[j]) |
|||
} |
|||
} |
|||
|
|||
/* 实现数组的输出 */ |
|||
func zhuangHuan() { |
|||
var j int |
|||
fmt.Println(string(j)) |
|||
} |
@ -0,0 +1,38 @@ |
|||
package main |
|||
|
|||
import "fmt" |
|||
|
|||
type myint int |
|||
|
|||
//乘2
|
|||
func (p *myint) mydouble() int { |
|||
*p = *p * 2 |
|||
return int(*p) |
|||
} |
|||
|
|||
//平方
|
|||
func (p myint) mysquare() int { |
|||
p = p * p |
|||
fmt.Println("mysquare p = ", p) |
|||
return int(p) |
|||
} |
|||
|
|||
func main() { |
|||
var i myint = 2 |
|||
b := i.mydouble() |
|||
fmt.Println("i = ", i) |
|||
fmt.Println("b = ", b) |
|||
c := i.mysquare() |
|||
fmt.Println("i = ", i) |
|||
fmt.Println("c = ", c) |
|||
|
|||
fmt.Println("+++++++++++++++++++++++++++++++++++++++++++") |
|||
var aa int = 1 |
|||
var aaa *int = &aa |
|||
fmt.Println(aaa) |
|||
fmt.Println(&aaa) |
|||
fmt.Println(*aaa) |
|||
var bbb int = 56 |
|||
fmt.Println(bbb) |
|||
fmt.Println(&bbb) |
|||
} |
@ -0,0 +1,98 @@ |
|||
package main |
|||
|
|||
import ( |
|||
"encoding/json" |
|||
"fmt" |
|||
) |
|||
|
|||
type dept struct { |
|||
DeptId string `json:"deptId"` |
|||
FrameDeptStr string `json:"frameDeptStr"` |
|||
Child []*dept `json:"child"` |
|||
} |
|||
|
|||
func main() { |
|||
depts := make([]dept, 0) |
|||
var a dept |
|||
a.DeptId = "1" |
|||
a.FrameDeptStr = "" |
|||
depts = append(depts, a) |
|||
a.DeptId = "3" |
|||
a.FrameDeptStr = "1" |
|||
depts = append(depts, a) |
|||
a.DeptId = "4" |
|||
a.FrameDeptStr = "1" |
|||
depts = append(depts, a) |
|||
a.DeptId = "5" |
|||
a.FrameDeptStr = "13" |
|||
depts = append(depts, a) |
|||
a.DeptId = "6" |
|||
a.FrameDeptStr = "13" |
|||
depts = append(depts, a) |
|||
fmt.Println(depts) |
|||
|
|||
deptRoots := make([]dept, 0) |
|||
for _, v := range depts { |
|||
if v.FrameDeptStr == "" { |
|||
deptRoots = append(deptRoots, v) |
|||
} |
|||
} |
|||
|
|||
pdepts := make([]*dept, 0) |
|||
for i, _ := range depts { |
|||
var a *dept |
|||
a = &depts[i] |
|||
pdepts = append(pdepts, a) |
|||
} |
|||
//获取了根上的科室
|
|||
fmt.Println("根上的科室有:", deptRoots) |
|||
|
|||
var node *dept |
|||
node = &depts[0] |
|||
makeTree(pdepts, node) |
|||
fmt.Println("the result we got is", pdepts) |
|||
data, _ := json.Marshal(node) |
|||
fmt.Printf("%s", data) |
|||
|
|||
} |
|||
|
|||
func has(v1 dept, vs []*dept) bool { |
|||
var has bool |
|||
has = false |
|||
for _, v2 := range vs { |
|||
v3 := *v2 |
|||
if v1.FrameDeptStr+v1.DeptId == v3.FrameDeptStr { |
|||
has = true |
|||
break |
|||
} |
|||
} |
|||
return has |
|||
|
|||
} |
|||
|
|||
func makeTree(vs []*dept, node *dept) { |
|||
fmt.Println("the node value in maketree is:", *node) |
|||
childs := findChild(node, vs) |
|||
fmt.Println(" the child we got is :", childs) |
|||
for _, child := range childs { |
|||
fmt.Println("in the childs's for loop, the child's address here is:", &child) |
|||
node.Child = append(node.Child, child) |
|||
fmt.Println("in the child's for loop, after append the child is:", child) |
|||
if has(*child, vs) { |
|||
fmt.Println("i am in if has") |
|||
fmt.Println("the child in if has is:", *child) |
|||
fmt.Println("the child in if has 's address is:", child) |
|||
makeTree(vs, child) |
|||
} |
|||
} |
|||
|
|||
} |
|||
|
|||
func findChild(v *dept, vs []*dept) (ret []*dept) { |
|||
for _, v2 := range vs { |
|||
if v.FrameDeptStr+v.DeptId == v2.FrameDeptStr { |
|||
ret = append(ret, v2) |
|||
} |
|||
} |
|||
return |
|||
} |
@ -0,0 +1,68 @@ |
|||
package main |
|||
|
|||
/** |
|||
Go 语言通过内置的错误接口提供了非常简单的错误处理机制。 |
|||
|
|||
error类型是一个接口类型,这是它的定义: |
|||
|
|||
type error interface { |
|||
Error() string |
|||
} |
|||
|
|||
使用errors.New 可返回一个错误信息: |
|||
|
|||
func Sqrt(f float64) (float64, error) { |
|||
if f < 0 { |
|||
return 0, errors.New("math: square root of negative number") |
|||
} |
|||
// 实现
|
|||
} |
|||
*/ |
|||
|
|||
import ( |
|||
"fmt" |
|||
) |
|||
|
|||
// 定义一个 DivideError 结构
|
|||
type DivideError struct { |
|||
dividee int |
|||
divider int |
|||
} |
|||
|
|||
// 实现 `error` 接口
|
|||
func (de *DivideError) Error() string { |
|||
strFormat := ` |
|||
Cannot proceed, the divider is zero. |
|||
dividee: %d |
|||
divider: 0 |
|||
` |
|||
return fmt.Sprintf(strFormat, de.dividee) |
|||
} |
|||
|
|||
// 定义 `int` 类型除法运算的函数
|
|||
func Divide(varDividee int, varDivider int) (result int, errorMsg string) { |
|||
if varDivider == 0 { |
|||
dData := DivideError{ |
|||
dividee: varDividee, |
|||
divider: varDivider, |
|||
} |
|||
errorMsg = dData.Error() |
|||
return |
|||
} else { |
|||
return varDividee / varDivider, "" |
|||
} |
|||
|
|||
} |
|||
|
|||
func main() { |
|||
|
|||
// 正常情况
|
|||
if result, errorMsg := Divide(100, 10); errorMsg == "" { |
|||
fmt.Println("100/10 = ", result) |
|||
} |
|||
// 当除数为零的时候会返回错误信息
|
|||
if _, errorMsg := Divide(100, 0); errorMsg != "" { |
|||
fmt.Println("errorMsg is: ", errorMsg) |
|||
} |
|||
|
|||
} |
@ -0,0 +1,36 @@ |
|||
package main |
|||
|
|||
/** |
|||
通道(channel)是用来传递数据的一个数据结构。 |
|||
|
|||
通道可用于两个 goroutine 之间通过传递一个指定类型的值来同步运行和通讯。操作符 <- 用于指定通道的方向,发送或接收。如果未指定方向,则为双向通道。 |
|||
|
|||
ch <- v // 把 v 发送到通道 ch
|
|||
v := <-ch // 从 ch 接收数据
|
|||
// 并把值赋给 v
|
|||
|
|||
声明一个通道很简单,我们使用chan关键字即可,通道在使用前必须先创建: |
|||
|
|||
ch := make(chan int) |
|||
注意:默认情况下,通道是不带缓冲区的。发送端发送数据,同时必须有接收端相应的接收数据。 |
|||
*/ |
|||
import "fmt" |
|||
|
|||
func sum(s []int, c chan int) { |
|||
sum := 0 |
|||
for _, v := range s { |
|||
sum += v |
|||
} |
|||
c <- sum // 把 sum 发送到通道 c
|
|||
} |
|||
|
|||
func main() { |
|||
s := []int{7, 2, 8, -9, 4, 0} |
|||
|
|||
c := make(chan int) |
|||
go sum(s[:len(s)/2], c) |
|||
go sum(s[len(s)/2:], c) |
|||
x, y := <-c, <-c // 从通道 c 中接收
|
|||
|
|||
fmt.Println(x, y, x+y) |
|||
} |
@ -0,0 +1,34 @@ |
|||
package main |
|||
|
|||
/** |
|||
通道可以设置缓冲区,通过 make 的第二个参数指定缓冲区大小: |
|||
|
|||
ch := make(chan int, 100) |
|||
带缓冲区的通道允许发送端的数据发送和接收端的数据获取处于异步状态,就是说发送端发送的数据可以放在缓冲区里面,可以等待接收端去获取数据,而不是立刻需要接收端去获取数据。 |
|||
|
|||
不过由于缓冲区的大小是有限的,所以还是必须有接收端来接收数据的,否则缓冲区一满,数据发送端就无法再发送数据了。 |
|||
|
|||
注意:如果通道不带缓冲,发送方会阻塞直到接收方从通道中接收了值。如果通道带缓冲,发送方则会阻塞直到发送的值被拷贝到缓冲区内;如果缓冲区已满,则意味着需要等待直到某个接收方获取到一个值。接收方在有值可以接收之前会一直阻塞。 |
|||
*/ |
|||
import "fmt" |
|||
|
|||
func main() { |
|||
// 这里我们定义了一个可以存储整数类型的带缓冲通道
|
|||
// 缓冲区大小为2
|
|||
ch := make(chan int, 2) |
|||
|
|||
// 因为 ch 是带缓冲的通道,我们可以同时发送两个数据
|
|||
// 而不用立刻需要去同步读取数据
|
|||
ch <- 1 |
|||
ch <- 2 |
|||
|
|||
// 获取这两个数据
|
|||
fmt.Println(<-ch) |
|||
fmt.Println(<-ch) |
|||
|
|||
fmt.Println(ch) |
|||
|
|||
ca := make(chan string, 2) |
|||
ca <- "s" |
|||
fmt.Println(<-ca) |
|||
} |
@ -0,0 +1,53 @@ |
|||
package main |
|||
|
|||
import "errors" |
|||
|
|||
type Queue struct { |
|||
queue []int // 存储内容
|
|||
size int // 队列大小
|
|||
rear int // 对尾
|
|||
front int // 对首
|
|||
} |
|||
|
|||
func (receiver *Queue) Push(obj int) error { |
|||
if !receiver.IsFilled() { |
|||
receiver.rear = (receiver.rear + 1) % receiver.size |
|||
receiver.queue[receiver.rear] = obj |
|||
return nil |
|||
} else { |
|||
return errors.New("Queue is filled!") |
|||
} |
|||
} |
|||
|
|||
func (receiver *Queue) Pop() (int, error) { |
|||
if !receiver.IsEmpty() { |
|||
receiver.front = (receiver.front + 1) % receiver.size |
|||
return receiver.queue[receiver.front], nil |
|||
} else { |
|||
return 0, errors.New("Queue is empty!") |
|||
} |
|||
} |
|||
|
|||
func (receiver *Queue) IsFilled() bool { |
|||
return receiver.front == (receiver.rear+1)%receiver.size |
|||
} |
|||
|
|||
func (receiver *Queue) IsEmpty() bool { |
|||
return receiver.front == receiver.rear |
|||
} |
|||
|
|||
func main() { |
|||
s := Queue{ |
|||
make([]int, 10), |
|||
10, |
|||
0, |
|||
0, |
|||
} |
|||
s.Push(2) |
|||
s.Push(3) |
|||
s.Push(4) |
|||
|
|||
println(s.IsEmpty()) |
|||
a, _ := s.Pop() |
|||
println(a) |
|||
} |