1
0
mirror of https://github.com/chai2010/advanced-go-programming-book.git synced 2025-05-24 04:22:22 +00:00
This commit is contained in:
chai2010 2018-12-17 18:08:30 +08:00
parent 9ad62fe063
commit 6e50377cee
15 changed files with 1129 additions and 1109 deletions

View File

@ -47,15 +47,15 @@ func hello(wr http.ResponseWriter, r *http.Request) {
package main package main
func helloHandler(wr http.ResponseWriter, r *http.Request) { func helloHandler(wr http.ResponseWriter, r *http.Request) {
... // ...
} }
func showInfoHandler(wr http.ResponseWriter, r *http.Request) { func showInfoHandler(wr http.ResponseWriter, r *http.Request) {
... // ...
} }
func showEmailHandler(wr http.ResponseWriter, r *http.Request) { func showEmailHandler(wr http.ResponseWriter, r *http.Request) {
... // ...
} }
func showFriendsHandler(wr http.ResponseWriter, r *http.Request) { func showFriendsHandler(wr http.ResponseWriter, r *http.Request) {
@ -70,7 +70,7 @@ func main() {
http.HandleFunc("/info/show", showInfoHandler) http.HandleFunc("/info/show", showInfoHandler)
http.HandleFunc("/email/show", showEmailHandler) http.HandleFunc("/email/show", showEmailHandler)
http.HandleFunc("/friends/show", showFriendsHandler) http.HandleFunc("/friends/show", showFriendsHandler)
... // ...
} }
``` ```

View File

@ -119,7 +119,10 @@ var req = RegisterReq {
} }
err := validate(req) err := validate(req)
fmt.Println(err) // Key: 'RegisterReq.PasswordRepeat' Error:Field validation for 'PasswordRepeat' failed on the 'eqfield' tag fmt.Println(err)
// Key: 'RegisterReq.PasswordRepeat' Error:Field validation for
// 'PasswordRepeat' failed on the 'eqfield' tag
``` ```
如果觉得这个 validator 提供的错误信息不够人性化,例如要把错误信息返回给用户,那就不应该直接显示英文了。可以针对每种 tag 进行错误信息定制,读者可以自行探索。 如果觉得这个 validator 提供的错误信息不够人性化,例如要把错误信息返回给用户,那就不应该直接显示英文了。可以针对每种 tag 进行错误信息定制,读者可以自行探索。
@ -166,7 +169,9 @@ type T struct {
} }
func validateEmail(input string) bool { func validateEmail(input string) bool {
if pass, _ := regexp.MatchString(`^([\w\.\_]{2,10})@(\w{1,}).([a-z]{2,4})$`, input); pass { if pass, _ := regexp.MatchString(
`^([\w\.\_]{2,10})@(\w{1,}).([a-z]{2,4})$`, input,
); pass {
return true return true
} }
return false return false
@ -188,7 +193,9 @@ func validate(v interface{}) (bool, string) {
tagValStr := strings.Split(tagContent, "=") tagValStr := strings.Split(tagContent, "=")
tagVal, _ := strconv.ParseInt(tagValStr[1], 10, 64) tagVal, _ := strconv.ParseInt(tagValStr[1], 10, 64)
if val != tagVal { if val != tagVal {
errmsg = "validate int failed, tag is: "+ strconv.FormatInt(tagVal, 10) errmsg = "validate int failed, tag is: "+ strconv.FormatInt(
tagVal, 10,
)
validateResult = false validateResult = false
} }
case reflect.String: case reflect.String:

View File

@ -135,7 +135,9 @@ func NewBucketWithRate(rate float64, capacity int64) *Bucket
```go ```go
func (tb *Bucket) Take(count int64) time.Duration {} func (tb *Bucket) Take(count int64) time.Duration {}
func (tb *Bucket) TakeAvailable(count int64) int64 {} func (tb *Bucket) TakeAvailable(count int64) int64 {}
func (tb *Bucket) TakeMaxDuration(count int64, maxWait time.Duration) (time.Duration, bool) {} func (tb *Bucket) TakeMaxDuration(count int64, maxWait time.Duration) (
time.Duration, bool,
) {}
func (tb *Bucket) Wait(count int64) {} func (tb *Bucket) Wait(count int64) {}
func (tb *Bucket) WaitMaxDuration(count int64, maxWait time.Duration) bool {} func (tb *Bucket) WaitMaxDuration(count int64, maxWait time.Duration) bool {}
``` ```

View File

@ -29,7 +29,10 @@
这样我们 controller 中的入口函数就变成了下面这样: 这样我们 controller 中的入口函数就变成了下面这样:
```go ```go
func CreateOrder(ctx context.Context, req *CreateOrderStruct) (*CreateOrderRespStruct, error) { func CreateOrder(ctx context.Context, req *CreateOrderStruct) (
*CreateOrderRespStruct, error,
) {
// ...
} }
``` ```

View File

@ -155,7 +155,6 @@ func isPassed(cityID int) bool {
return false return false
} }
``` ```
按白名单、按业务线、按 UA、按分发渠道发布本质上和按城市发布是一样的这里就不再赘述了。 按白名单、按业务线、按 UA、按分发渠道发布本质上和按城市发布是一样的这里就不再赘述了。
@ -193,9 +192,12 @@ hash.go:
```go ```go
package main package main
import "crypto/md5" import (
import "crypto/sha1" "crypto/md5"
import "github.com/spaolacci/murmur3" "crypto/sha1"
"github.com/spaolacci/murmur3"
)
var str = "hello world" var str = "hello world"
@ -214,7 +216,6 @@ func murmur32() uint32 {
func murmur64() uint64 { func murmur64() uint64 {
return murmur3.Sum64([]byte(str)) return murmur3.Sum64([]byte(str))
} }
``` ```
hash_test.go hash_test.go
@ -296,7 +297,8 @@ func murmur64(p string) uint64 {
``` ```
```shell ```shell
map[7:999475 5:1000359 1:999945 6:1000200 3:1000193 9:1000765 2:1000044 4:1000343 8:1000823 0:997853] map[7:999475 5:1000359 1:999945 6:1000200 3:1000193 9:1000765 2:1000044 \
4:1000343 8:1000823 0:997853]
``` ```
偏差基本都在 1/100 以内,是可以接受的。 偏差基本都在 1/100 以内,是可以接受的。

View File

@ -7,7 +7,6 @@
Twitter 的 snowflake 算法是这种场景下的一个典型解法。先来看看 snowflake 是怎么一回事: Twitter 的 snowflake 算法是这种场景下的一个典型解法。先来看看 snowflake 是怎么一回事:
``` ```
datacenter_id sequence_id datacenter_id sequence_id
unused unused
│ │ │ │
@ -28,7 +27,6 @@ Twitter 的 snowflake 算法是这种场景下的一个典型解法。先来看
│ │ │ │
time in milliseconds worker_id time in milliseconds worker_id
``` ```
首先确定我们的数值是 64 位int64 类型,被划分为四部分,不含开头的第一个 bit因为这个 bit 是符号位。用 41 位来表示收到请求时的时间戳,单位为毫秒,然后五位来表示数据中心的 id然后再五位来表示机器的实例 id最后是 12 位的循环自增 id(到达 1111 1111 1111 后会归 0)。 首先确定我们的数值是 64 位int64 类型,被划分为四部分,不含开头的第一个 bit因为这个 bit 是符号位。用 41 位来表示收到请求时的时间戳,单位为毫秒,然后五位来表示数据中心的 id然后再五位来表示机器的实例 id最后是 12 位的循环自增 id(到达 1111 1111 1111 后会归 0)。
@ -98,10 +96,14 @@ func main() {
for i := 0; i < 3; i++ { for i := 0; i < 3; i++ {
id := n.Generate() id := n.Generate()
fmt.Println("id", id) fmt.Println("id", id)
fmt.Println("node: ", id.Node(), "step: ", id.Step(), "time: ", id.Time(), "\n") fmt.Println(
"node: ", id.Node(),
"step: ", id.Step(),
"time: ", id.Time(),
"\n",
)
} }
} }
``` ```
当然,这个库也给我们留好了定制的后路: 当然,这个库也给我们留好了定制的后路:
@ -221,5 +223,4 @@ func main() {
fmt.Println(id) fmt.Println(id)
} }
``` ```

View File

@ -201,7 +201,6 @@ func main() {
} }
wg.Wait() wg.Wait()
} }
``` ```
看看运行结果: 看看运行结果:
@ -297,7 +296,6 @@ func main() {
log.Printf("etcdsync.Unlock OK") log.Printf("etcdsync.Unlock OK")
} }
} }
``` ```
etcd 中没有像 ZooKeeper 那样的 sequence 节点。所以其锁实现和基于 ZooKeeper 实现的有所不同。在上述示例代码中使用的 etcdsync 的 Lock 流程是: etcd 中没有像 ZooKeeper 那样的 sequence 节点。所以其锁实现和基于 ZooKeeper 实现的有所不同。在上述示例代码中使用的 etcdsync 的 Lock 流程是:
@ -351,7 +349,9 @@ func newPools(servers []string) []redsync.Pool {
} }
func main() { func main() {
pools := newPools([]string{"127.0.0.1:6379", "127.0.0.1:6378", "127.0.0.1:6377"}) pools := newPools([]string{
"127.0.0.1:6379", "127.0.0.1:6378", "127.0.0.1:6377",
})
rs := redsync.New(pools) rs := redsync.New(pools)
m := rs.NewMutex("/lock") m := rs.NewMutex("/lock")
@ -362,7 +362,6 @@ func main() {
fmt.Println("lock success") fmt.Println("lock success")
unlockRes := m.Unlock() unlockRes := m.Unlock()
fmt.Println("unlock result: ", unlockRes) fmt.Println("unlock result: ", unlockRes)
} }
``` ```

View File

@ -57,15 +57,18 @@ elasticsearch 是开源分布式搜索引擎的霸主,其依赖于 Lucene 实
```go ```go
func equal() { func equal() {
if postEntry.docID of '天气' == postEntry.docID of '气很' && postEntry.offset + 1 of '天气' == postEntry.offset of '气很' { if postEntry.docID of '天气' == postEntry.docID of '气很' &&
postEntry.offset + 1 of '天气' == postEntry.offset of '气很' {
return true return true
} }
if postEntry.docID of '气很' == postEntry.docID of '很好' && postEntry.offset + 1 of '气很' == postEntry.offset of '很好' { if postEntry.docID of '气很' == postEntry.docID of '很好' &&
postEntry.offset + 1 of '气很' == postEntry.offset of '很好' {
return true return true
} }
if postEntry.docID of '天气' == postEntry.docID of '很好' && postEntry.offset + 2 of '天气' == postEntry.offset of '很好' { if postEntry.docID of '天气' == postEntry.docID of '很好' &&
postEntry.offset + 2 of '天气' == postEntry.offset of '很好' {
return true return true
} }
@ -235,7 +238,6 @@ func insertDocument(db string, table string, obj map[string]interface{}) {
// insert success // insert success
} }
} }
``` ```
获取: 获取:
@ -246,8 +248,10 @@ func query(indexName string, typeName string) (*elastic.SearchResult, error) {
q := elastic.NewBoolQuery().Must(elastic.NewMatchPhraseQuery("id", 1), q := elastic.NewBoolQuery().Must(elastic.NewMatchPhraseQuery("id", 1),
elastic.NewBoolQuery().Must(elastic.NewMatchPhraseQuery("male", "m"))) elastic.NewBoolQuery().Must(elastic.NewMatchPhraseQuery("male", "m")))
q = q.Should(elastic.NewMatchPhraseQuery("name", "alex"), q = q.Should(
elastic.NewMatchPhraseQuery("name", "xargin")) elastic.NewMatchPhraseQuery("name", "alex"),
elastic.NewMatchPhraseQuery("name", "xargin"),
)
searchService := esClient.Search(indexName).Type(typeName) searchService := esClient.Search(indexName).Type(typeName)
res, err := searchService.Query(q).Do() res, err := searchService.Query(q).Do()
@ -263,7 +267,9 @@ func query(indexName string, typeName string) (*elastic.SearchResult, error) {
删除: 删除:
```go ```go
func deleteDocument(indexName string, typeName string, obj map[string]interface{}) { func deleteDocument(
indexName string, typeName string, obj map[string]interface{},
) {
id := obj["id"] id := obj["id"]
res, err := esClient.Delete().Index(indexName).Type(typeName).Id(id).Do() res, err := esClient.Delete().Index(indexName).Type(typeName).Id(id).Do()
@ -284,7 +290,9 @@ func deleteDocument(indexName string, typeName string, obj map[string]interface{
比如我们有一段 bool 表达式user_id = 1 and (product_id = 1 and (star_num = 4 or star_num = 5) and banned = 1),写成 SQL 是如下形式: 比如我们有一段 bool 表达式user_id = 1 and (product_id = 1 and (star_num = 4 or star_num = 5) and banned = 1),写成 SQL 是如下形式:
```sql ```sql
select * from xxx where user_id = 1 and (product_id = 1 and (star_num = 4 or star_num = 5) and banned = 1) select * from xxx where user_id = 1 and (
product_id = 1 and (star_num = 4 or star_num = 5) and banned = 1
)
``` ```
写成 es 的 DSL 是如下形式: 写成 es 的 DSL 是如下形式:
@ -399,7 +407,9 @@ select * from wms_orders where update_time >= date_sub(now(), interval 10 minute
当然,考虑到边界情况,我们可以让这个时间段的数据与前一次的有一些重叠: 当然,考虑到边界情况,我们可以让这个时间段的数据与前一次的有一些重叠:
```sql ```sql
select * from wms_orders where update_time >= date_sub(now(), interval 11 minute); select * from wms_orders where update_time >= date_sub(
now(), interval 11 minute
);
``` ```
取最近 11 分钟有变动的数据覆盖更新到 es 中。这种方案的缺点显而易见,我们必须要求业务数据严格遵守一定的规范。比如这里的,必须要有 update_time 字段,并且每次创建和更新都要保证该字段有正确的时间值。否则我们的同步逻辑就会丢失数据。 取最近 11 分钟有变动的数据覆盖更新到 es 中。这种方案的缺点显而易见,我们必须要求业务数据严格遵守一定的规范。比如这里的,必须要有 update_time 字段,并且每次创建和更新都要保证该字段有正确的时间值。否则我们的同步逻辑就会丢失数据。

View File

@ -63,7 +63,6 @@ func request(params map[string]interface{}) error {
return nil return nil
} }
``` ```
我们循环一遍 slice两两交换这个和我们平常打牌时常用的洗牌方法类似。看起来没有什么问题。 我们循环一遍 slice两两交换这个和我们平常打牌时常用的洗牌方法类似。看起来没有什么问题。
@ -167,7 +166,6 @@ func main() {
fmt.Println(cnt1, "\n", cnt2) fmt.Println(cnt1, "\n", cnt2)
} }
``` ```
输出: 输出:

View File

@ -65,7 +65,6 @@ func main() {
fmt.Println(err) fmt.Println(err)
} }
} }
``` ```
## 分布式爬虫 ## 分布式爬虫
@ -316,7 +315,6 @@ func startConsumer() {
func main() { func main() {
startConsumer() startConsumer()
} }
``` ```
从代码层面上来讲,这里的生产者和消费者其实本质上差不多。如果日后我们要灵活地支持增加、减少各种网站的爬取的话,应该思考如何将这些爬虫的策略、参数尽量地配置化。 从代码层面上来讲,这里的生产者和消费者其实本质上差不多。如果日后我们要灵活地支持增加、减少各种网站的爬取的话,应该思考如何将这些爬虫的策略、参数尽量地配置化。