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
func helloHandler(wr http.ResponseWriter, r *http.Request) {
...
// ...
}
func showInfoHandler(wr http.ResponseWriter, r *http.Request) {
...
// ...
}
func showEmailHandler(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("/email/show", showEmailHandler)
http.HandleFunc("/friends/show", showFriendsHandler)
...
// ...
}
```

View File

@ -119,7 +119,10 @@ var req = RegisterReq {
}
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 进行错误信息定制,读者可以自行探索。
@ -166,7 +169,9 @@ type T struct {
}
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 false
@ -188,7 +193,9 @@ func validate(v interface{}) (bool, string) {
tagValStr := strings.Split(tagContent, "=")
tagVal, _ := strconv.ParseInt(tagValStr[1], 10, 64)
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
}
case reflect.String:

View File

@ -135,7 +135,9 @@ func NewBucketWithRate(rate float64, capacity int64) *Bucket
```go
func (tb *Bucket) Take(count int64) time.Duration {}
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) WaitMaxDuration(count int64, maxWait time.Duration) bool {}
```

View File

@ -29,7 +29,10 @@
这样我们 controller 中的入口函数就变成了下面这样:
```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
}
```
按白名单、按业务线、按 UA、按分发渠道发布本质上和按城市发布是一样的这里就不再赘述了。
@ -193,9 +192,12 @@ hash.go:
```go
package main
import "crypto/md5"
import "crypto/sha1"
import "github.com/spaolacci/murmur3"
import (
"crypto/md5"
"crypto/sha1"
"github.com/spaolacci/murmur3"
)
var str = "hello world"
@ -214,7 +216,6 @@ func murmur32() uint32 {
func murmur64() uint64 {
return murmur3.Sum64([]byte(str))
}
```
hash_test.go
@ -296,7 +297,8 @@ func murmur64(p string) uint64 {
```
```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 以内,是可以接受的。

View File

@ -7,7 +7,6 @@
Twitter 的 snowflake 算法是这种场景下的一个典型解法。先来看看 snowflake 是怎么一回事:
```
datacenter_id sequence_id
unused
│ │
@ -28,7 +27,6 @@ Twitter 的 snowflake 算法是这种场景下的一个典型解法。先来看
│ │
time in milliseconds worker_id
```
首先确定我们的数值是 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++ {
id := n.Generate()
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)
}
```

View File

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

View File

@ -57,15 +57,18 @@ elasticsearch 是开源分布式搜索引擎的霸主,其依赖于 Lucene 实
```go
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
}
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
}
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
}
@ -235,7 +238,6 @@ func insertDocument(db string, table string, obj map[string]interface{}) {
// insert success
}
}
```
获取:
@ -246,8 +248,10 @@ func query(indexName string, typeName string) (*elastic.SearchResult, error) {
q := elastic.NewBoolQuery().Must(elastic.NewMatchPhraseQuery("id", 1),
elastic.NewBoolQuery().Must(elastic.NewMatchPhraseQuery("male", "m")))
q = q.Should(elastic.NewMatchPhraseQuery("name", "alex"),
elastic.NewMatchPhraseQuery("name", "xargin"))
q = q.Should(
elastic.NewMatchPhraseQuery("name", "alex"),
elastic.NewMatchPhraseQuery("name", "xargin"),
)
searchService := esClient.Search(indexName).Type(typeName)
res, err := searchService.Query(q).Do()
@ -263,7 +267,9 @@ func query(indexName string, typeName string) (*elastic.SearchResult, error) {
删除:
```go
func deleteDocument(indexName string, typeName string, obj map[string]interface{}) {
func deleteDocument(
indexName string, typeName string, obj map[string]interface{},
) {
id := obj["id"]
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 是如下形式:
```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 是如下形式:
@ -399,7 +407,9 @@ select * from wms_orders where update_time >= date_sub(now(), interval 10 minute
当然,考虑到边界情况,我们可以让这个时间段的数据与前一次的有一些重叠:
```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 字段,并且每次创建和更新都要保证该字段有正确的时间值。否则我们的同步逻辑就会丢失数据。

View File

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

View File

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