├── .codeclimate.yml ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── _config.yml ├── cmd └── xj │ └── main.go ├── example └── sample.go ├── file.go ├── go.mod ├── go.sum ├── json.go ├── node.go ├── path.go ├── testjson ├── githubAPI.json ├── smartStreetsAPI.json └── topics.json ├── testxlsx └── testxml.xlsx ├── testxml ├── [Content_Types].xml ├── _rels │ └── .rels ├── docProps │ ├── app.xml │ └── core.xml ├── sample.xml ├── test.xml └── xl │ ├── _rels │ └── workbook.xml.rels │ ├── sharedStrings.xml │ ├── styles.xml │ ├── theme │ └── theme1.xml │ ├── workbook.xml │ └── worksheets │ └── sheet1.xml ├── utils.go ├── utils_test.go ├── xj2go.go ├── xj2go_test.go └── xml.go /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | --- 2 | engines: 3 | rubocop: 4 | enabled: true 5 | golint: 6 | enabled: true 7 | gofmt: 8 | enabled: true 9 | govet: 10 | enabled: true 11 | fixme: 12 | enabled: true 13 | duplication: 14 | enabled: true 15 | config: 16 | languages: 17 | - go 18 | ratings: 19 | paths: 20 | - "**.go" 21 | exclude_paths: 22 | - vendor/ 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | .sonarlint 4 | .scannerwork 5 | excel/* 6 | excel2/* 7 | xjson/* 8 | xjson2/* 9 | coverage.txt 10 | coverage.xml 11 | report.xml 12 | /test.xml 13 | sonar-project.properties 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.x 5 | 6 | before_install: 7 | - go get -u -v github.com/campoy/embedmd 8 | - embedmd -d *.md 9 | - go get -t -v ./... 10 | 11 | script: 12 | - go test -v -race -coverprofile=coverage.txt -covermode=atomic 13 | 14 | after_success: 15 | - bash <(curl -s https://codecov.io/bash) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 wk30 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # xj2go 2 | 3 | [![Go Report Card](https://goreportcard.com/badge/github.com/wk30/xj2go)](https://goreportcard.com/badge/github.com/wk30/xj2go) [![Build Status](https://www.travis-ci.org/wk30/xj2go.svg?branch=master)](https://www.travis-ci.org/wk30/xj2go) [![codecov](https://codecov.io/gh/wk30/xj2go/branch/master/graph/badge.svg)](https://codecov.io/gh/wk30/xj2go) [![codebeat badge](https://codebeat.co/badges/baec2a13-1f35-4032-bbf4-66cbead635c4)](https://codebeat.co/projects/github-com-wk30-xj2go-master) 4 | 5 | The goal is to convert xml or json file to go struct file. 6 | 7 | ## Usage 8 | 9 | Download and install it: 10 | ```sh 11 | $ go get -u -v github.com/wk30/xj2go/cmd/... 12 | 13 | $ xj [-t json/xml] [-p sample] [-r result] sample.json 14 | ``` 15 | Import it in your code: 16 | ```go 17 | import "github.com/wk30/xj2go" 18 | ``` 19 | ## Example 20 | 21 | Please see [the example file](example/sample.go). 22 | 23 | [embedmd]:# (example/sample.go go) 24 | ```go 25 | package main 26 | 27 | import ( 28 | "io/ioutil" 29 | "log" 30 | 31 | "github.com/wk30/xj2go" 32 | ) 33 | 34 | func main() { 35 | xmlfilename := "../testxml/xl/styles.xml" 36 | xj1 := xj2go.New(xmlfilename, "demoxml", "") 37 | xj1.XMLToGo() 38 | 39 | b1, err := ioutil.ReadFile(xmlfilename) 40 | if err != nil { 41 | log.Fatal(err) 42 | } 43 | 44 | if err := xj2go.XMLBytesToGo("test", "demoxml2", &b1); err != nil { 45 | log.Fatal(err) 46 | } 47 | 48 | jsonfilename := "../testjson/githubAPI.json" 49 | xj2 := xj2go.New(jsonfilename, "demojson", "sample") 50 | xj2.JSONToGo() 51 | 52 | b2, err := ioutil.ReadFile(jsonfilename) 53 | if err != nil { 54 | log.Fatal(err) 55 | } 56 | 57 | if err := xj2go.JSONBytesToGo("test", "demojson2", "github", &b2); err != nil { 58 | log.Fatal(err) 59 | } 60 | } 61 | ``` 62 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-merlot -------------------------------------------------------------------------------- /cmd/xj/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | 7 | "github.com/wk30/xj2go" 8 | ) 9 | 10 | func usage() { 11 | fmt.Println("Usage: xj [-t json/xml] [-p sample] [-r result] sample.json") 12 | flag.PrintDefaults() 13 | } 14 | 15 | func main() { 16 | t := flag.String("t", "xml", "Type to parse\navaliable type:xml,json") 17 | p := flag.String("p", "sample", "Package name to generate") 18 | r := flag.String("r", "", "Root name for struct") //TODO: useless for xml file 19 | flag.Parse() 20 | 21 | if flag.NArg() > 0 { 22 | xj := xj2go.New(flag.Arg(0), *p, *r) 23 | switch *t { 24 | case "xml": 25 | if err := xj.XMLToGo(); err != nil { 26 | panic(err) 27 | } 28 | case "json": 29 | if err := xj.JSONToGo(); err != nil { 30 | panic(err) 31 | } 32 | } 33 | } else { 34 | usage() 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /example/sample.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io/ioutil" 5 | "log" 6 | 7 | "github.com/wk30/xj2go" 8 | ) 9 | 10 | func main() { 11 | xmlfilename := "../testxml/xl/styles.xml" 12 | xj1 := xj2go.New(xmlfilename, "demoxml", "") 13 | xj1.XMLToGo() 14 | 15 | b1, err := ioutil.ReadFile(xmlfilename) 16 | if err != nil { 17 | log.Fatal(err) 18 | } 19 | 20 | if err := xj2go.XMLBytesToGo("test", "demoxml2", &b1); err != nil { 21 | log.Fatal(err) 22 | } 23 | 24 | jsonfilename := "../testjson/githubAPI.json" 25 | xj2 := xj2go.New(jsonfilename, "demojson", "sample") 26 | xj2.JSONToGo() 27 | 28 | b2, err := ioutil.ReadFile(jsonfilename) 29 | if err != nil { 30 | log.Fatal(err) 31 | } 32 | 33 | if err := xj2go.JSONBytesToGo("test", "demojson2", "github", &b2); err != nil { 34 | log.Fatal(err) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /file.go: -------------------------------------------------------------------------------- 1 | package xj2go 2 | 3 | import ( 4 | "errors" 5 | "log" 6 | "os" 7 | "os/exec" 8 | "path" 9 | "regexp" 10 | "sort" 11 | ) 12 | 13 | func checkFile(filename, pkg string) (string, error) { 14 | if ok, err := pathExists(pkg); !ok { 15 | os.Mkdir(pkg, 0755) 16 | if err != nil { 17 | return "", err 18 | } 19 | } 20 | 21 | filename = path.Base(filename) 22 | if filename[:1] == "." { 23 | return "", errors.New("File could not start with '.'") 24 | } 25 | 26 | filename = pkg + "/" + filename + ".go" 27 | if ok, _ := pathExists(filename); ok { 28 | if err := os.Remove(filename); err != nil { 29 | log.Fatal(err) 30 | return "", err 31 | } 32 | } 33 | 34 | return filename, nil 35 | } 36 | 37 | func writeStruct(filename, pkg string, strcts []strctMap) error { 38 | re := regexp.MustCompile("\\[|\\]") 39 | filename = re.ReplaceAllString(filename, "") 40 | 41 | file, err := os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) 42 | defer file.Close() 43 | if err != nil { 44 | log.Fatal(err) 45 | return err 46 | } 47 | 48 | pkgLines := make(map[string]string) 49 | strctLines := []string{} 50 | 51 | var roots []string 52 | strctsMap := make(map[string]strctMap) 53 | 54 | for _, strct := range strcts { 55 | for root := range strct { 56 | roots = append(roots, root) 57 | strctsMap[root] = strct 58 | } 59 | } 60 | 61 | sort.Strings(roots) 62 | 63 | for _, root := range roots { 64 | strct := strctsMap[root] 65 | for r, sns := range strct { 66 | sort.Sort(byName(sns)) 67 | strctLines = append(strctLines, "type "+toProperCase(r)+" struct {\n") 68 | for i := 0; i < len(sns); i++ { 69 | if sns[i].Type == "time.Time" { 70 | pkgLines["time.Time"] = "import \"time\"\n" 71 | } 72 | strctLines = append(strctLines, "\t"+toProperCase(sns[i].Name)+"\t"+sns[i].Type+"\t"+sns[i].Tag+"\n") 73 | } 74 | strctLines = append(strctLines, "}\n") 75 | } 76 | } 77 | 78 | strctLines = append(strctLines, "\n") 79 | 80 | file.WriteString("package " + pkg + "\n\n") 81 | for _, pl := range pkgLines { 82 | file.WriteString(pl) 83 | } 84 | for _, sl := range strctLines { 85 | file.WriteString(sl) 86 | } 87 | 88 | ft := exec.Command("go", "fmt", filename) 89 | if err := ft.Run(); err != nil { 90 | log.Fatal(err) 91 | return err 92 | } 93 | 94 | vt := exec.Command("go", "vet", filename) 95 | if err := vt.Run(); err != nil { 96 | log.Fatal(err) 97 | return err 98 | } 99 | 100 | return nil 101 | } 102 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/wk30/xj2go 2 | 3 | go 1.13 4 | 5 | require ( 6 | golang.org/x/net v0.0.0-20211011170408-caeb26a5c8c0 7 | golang.org/x/text v0.3.7 // indirect 8 | ) 9 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | golang.org/x/net v0.0.0-20211011170408-caeb26a5c8c0 h1:qOfNqBm5gk93LjGZo1MJaKY6Bph39zOKz1Hz2ogHj1w= 2 | golang.org/x/net v0.0.0-20211011170408-caeb26a5c8c0/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 3 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 4 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 5 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 6 | golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= 7 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 8 | golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= 9 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 10 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 11 | -------------------------------------------------------------------------------- /json.go: -------------------------------------------------------------------------------- 1 | package xj2go 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "io/ioutil" 7 | "log" 8 | ) 9 | 10 | func jsonToLeafNodes(root, filename string) ([]leafNode, error) { 11 | if root == "" { 12 | root = "Result" 13 | } 14 | m, err := jsonFileToMap(root, filename) 15 | if err != nil { 16 | log.Fatal(err) 17 | return nil, err 18 | } 19 | 20 | lns, err := leafNodes(m) 21 | if err != nil { 22 | log.Fatal(err) 23 | return nil, err 24 | } 25 | 26 | return reLeafNodes(lns, root) 27 | } 28 | 29 | func jsonFileToMap(root, filename string) (map[string]interface{}, error) { 30 | m := make(map[string]interface{}) 31 | val, err := ioutil.ReadFile(filename) 32 | if err != nil { 33 | return nil, err 34 | } 35 | if len(val) == 0 { 36 | return m, nil 37 | } 38 | 39 | if val[0] == '[' { 40 | val = []byte(`{"` + root + `":` + string(val) + `}`) 41 | } 42 | 43 | return jsonDecode(&m, &val) 44 | } 45 | 46 | func jsonBytesToMap(pkg, root string, b *[]byte) (map[string]interface{}, error) { 47 | if root == "" { 48 | root = "Result" 49 | } 50 | m := make(map[string]interface{}) 51 | if (*b)[0] == '[' { 52 | *b = []byte(`{"` + root + `":` + string(*b) + `}`) 53 | } 54 | 55 | return jsonDecode(&m, b) 56 | } 57 | 58 | func jsonDecode(m *map[string]interface{}, b *[]byte) (map[string]interface{}, error) { 59 | buf := bytes.NewReader(*b) 60 | dec := json.NewDecoder(buf) 61 | err := dec.Decode(m) 62 | return *m, err 63 | } 64 | -------------------------------------------------------------------------------- /node.go: -------------------------------------------------------------------------------- 1 | package xj2go 2 | 3 | import ( 4 | "strconv" 5 | ) 6 | 7 | type leafNode struct { 8 | path string 9 | value interface{} 10 | } 11 | 12 | func appendStrctNode(strct *strctMap, strcts *[]strctMap) { 13 | m := 0 14 | for key, vals := range *strct { 15 | for _, stct := range *strcts { 16 | if _, ok := stct[key]; !ok { 17 | continue 18 | } 19 | for j := 0; j < len(vals); j++ { 20 | n := 0 21 | for k := 0; k < len(stct[key]); k++ { 22 | if vals[j].Name == stct[key][k].Name && vals[j].Type == stct[key][k].Type { 23 | n++ 24 | } 25 | } 26 | if n == 0 { 27 | stct[key] = append(stct[key], vals[j]) 28 | } 29 | } 30 | m++ 31 | } 32 | } 33 | if m == 0 { 34 | *strcts = append(*strcts, *strct) 35 | } 36 | } 37 | 38 | func leafNodes(m map[string]interface{}) ([]leafNode, error) { 39 | l := []leafNode{} 40 | getLeafNodes("", "", m, &l) 41 | 42 | // paths := []string{} 43 | // for i := 0; i < len(l); i++ { 44 | // paths = append(paths, l[i].path) 45 | // } 46 | 47 | return l, nil 48 | } 49 | 50 | func reLeafNodes(lns []leafNode, prefix string) ([]leafNode, error) { 51 | ls := []leafNode{} 52 | var l leafNode 53 | for _, ln := range lns { 54 | l = leafNode{ 55 | path: prefix + "." + ln.path, 56 | value: ln.value, 57 | } 58 | ls = append(ls, l) 59 | } 60 | 61 | return ls, nil 62 | } 63 | 64 | func getLeafNodes(path, node string, m interface{}, l *[]leafNode) { 65 | if node != "" { 66 | if path != "" && node[:1] != "[" { 67 | path += "." 68 | } 69 | path += node 70 | } 71 | 72 | switch mm := m.(type) { 73 | case map[string]interface{}: 74 | for k, v := range mm { 75 | getLeafNodes(path, k, v, l) 76 | } 77 | case []interface{}: 78 | for i, v := range mm { 79 | getLeafNodes(path, "["+strconv.Itoa(i)+"]", v, l) 80 | } 81 | default: 82 | if mm != nil { 83 | n := leafNode{path, mm} 84 | *l = append(*l, n) 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /path.go: -------------------------------------------------------------------------------- 1 | package xj2go 2 | 3 | import ( 4 | "regexp" 5 | "strings" 6 | ) 7 | 8 | type strctNode struct { 9 | Name string 10 | Type string 11 | Tag string 12 | } 13 | 14 | // sort.Interface implementation 15 | type byName []strctNode 16 | 17 | func (a byName) Len() int { return len(a) } 18 | func (a byName) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 19 | func (a byName) Less(i, j int) bool { return strings.Compare(a[i].Name, a[j].Name) < 0 } 20 | 21 | type strctMap map[string][]strctNode 22 | 23 | func leafNodesToStrcts(typ string, nodes []leafNode) []strctMap { 24 | n := max(nodes) 25 | root := strings.Split((nodes)[0].path, ".")[0] 26 | 27 | exist := make(map[string]bool) 28 | 29 | strct := leafNodesToStruct(typ, root, root, nodes, &exist) 30 | strcts := []strctMap{} 31 | strcts = append(strcts, strct) 32 | 33 | es := []string{} 34 | for i := 0; i < n; i++ { 35 | for e := range exist { 36 | es = strings.Split(e, ".") 37 | root = es[len(es)-1] 38 | strct = leafNodesToStruct(typ, e, root, nodes, &exist) 39 | appendStrctNode(&strct, &strcts) 40 | } 41 | } 42 | 43 | return strcts 44 | } 45 | 46 | var leafNodesToStructRE = regexp.MustCompile(`\[\d+\]`) 47 | 48 | func leafNodesToStruct(typ, e, root string, nodes []leafNode, exist *map[string]bool) strctMap { 49 | strct := make(strctMap) 50 | var spath string 51 | re := leafNodesToStructRE 52 | for _, node := range nodes { 53 | if eld := strings.LastIndex(e, "."); eld > 0 { 54 | elp := e[eld:] // with . 55 | if pos := strings.Index(node.path, elp); pos > 0 { 56 | pre := node.path[:pos] 57 | rest := strings.TrimPrefix(node.path, pre) 58 | pre = re.ReplaceAllString(pre, "") // replace [1] with "" 59 | spath = pre + rest 60 | } 61 | } else { 62 | spath = node.path 63 | } 64 | 65 | chkpath := strings.TrimPrefix(spath, e) 66 | if !(chkpath == "" || chkpath[:1] == "[" || chkpath[:1] == ".") { 67 | continue 68 | } 69 | 70 | if len(spath) > len(e) && spath[:len(e)] == e { 71 | node.path = strings.TrimPrefix(spath, e) 72 | if node.path == "" { 73 | continue 74 | } 75 | node.path = strings.TrimPrefix(node.path, ".") 76 | if node.path[:1] == "[" { 77 | loc := re.FindStringIndex(node.path) 78 | node.path = strings.Replace(node.path, node.path[loc[0]:loc[1]], "", 1) 79 | node.path = strings.TrimPrefix(node.path, ".") 80 | } 81 | 82 | if node.path != "" { 83 | leafNodeToStruct(typ, e, root, &node, &strct, exist, re) 84 | } 85 | } 86 | } 87 | 88 | return strct 89 | } 90 | 91 | func leafNodeToStruct(typ, e, root string, node *leafNode, strct *strctMap, exist *map[string]bool, re *regexp.Regexp) { 92 | s := strings.Split(node.path, ".") 93 | if len(s) >= 1 { 94 | name := re.ReplaceAllString(s[0], "") 95 | ek := string(append(append([]rune(e), []rune(".")...), []rune(name)...)) 96 | sname := toProperCase(name) 97 | if !(*exist)[ek] { 98 | sn := strctNode{ 99 | Name: sname, 100 | } 101 | if re.MatchString(s[0]) { 102 | if len(s) > 1 { 103 | sn.Type = "[]" + sname 104 | } else { 105 | sn.Type = "[]" + toProperType(node.value) 106 | } 107 | } else { 108 | if len(s) > 1 { 109 | sn.Type = sname 110 | } else { 111 | sn.Type = toProperType(node.value) 112 | } 113 | } 114 | switch node.value.(type) { 115 | case xmlVal: 116 | sn.Tag = "`" + typ + ":\"" + name + ",attr\"`" 117 | default: 118 | sn.Tag = "`" + typ + ":\"" + name + "\"`" 119 | } 120 | 121 | /* 122 | if len(s) > 1 { 123 | if re.MatchString(s[0]) { 124 | sn.Type = "[]" + sname 125 | } else { 126 | sn.Type = sname 127 | } 128 | sn.Tag = "`" + typ + ":\"" + name + "\"`" 129 | } else { 130 | sn.Type = toProperType(node.value) 131 | switch node.value.(type) { 132 | case xmlVal: 133 | sn.Tag = "`" + typ + ":\"" + name + ",attr\"`" 134 | // case string: 135 | // sn.Tag = "`" + typ + ":\"" + name + "\"`" 136 | default: 137 | sn.Tag = "`" + typ + ":\"" + name + "\"`" 138 | } 139 | } 140 | */ 141 | (*strct)[root] = append((*strct)[root], sn) 142 | (*exist)[ek] = true 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /testjson/githubAPI.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1296269, 3 | "owner": { 4 | "login": "octocat", 5 | "id": 1, 6 | "avatar_url": "https://github.com/images/error/octocat_happy.gif", 7 | "gravatar_id": "somehexcode", 8 | "url": "https://api.github.com/users/octocat", 9 | "html_url": "https://github.com/octocat", 10 | "followers_url": "https://api.github.com/users/octocat/followers", 11 | "following_url": "https://api.github.com/users/octocat/following{/other_user}", 12 | "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", 13 | "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", 14 | "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", 15 | "organizations_url": "https://api.github.com/users/octocat/orgs", 16 | "repos_url": "https://api.github.com/users/octocat/repos", 17 | "events_url": "https://api.github.com/users/octocat/events{/privacy}", 18 | "received_events_url": "https://api.github.com/users/octocat/received_events", 19 | "type": "User", 20 | "site_admin": false 21 | }, 22 | "name": "Hello-World", 23 | "full_name": "octocat/Hello-World", 24 | "description": "This your first repo!", 25 | "private": false, 26 | "fork": false, 27 | "url": "https://api.github.com/repos/octocat/Hello-World", 28 | "html_url": "https://github.com/octocat/Hello-World", 29 | "clone_url": "https://github.com/octocat/Hello-World.git", 30 | "git_url": "git://github.com/octocat/Hello-World.git", 31 | "ssh_url": "git@github.com:octocat/Hello-World.git", 32 | "svn_url": "https://svn.github.com/octocat/Hello-World", 33 | "mirror_url": "git://git.example.com/octocat/Hello-World", 34 | "homepage": "https://github.com", 35 | "language": null, 36 | "forks_count": 9, 37 | "stargazers_count": 80, 38 | "watchers_count": 80, 39 | "size": 108, 40 | "default_branch": "master", 41 | "open_issues_count": 0, 42 | "has_issues": true, 43 | "has_wiki": true, 44 | "has_downloads": true, 45 | "pushed_at": "2011-01-26T19:06:43Z", 46 | "created_at": "2011-01-26T19:01:12Z", 47 | "updated_at": "2011-01-26T19:14:43Z", 48 | "permissions": { 49 | "admin": false, 50 | "push": false, 51 | "pull": true 52 | }, 53 | "subscribers_count": 42, 54 | "organization": { 55 | "login": "octocat", 56 | "id": 1, 57 | "avatar_url": "https://github.com/images/error/octocat_happy.gif", 58 | "gravatar_id": "somehexcode", 59 | "url": "https://api.github.com/users/octocat", 60 | "html_url": "https://github.com/octocat", 61 | "followers_url": "https://api.github.com/users/octocat/followers", 62 | "following_url": "https://api.github.com/users/octocat/following{/other_user}", 63 | "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", 64 | "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", 65 | "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", 66 | "organizations_url": "https://api.github.com/users/octocat/orgs", 67 | "repos_url": "https://api.github.com/users/octocat/repos", 68 | "events_url": "https://api.github.com/users/octocat/events{/privacy}", 69 | "received_events_url": "https://api.github.com/users/octocat/received_events", 70 | "type": "Organization", 71 | "site_admin": false 72 | }, 73 | "parent": { 74 | "id": 1296269, 75 | "owner": { 76 | "login": "octocat", 77 | "id": 1, 78 | "avatar_url": "https://github.com/images/error/octocat_happy.gif", 79 | "gravatar_id": "somehexcode", 80 | "url": "https://api.github.com/users/octocat", 81 | "html_url": "https://github.com/octocat", 82 | "followers_url": "https://api.github.com/users/octocat/followers", 83 | "following_url": "https://api.github.com/users/octocat/following{/other_user}", 84 | "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", 85 | "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", 86 | "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", 87 | "organizations_url": "https://api.github.com/users/octocat/orgs", 88 | "repos_url": "https://api.github.com/users/octocat/repos", 89 | "events_url": "https://api.github.com/users/octocat/events{/privacy}", 90 | "received_events_url": "https://api.github.com/users/octocat/received_events", 91 | "type": "User", 92 | "site_admin": false 93 | }, 94 | "name": "Hello-World", 95 | "full_name": "octocat/Hello-World", 96 | "description": "This your first repo!", 97 | "private": false, 98 | "fork": true, 99 | "url": "https://api.github.com/repos/octocat/Hello-World", 100 | "html_url": "https://github.com/octocat/Hello-World", 101 | "clone_url": "https://github.com/octocat/Hello-World.git", 102 | "git_url": "git://github.com/octocat/Hello-World.git", 103 | "ssh_url": "git@github.com:octocat/Hello-World.git", 104 | "svn_url": "https://svn.github.com/octocat/Hello-World", 105 | "mirror_url": "git://git.example.com/octocat/Hello-World", 106 | "homepage": "https://github.com", 107 | "language": null, 108 | "forks_count": 9, 109 | "stargazers_count": 80, 110 | "watchers_count": 80, 111 | "size": 108, 112 | "default_branch": "master", 113 | "open_issues_count": 0, 114 | "has_issues": true, 115 | "has_wiki": true, 116 | "has_downloads": true, 117 | "pushed_at": "2011-01-26T19:06:43Z", 118 | "created_at": "2011-01-26T19:01:12Z", 119 | "updated_at": "2011-01-26T19:14:43Z", 120 | "permissions": { 121 | "admin": false, 122 | "push": false, 123 | "pull": true 124 | } 125 | }, 126 | "source": { 127 | "id": 1296269, 128 | "owner": { 129 | "login": "octocat", 130 | "id": 1, 131 | "avatar_url": "https://github.com/images/error/octocat_happy.gif", 132 | "gravatar_id": "somehexcode", 133 | "url": "https://api.github.com/users/octocat", 134 | "html_url": "https://github.com/octocat", 135 | "followers_url": "https://api.github.com/users/octocat/followers", 136 | "following_url": "https://api.github.com/users/octocat/following{/other_user}", 137 | "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", 138 | "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", 139 | "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", 140 | "organizations_url": "https://api.github.com/users/octocat/orgs", 141 | "repos_url": "https://api.github.com/users/octocat/repos", 142 | "events_url": "https://api.github.com/users/octocat/events{/privacy}", 143 | "received_events_url": "https://api.github.com/users/octocat/received_events", 144 | "type": "User", 145 | "site_admin": false 146 | }, 147 | "name": "Hello-World", 148 | "full_name": "octocat/Hello-World", 149 | "description": "This your first repo!", 150 | "private": false, 151 | "fork": true, 152 | "url": "https://api.github.com/repos/octocat/Hello-World", 153 | "html_url": "https://github.com/octocat/Hello-World", 154 | "clone_url": "https://github.com/octocat/Hello-World.git", 155 | "git_url": "git://github.com/octocat/Hello-World.git", 156 | "ssh_url": "git@github.com:octocat/Hello-World.git", 157 | "svn_url": "https://svn.github.com/octocat/Hello-World", 158 | "mirror_url": "git://git.example.com/octocat/Hello-World", 159 | "homepage": "https://github.com", 160 | "language": null, 161 | "forks_count": 9, 162 | "stargazers_count": 80, 163 | "watchers_count": 80, 164 | "size": 108, 165 | "default_branch": "master", 166 | "open_issues_count": 0, 167 | "has_issues": true, 168 | "has_wiki": true, 169 | "has_downloads": true, 170 | "pushed_at": "2011-01-26T19:06:43Z", 171 | "created_at": "2011-01-26T19:01:12Z", 172 | "updated_at": "2011-01-26T19:14:43Z", 173 | "permissions": { 174 | "admin": false, 175 | "push": false, 176 | "pull": true 177 | } 178 | } 179 | } -------------------------------------------------------------------------------- /testjson/smartStreetsAPI.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "input_index": 0, 4 | "candidate_index": 0, 5 | "delivery_line_1": "1 N Rosedale St", 6 | "last_line": "Baltimore MD 21229-3737", 7 | "delivery_point_barcode": "212293737013", 8 | "components": { 9 | "primary_number": "1", 10 | "street_predirection": "N", 11 | "street_name": "Rosedale", 12 | "street_suffix": "St", 13 | "city_name": "Baltimore", 14 | "state_abbreviation": "MD", 15 | "zipcode": "21229", 16 | "plus4_code": "3737", 17 | "delivery_point": "01", 18 | "delivery_point_check_digit": "3" 19 | }, 20 | "metadata": { 21 | "record_type": "S", 22 | "zip_type": "Standard", 23 | "county_fips": "24510", 24 | "county_name": "Baltimore City", 25 | "carrier_route": "C047", 26 | "congressional_district": "07", 27 | "rdi": "Residential", 28 | "elot_sequence": "0059", 29 | "elot_sort": "A", 30 | "latitude": 39.28602, 31 | "longitude": -76.6689, 32 | "precision": "Zip9", 33 | "time_zone": "Eastern", 34 | "utc_offset": -5, 35 | "dst": true 36 | }, 37 | "analysis": { 38 | "dpv_match_code": "Y", 39 | "dpv_footnotes": "AABB", 40 | "dpv_cmra": "N", 41 | "dpv_vacant": "N", 42 | "active": "Y" 43 | } 44 | }, 45 | { 46 | "input_index": 0, 47 | "candidate_index": 1, 48 | "delivery_line_1": "1 S Rosedale St", 49 | "last_line": "Baltimore MD 21229-3739", 50 | "delivery_point_barcode": "212293739011", 51 | "components": { 52 | "primary_number": "1", 53 | "street_predirection": "S", 54 | "street_name": "Rosedale", 55 | "street_suffix": "St", 56 | "city_name": "Baltimore", 57 | "state_abbreviation": "MD", 58 | "zipcode": "21229", 59 | "plus4_code": "3739", 60 | "delivery_point": "01", 61 | "delivery_point_check_digit": "1" 62 | }, 63 | "metadata": { 64 | "record_type": "S", 65 | "zip_type": "Standard", 66 | "county_fips": "24510", 67 | "county_name": "Baltimore City", 68 | "carrier_route": "C047", 69 | "congressional_district": "07", 70 | "rdi": "Residential", 71 | "elot_sequence": "0064", 72 | "elot_sort": "A", 73 | "latitude": 39.2858, 74 | "longitude": -76.66889, 75 | "precision": "Zip9", 76 | "time_zone": "Eastern", 77 | "utc_offset": -5, 78 | "dst": true 79 | }, 80 | "analysis": { 81 | "dpv_match_code": "Y", 82 | "dpv_footnotes": "AABB", 83 | "dpv_cmra": "N", 84 | "dpv_vacant": "N", 85 | "active": "Y" 86 | } 87 | } 88 | ] -------------------------------------------------------------------------------- /testjson/topics.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "author": { 5 | "avatar_url": "https://avatars2.githubusercontent.com/u/2081487?v=4&s=120", 6 | "loginname": "lellansin" 7 | }, 8 | "author_id": "51f0f267f4963ade0e08f503", 9 | "content": "

\"untitled4.png\"

\n

饿了么大前端 Node.js 进阶教程

\n

因为 2016 年面试了很多做 Node.js 的同学,发现大部分做 Node 的同学都是前端转过来的,对后端的知识大多一片空白。所以很难招到比较好的 Node.js 服务端程序员(注意,不是全栈)。

\n

于是出于我们一贯的开源与分享精神,我们筹备了这个名字叫《如何通过饿了么 Node.js 面试》的开源的 Node.js 进阶教程。

\n

github 仓库地址:https://github.com/ElemeFE/node-interview

\n

导读

\n

本教程包含 2~3 年经验的 Node.js 服务端需要知道的知识点。

\n

需要注意的是, 并不适用于零基础的同学, 你需要有一定的 JavaScript/Node.js 基础, 并且有一定的工作经验. 另外本教程的重点更准确的说是服务端基础中 Node.js 程序员需要了解的部分.

\n

稳重将一些常见的问题划分归类, 每类标明涵盖的一些覆盖点, 并且列举几个常见问题, 通常这些问题都是 2~3 年工作经验需要了解或者面对的. 如果你对某类问题感兴趣, 或者想知道其中列举问题的答案, 可以通过该类下方的 阅读更多 查看更多的内容.

\n

整体上大纲列举的并不是很全面, 细节上覆盖率不高, 很多讨论只是点到即止, 希望大家带着问题去思考.

\n

Js 基础问题

\n
\n

与前端 Js 不同, 后端是直面服务器的, 更加偏向内存方面.

\n
\n\n

常见问题

\n\n

阅读更多

\n

模块

\n\n

常见问题

\n\n

阅读更多

\n

事件/异步

\n\n

常见问题

\n\n

阅读更多

\n

进程

\n\n

常见问题

\n\n

阅读更多

\n

IO

\n\n

常见问题

\n\n

阅读更多

\n

Network

\n\n

常见问题

\n\n

阅读更多

\n

OS

\n\n

常见问题

\n\n

阅读更多

\n

错误处理/调试/优化

\n\n

常见问题

\n\n

阅读更多

\n

测试

\n\n

常见问题

\n\n

阅读更多

\n

util

\n\n

常见问题

\n\n

阅读更多

\n

存储

\n\n

常见问题

\n\n

阅读更多

\n

安全

\n\n

常见问题

\n\n

阅读更多

\n

最后

\n

目前 repo 处于施工现场的情况,如果发现问题欢迎在 issues 中指出。如果有比较好的问题/知识点/指正,也欢迎提 PR。

\n

另外关于 Js 基础 是个比较大的话题,在本教程不会很细致深入的讨论,更多的是列出一些重要或者更服务端更相关的地方,所以如果你拿着《JavaScript 权威指南》给教程提 PR 可能不会采纳。本教程的重点更准确的说是服务端基础中 Node.js 程序员需要了解的部分。

\n
", 10 | "create_at": "2017-02-22T11:32:43.547Z", 11 | "good": true, 12 | "id": "58ad76db7872ea0864fedfcc", 13 | "last_reply_at": "2017-09-22T09:06:48.899Z", 14 | "reply_count": 231, 15 | "tab": "share", 16 | "title": "饿了么大前端 Node.js 进阶教程", 17 | "top": true, 18 | "visit_count": 122028 19 | }, 20 | { 21 | "author": { 22 | "avatar_url": "https://avatars3.githubusercontent.com/u/3118295?v=4&s=120", 23 | "loginname": "i5ting" 24 | }, 25 | "author_id": "54009f5ccd66f2eb37190485", 26 | "content": "

2017,我们来聊聊 Node.js

\n

版本帝?

\n

Chrome浏览器已经蹦到57版本了,是名副其实的版本帝,作为兄弟的Node.js也一样,1.0之前等了6年,而从1.0到8.0,只用了2年时间,这世界到底怎么了?

\n

我们就数一下

\n\n

\"lts-schedule.png\"

\n

整体来说趋于稳定

\n\n

已无性能优势?

\n

Node.js在2009年横空出世,可以说是纯异步获得高性能的功劳。所有语言几乎没有能够和它相比的,比如Java、PHP、Ruby都被啪啪的打脸。但是山一程,水一程,福祸相依,因为性能太出众,导致很多语言、编程模型上有更多探索,比如go语言产生、php里的swolo和vm改进等,大家似乎都以不支持异步为耻辱。后来的故事大家都知道了,性能都提到非常高,c10问题已经没人再考虑,只是大家实现早晚而产生的性能差距而已。

\n
\n

编程语言的性能趋于一样的极限,所以剩下的选择,只有喜好

\n
\n

那么在这种情况下,Node.js还有优势么?

\n\n
\n

误读:Node.js已无性能优势,它现在最强大的是基于npm的生态

\n
\n

上面是成本上的比较,其实大家把关注点都转移到基于npm的生态上,截止2017年2月,在npm上有超过45万个模块,秒杀无数。npm是所有的开源的包管理里最强大的,我们说更了不起的Node.js,其实npm居功甚伟,后面会有独立的章节进行阐述。

\n

来自www.modulecounts.com的各个包管理模块梳理的比较

\n

\"Screen\nnpm生态是Node的优势不假,可是说“Node.js没有性能优势”真的对么?这其实就是误读,Node.js的性能依然很好呀,而且它有npm极其强大的生态,可谓性能与生态双剑合璧,你说你死不死?

\n

异步和回调地狱?

\n
\n

天生异步,败也异步,成也异步

\n
\n

正因为异步导致了api设计方式只能采用error-first风格的回调,于是大家硬生生的把callback写成了callback hell。于是各种黑粉就冒出来,无非是一些浅尝辄止之辈。但也正因为回调地狱是最差实践,所以大家才不得不求变,于是thunk、promise等纷沓而至。虽然Promise/A+不完美,但对于解决回调地狱是足够的了。而且随着ES6等规范实现,引入generator、co等,让异步越来越近于同步。当async函数落地的时候,Node已经站在了同C#、Python一样的高度上,大家还有什么理由黑呢?

\n

本小节先科普一下异步流程里的各种概念,后面会有独立章节进行详细讲解

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
名称说明
callbackNode.js API天生就是这样的
thunk参数的求值策略
promise最开始是Promise/A+规范,随后成为ES6标准
generatorES6种的生成器,用于计算,但tj想用做流程控制
cogenerator用起来非常麻烦,故而tj写了co这个generator生成器,用法更简单
async函数原本计划进入es7规范,结果差一点,但好在v8实现了,所以node 7就可以使用,无须等es7规范落地
\n
\n

有时,将一件事儿做到极致,也许能有另一种天地

\n
\n

应用场景

\n

MEAN是一个Javascript平台的现代Web开发框架总称,它是MongoDB + Express +AngularJS + NodeJS 四个框架的第一个字母组合。它与传统LAMP一样是一种全套开发工具的简称。在2014和2015年喜欢讲这个,并且还有MEAN.js等框架,但今天已经过时,Node.js有了更多的应用场景。

\n

《Node.js in action》一书里说,Node所针对的应用程序有一个专门的简称:DIRT。它表示数据密集型实时(data-intensive real-time)程序。因为Node自身在I/O上非常轻量,它善于将数据从一个管道混排或代理到另一个管道上,这能在处理大量请求时持有很多开放的连接,并且只占用一小部分内存。它的设计目标是保证响应能力,跟浏览器一样。

\n

这话不假,但在今天来看,DIRT还是范围小了。其实DIRT本质上说的I/O处理的都算,但随着大前端的发展,Node.js已经不再只是I/O处理相关,而是更加的“Node”!

\n

这里给出Node.js的若干使用场景

\n\n

可以说目前大家能够看到的、用到的软件都有Node.js身影,当下最流行的软件写法也大都是基于Node.js的,比如PC客户端luin/medis采用electron打包,写法采用React+Redux。我自己一直的实践的【Node全栈】,也正是基于这种趋势而形成的。在未来,Node.js的应用场景会更加的广泛。更多参见sindresorhus/awesome-nodejs

\n

Web框架

\n

演进时间线大致如下:

\n\n

我们可以根据框架的特性进行分类

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
框架名称特性点评
Express简单、实用,路由中间件等五脏俱全最著名的Web框架
Derby.js && Meteor同构前后端都放到一起,模糊了开发便捷,看上去更简单,实际上上对开发来说要求更高
Sails、Total面向其他语言,Ruby、PHP等借鉴业界优秀实现,也是Node.js成熟的一个标志
MEAN.js面向架构类似于脚手架,又期望同构,结果只是蹭了热点
Hapi和Restfy面向Api && 微服务移动互联网时代Api的作用被放大,故而独立分类。尤其是对于微服务开发更是利器
ThinkJS面向新特性借鉴ThinkPHP,并慢慢走出自己的一条路,对于Async函数等新特性支持,无出其右
Koa专注于异步流程改进下一代Web框架
\n

对于框架选型

\n\n
\n

个人学习求新,企业架构求稳,无非喜好与场景而已

\n
\n

我猜大家能够想到的场景,大约如下

\n\n

如果只是做这些,和Java、PHP等就没啥区别了。如果再冠上更了不起的Node.js,就有点名不符实了。所以这里我稍加整理,看看和大家想的是否一样

\n

技术栈演进

\n

自从ES 2015(俗称ES 6)在Node.js 落地之后,整个Node.js开发都发生了翻天覆地的变化。自从0.10开始,Node.js就逐渐的加入了ES 6特性,比如0.12就可以使用generator,才导致寻求异步流程控制的tj写出了co这个著名的模块,继而诞生了Koa框架。但是在4.0之前,一直都是要通过flag才能开启generator支持,故而Koa 1.0迟迟未发布,在Node 4.0发布才发布的Koa 1.0。

\n
\n

2015年,成熟的传统,而2016年,变革开始

\n
\n

核心变更:es语法支持

\n\n

对比一下变革前后的技术栈选型,希望读者能够从中感受到其中的变化

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
分类2015年2016年选型原因
Web框架express 4.xkoa 1.0 && 2.0 (koa2.0刚发布不久,喜欢折腾的可以考虑)主要在流程控制上的便利,异步毕竟要时刻注意,心累
数据库mongoose(mongodb)mongoose(mongodb)对mongodb和mysql支持都一样,不过是mongodb更简单,足以应付绝大部分场景
异步流程控制bluebird(Promise/A+实现)bluebird(Promise/A+实现)1) Koa 1.0 使用co + generator 2) Koa 2.0 使用async函数流程控制演进路线,从promise到async函数,无论如何,promise都是基石,必要掌握的
模板引擎(视图层)ejs && jadejade && nunjucks给出了2种,一种可读性好,另一种简洁高效,都是非常好的
测试mochaavamocha是Node.js里著名的测试框架,但对新特性的支持没有ava那么好,而ava基于babel安装也要大上好多
调试node-inspectorVSCode在Node 6和7出来之后,node-inspector支持的不是那么好,相反VSCode可视化,简单,文件多时也不卡,特别好用
\n

预处理器

\n

前端预处理可分3种

\n\n

这些都离不开Node.js的支持,对于前端工程师来说,使用Node.js来实现这些是最方便不过的。

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
名称实现描述
模板引擎art\\mustache\\ejs\\hbs\\jade …上百种之多,自定义默认,编译成html,继而完成更多操作
css预处理器less\\sass\\scss\\rework\\postcss自定义语法规则,编译成css
js友好语言coffeescript、typescript自定义语法规则、编译成js
\n

跨平台

\n

跨平台指的是PC端、移动端、Web/H5

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
平台实现点评
Web/H5纯前端不必解释
PC客户端nw.js和electron尤其是atom和vscode编辑器最为著名,像钉钉PC端,微信客户端、微信小程序IDE等都是这样的,通过web技术来打包成PC客户端
移动端cordova(旧称PhoneGap),基于cordova的ionicframework这种采用h5开发,打包成ipa或apk的应用,称为Hybrid开发(混搭),通过webview实现所谓的跨平台,应用的还是非常广泛的
\n

构建工具

\n

说起构建工具,大概会想到make、ant、rake、gradle等,其实Node.js里有更多实现

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
名称介绍点评
jake基于coffeescript的大概都熟悉这个,和make、rake类似经典传统
gruntdsl风格的早期著名框架配置非常麻烦
gulp流式构建,不会产生中间文件,利用Stream机制,处理大文件和内存有优势,配置简单,只有懂点js就能搞定grunt的替代品
webpack + npm scripts说是构建工具有点过,但二者组合勉强算吧,loader和plugin机制还是非常强大的流行而已
\n

构建工具都不会特别复杂,所以Node.js世界里有非常多的实现,还有人写过node版本的make呢,玩的很嗨

\n

HTTP Proxy

\n\n

1)请求代理

\n

对于http请求复杂定制的时候,你是需要让Node.js来帮你的,比如为了兼容一个历史遗留需求,在访问某个CSS的时候必须提供HEADER才可以,如果放到静态server或cdn上是做不到的。

\n

2)SSR && PWA

\n

SSR是服务器端渲染,PWA是渐进式Web应用,都是今年最火的技术。如果大家用过,一定对Node.js不陌生。比如React、Vuejs都是Node.js实现的ssr。至于pwa的service-worker也是Node.js实现的。那么为啥不用其他语言实现呢?不是其他语言不能实现,而是使用Node.js简单、方便、学习成本低,轻松获得高性能,如果用其他语言,我至少还得装环境

\n

3)Api Proxy

\n

产品需要应变,后端不好变,一变就要设计到数据库、存储等,可能引发事故。而在前端相对更容易,前端只负责组装服务,而非真正对数据库进行变动,所以只要服务api粒度合适,在前端来处理是更好的。

\n

Api的问题

\n\n

所以,在前端渲染之余,加一层Api Proxy是非常必要的。淘宝早起曾公开过一张架构图,在今天看来,依然不过时

\n

\"taobao.jpg\"

\n\n

这里的Model Proxy其实就是我们所说的Api Proxy,这张图里只是说了结果,把聚合的服务转成模型,继而为HTTP服务提供Api。

\n

下面我们再深化一下Api Proxy的概念

\n

\"proxy.png\"

\n

这里的Node Proxy做了2件事儿,Api和渲染辅助。

\n\n

所以Api后面还有一个服务组装,在微服务架构流行的今天,这种服务组装放到Node Proxy里的好处尤其明显。既可以提高前端开发效率,又可以让后端更加专注于服务开发。甚至如果前端团队足够大,可以在前端建一个Api小组,专门做服务集成的事儿。

\n

Api服务

\n

说完了Proxy,我们再看看利益问题。Node.js向后端延伸,必然会触动后端开发的利益。那么Proxy层的事儿,前后端矛盾的交界处,后端不想变,前端又求变,那么长此以往,Api接口会变得越来越恶心。后端是愿意把Api的事儿叫前端的,对后端来说,只要你不动我的数据库和服务就可以。

\n

但是Node.js能不能做这部分呢?答案是能的 ,这个是和Java、PHP类似的,一般是和数据库连接到一起,处理带有业务逻辑的。目前国内大部分都是以Java、PHP等为主,所以要想吃到这部分并不容易。

\n\n

国内这部分一直没有做的很好,所以Node.js在大公司还没有很好的被应用,安全问题、生态问题、历史遗留问题等,还有很多人对Node.js的误解

\n\n

这些对于提供Api服务来说已经足够了。

\n

其他

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
用途说明前景
爬虫抢了不少Python的份额,整体来说简单,实用看涨
命令行工具写工具、提高效率,node+npm真是无出其右看涨
微服务与RPCNode做纯后端不好做,但在新项目和微服务架构下,必有一席之地看涨
微信公众号开发已经火了2年多了,尤其是付费阅读领域,还会继续火下去,gitchat就是使用Node.js做的,而且还在招人看涨
反向代理Node.js可以作为nginx这样的反向代理,虽然线上我们很少这样做,但它确确实实可以这样做。比如node-http-proxy和anyproxy等,其实使用Node.js做这种请求转发是非常简单的看涨
\n

更好的写法

\n

Async函数与Promise

\n\n

我整理了一张图,更直观一些。

\n

\"async-all.png\"

\n\n

结论:Promise是必须会的,那你为什么不顺势而为呢?

\n

推荐:使用Async函数 + Promise组合,如下图所示。

\n

\"async-suggest.png\"

\n

实践

\n

合理的结合Promise和Async函数是可以非常高效的,但也要因场景而异

\n\n

那么,在常见的Web应用里,我们总结的实践是,dao层使用Promise比较好,而service层,使用Async/Await更好。

\n

dao层使用Promise:

\n\n

这种用promisefyAll基本几行代码就够了,一般单一模型的操作,不会特别复杂,应变的需求基本不大。

\n

而service层一般是多个Model组合操作,多模型操作就可以拆分成多个小的操作,然后使用Await来组合,看起来会更加清晰,另外对需求应变也是非常容易的。

\n

ES.next

\n
\n

Node.js + ES.next = ♥

\n
\n

Flow && TypeScript

\n
\n

Type Systems Will Make You a Better JavaScript Developer

\n
\n

ES6模块

\n

现在ES6自带了模块标准, 也是JS第一次支持module(之前的CommonJS、AMD、CMD都不算), 但目前的所有Node.js版本都没有支持,目前只能用用Traceur、BabelJS, 或者TypeScript把ES6代码转化为兼容ES5版本的js代码,ES6模块新特性非常吸引人,下面简要说明。

\n

ES6 模块的目标是创建一个同时兼容CommonJS和AMD的格式,语法更加紧凑,通过编译时加载,使得编译时就能确定模块的依赖关系,效率要比 CommonJS 模块的加载方式高。而对于异步加载和配置模块加载方面,则借鉴AMD规范,其效率、灵活程度都远远好于CommonJS写法。

\n\n

ES6 模块标准只有2部分,它的用法更简单,你根本不需要关注实现细节:

\n\n

多模块管理器:Lerna

\n
\n

A tool for managing JavaScript projects with multiple packages.

\n
\n

https://lernajs.io/

\n

在设计框架的时候,经常做的事儿是进行模块拆分,继而提供插件或集成机制,这样是非常好的做法。但问题也随之而来,当你的模块模块非常多时,你该如何管理你的模块呢?

\n\n

法1虽然看起来干净,但模块多时,依赖安装,不同版本兼容等,会导致模块间依赖混乱,出现非常多的重复依赖,极其容易造成版本问题。这时法2就显得更加有效,对于测试,代码管理,发布等,都可以做到更好的支持。

\n

Lerna就是基于这种初衷而产生的专门用于管理Node.js多模块的工具,当然,前提是你有很多模块需要管理。

\n

你可以通过npm全局模块来安装Lerna,官方推荐直接使用Lerna 2.x版本

\n

更好的NPM替代品:Yarn

\n

Yarn是开源JavaScript包管理器,由于npm在扩展内部使用时遇到了大小、性能和安全等问题,Facebook携手来自Exponent、Google和Tilde的工程师,在大型JavaScript框架上打造和测试了Yarn,以便其尽可能适用于多人开发。Yarn承诺比各大流行npm包的安装更可靠,且速度更快。根据你所选的工作包的不同,Yarn可以将安装时间从数分钟减少至几秒钟。Yarn还兼容npm注册表,但包安装方法有所区别。其使用了lockfiles和一个决定性安装算法,能够为参与一个项目的所有用户维持相同的节点模块(node_modules)目录结构,有助于减少难以追踪的bug和在多台机器上复制。

\n

Yarn还致力于让安装更快速可靠,支持缓存下载的每一个包和并行操作,允许在没有互联网连接的情况下安装(如果此前有安装过的话)。此外,Yarn承诺同时兼容npm和Bower工作流,让你限制安装模块的授权许可。

\n

2016年10月份, Yarn在横空出世不到一周的时间里,github上的star数已经过万,可以看出大厂及社区的活跃度,以及解决问题的诚意,大概无出其右了!

\n

替换的原因

\n\n

与hack npm限制的做法相反,Facebook编写了Yarn

\n\n
\n

Yarn, which promises to even give developers that don’t work at Facebook’s scale a major performance boost, still uses the npm registry and is essentially a drop-in replacement for the npm client.

\n
\n

很多人说和ruby的gem机制类似,都生成lockfile。确实是一个很不错的改进,在速度上有很大改进,配置cnpm等国内源来用,还是相当爽的。

\n

友好语言

\n\n

总结

\n
\n

坦诚的力量是无穷的

\n
\n

Node.js是为异步而生的,它自己把复杂的事儿做了(高并发,低延时),交给用户的只是有点难用的Callback写法。也正是坦诚的将异步回调暴露出来,才有更好的流程控制方面的演进。也正是这些演进,让Node.js从DIRT(数据敏感实时应用)扩展到更多的应用场景,今天的Node.js已经不只是能写后端的JavaScript,已经涵盖了所有涉及到开发的各个方面,而Node全栈更是热门种的热门。

\n

直面问题才能有更好的解决方式,Node.js你值得拥有!

\n
", 27 | "create_at": "2017-04-13T02:41:41.818Z", 28 | "good": true, 29 | "id": "58eee565a92d341e48cfe7fc", 30 | "last_reply_at": "2017-09-18T23:37:46.631Z", 31 | "reply_count": 137, 32 | "tab": "share", 33 | "title": "2017,我们来聊聊 Node.js", 34 | "top": true, 35 | "visit_count": 58364 36 | }, 37 | { 38 | "author": { 39 | "avatar_url": "https://avatars3.githubusercontent.com/u/3118295?v=4&s=120", 40 | "loginname": "i5ting" 41 | }, 42 | "author_id": "54009f5ccd66f2eb37190485", 43 | "content": "

本来提供开发api,目的是为了开发第三方应用或客户端,如果大家用来学习也是好的,但现在很多人太过分了,随意发帖,at,严重影响了社区的用户,故而决定开始严查

\n

以下情况,直接封号

\n\n

欢迎大家监督

\n

封号

\n\n
\n

20170601更新

\n

https://cnodejs.org/?tab=dev 目前开了一个『客户端测试』专区,以后开发新客户端的同学,帖子直接发到这个专区去。tab 的值是 dev。

\n

\"image.png\"

\n
", 44 | "create_at": "2017-05-27T06:07:49.278Z", 45 | "good": false, 46 | "id": "592917b59e32cc84569a7458", 47 | "last_reply_at": "2017-09-18T12:47:14.495Z", 48 | "reply_count": 86, 49 | "tab": "share", 50 | "title": "测试请发到客户端测试专区,违规影响用户的,直接封号", 51 | "top": true, 52 | "visit_count": 20696 53 | }, 54 | { 55 | "author": { 56 | "avatar_url": "//gravatar.com/avatar/d00d8e3461257418a62b1cb7abeea85a?size=48", 57 | "loginname": "xinyu198736" 58 | }, 59 | "author_id": "50725bc001d0b80148f48097", 60 | "content": "

简单的总结

\n

本期 NodeParty 又有几个新的突破,先跟大家介绍下。

\n

1. 我们有钱了!

\n

本次我们有一个突破性的想法,并将其付诸实践,那就是 NodeParty 开源基金会。

\n

NodeParty 开源基金会是一个新型的、纯公益性质的、开源的基金会,建立此开源基金会的初衷是利用社区活动或者企业赞助产生的资金,不计回报地回馈给社区。例如支持社区相关活动的支出、支持在社区有突出贡献的个人开发者,以及在社区有贡献的小型初创公司等。

\n

目前,在没有大规模宣传的背景下,基金会已经收到 32 笔共 21900.76 元赞助款,其中包含一些企业的大额赞助、软件组织以及诸多个人开发者的赞助。

\n

这件事情最有意义的事情在于,我们以后的社区活动有了可以自己支配的资金,以此,我们可以不必受制于资金相关条件的制约。

\n

欢迎更多企业以赞助者的身份合作(提供一些宣传上的支持)。

\n

我们有严谨的流程来保证资金的安全和公正,详见基金会文档

\n

\n

2. 我们有了更多伙伴的支持

\n

之前几期的活动,其实办下来流程、服务、气氛上都有些尴尬,因为基本是我在组织,团队的几个同学做一些行政上的支持,很多东西不是我们擅长的,所以搞起来又吃力效果又不好。

\n

但是这次不一样了,有更多的人加入了我们的组织者群体,而且我们还有一个新的基金会组织,大家都在尽心尽力为活动付出,在此特别感谢一下这些人。

\n\n

3. 其他

\n\n

本场分享 Slide

\n\n

下期活动

\n

初步打算下次活动安排在 2 个月之后,敬请期待。

\n

现场照片

\n

\n\n\n\n\n\n\n

\n
", 61 | "create_at": "2017-08-24T08:40:01.807Z", 62 | "good": true, 63 | "id": "599e90e1bae6f2ed6f7e4cd4", 64 | "last_reply_at": "2017-09-13T05:23:41.635Z", 65 | "reply_count": 26, 66 | "tab": "share", 67 | "title": "杭州 NodeParty 第四期总结(slide、现场照片)", 68 | "top": true, 69 | "visit_count": 7049 70 | }, 71 | { 72 | "author": { 73 | "avatar_url": "https://avatars3.githubusercontent.com/u/26543174?v=4&s=120", 74 | "loginname": "Bob-huang-gdut" 75 | }, 76 | "author_id": "59760db73f0ab31540ed4d04", 77 | "content": "

仅仅是个测试哦

\n
", 78 | "create_at": "2017-09-23T09:06:27.107Z", 79 | "good": false, 80 | "id": "59c62413d7cbefc51196465e", 81 | "last_reply_at": "2017-09-23T09:16:12.128Z", 82 | "reply_count": 1, 83 | "tab": "ask", 84 | "title": "Api测试创建主题", 85 | "top": false, 86 | "visit_count": 12 87 | }, 88 | { 89 | "author": { 90 | "avatar_url": "https://avatars3.githubusercontent.com/u/26543174?v=4&s=120", 91 | "loginname": "Bob-huang-gdut" 92 | }, 93 | "author_id": "59760db73f0ab31540ed4d04", 94 | "content": "

仅仅是个测试哦

\n
", 95 | "create_at": "2017-09-23T09:05:11.246Z", 96 | "good": false, 97 | "id": "59c623c7c5ddc93d29364efa", 98 | "last_reply_at": "2017-09-23T09:05:11.246Z", 99 | "reply_count": 0, 100 | "tab": "ask", 101 | "title": "Api测试创建主题", 102 | "top": false, 103 | "visit_count": 6 104 | }, 105 | { 106 | "author": { 107 | "avatar_url": "https://avatars3.githubusercontent.com/u/26543174?v=4&s=120", 108 | "loginname": "Bob-huang-gdut" 109 | }, 110 | "author_id": "59760db73f0ab31540ed4d04", 111 | "content": "

仅仅是个测试哦

\n
", 112 | "create_at": "2017-09-23T09:04:52.782Z", 113 | "good": false, 114 | "id": "59c623b4c5ddc93d29364ef9", 115 | "last_reply_at": "2017-09-23T09:04:52.782Z", 116 | "reply_count": 0, 117 | "tab": "ask", 118 | "title": "Api测试创建主题", 119 | "top": false, 120 | "visit_count": 10 121 | }, 122 | { 123 | "author": { 124 | "avatar_url": "https://avatars3.githubusercontent.com/u/26543174?v=4&s=120", 125 | "loginname": "Bob-huang-gdut" 126 | }, 127 | "author_id": "59760db73f0ab31540ed4d04", 128 | "content": "

仅仅是个测试哦

\n
", 129 | "create_at": "2017-09-23T09:04:06.539Z", 130 | "good": false, 131 | "id": "59c62386c5ddc93d29364ef8", 132 | "last_reply_at": "2017-09-23T09:04:06.539Z", 133 | "reply_count": 0, 134 | "tab": "ask", 135 | "title": "Api测试创建主题", 136 | "top": false, 137 | "visit_count": 10 138 | }, 139 | { 140 | "author": { 141 | "avatar_url": "https://avatars2.githubusercontent.com/u/16136702?v=4&s=120", 142 | "loginname": "laoqiren" 143 | }, 144 | "author_id": "57b99bc2dcaeb5d932db220a", 145 | "content": "

仿Github Oauth功能,Github: https://github.com/laoqiren/Oauth-example

\n

Oauth-example

\n

Oauth example,仿Github Oauth,包括完整的授权流程,注册APP,修改APP信息,自定义并动态改变授权范围,测试客户端等。

\n

Server端线上地址: http://oauth.luoxia.me,可以注册自己的APP.

\n

功能

\n\n

运行流程

\n

准备: 安装Server端和Client端依赖,修改Server配置文件(redis相关),启动reids服务,启动Server:

\n
npm install\ncd client && npm install\nredis-server redis.conf\ncd .. && npm run server\n

step1 访问localhost:3000,发现尚未注册任何APP,选择注册第三方APP\n\"http://7xsi10.com1.z0.glb.clouddn.com/noApps.png\"

\n

step2 进入http://localhost:3000/api/registerApp,进行注册APP\n\"http://7xsi10.com1.z0.glb.clouddn.com/registerApp.png\"

\n

step3 注册成功,返回首页,可查看/修改授权的APP信息\n\"http://7xsi10.com1.z0.glb.clouddn.com/hasApps.png\"

\n

step4 复制上面得到的clientIdclientSecret,进行客户端配置(client/config.js):

\n
exports.appInfo = {\n    clientId: '027ea2ee-2182-588a-8203-fbeaa88295a0',\n    name: '宇宙最强APP',\n    description: '你好啊,我的小可爱!',\n    clientSecret: '0144a8eb475a55217c24048388041954',\n    redirectUri: '/login/callback'\n}\n

step5 启动client,访问localhost:4000进行授权登录:

\n
npm run client\n

\"http://7xsi10.com1.z0.glb.clouddn.com/clientLog.png\"

\n

step6 获得授权,尝试访问API,上例中,获取用户秘密的API未开放,所以会获取失败\n\"http://7xsi10.com1.z0.glb.clouddn.com/secretForbbiden.png\"

\n

step7 进入Server修改App信息,这里修改授权范围(scope),开放用户秘密,这会导致该App的token失效,需重新授权,接着再尝试访问秘密API,权限通过。

\n

step8注册新App,会导致clientSecret,clientId变化,需重新配置客户端;删除APP,收回Token,都会导致APP授权失效。

\n

TODOs

\n\n
", 146 | "create_at": "2017-09-19T14:00:15.811Z", 147 | "good": false, 148 | "id": "59c122efd7cbefc511964503", 149 | "last_reply_at": "2017-09-23T08:56:53.539Z", 150 | "reply_count": 2, 151 | "tab": "share", 152 | "title": "仿Github的Oauth功能例子", 153 | "top": false, 154 | "visit_count": 487 155 | }, 156 | { 157 | "author": { 158 | "avatar_url": "https://avatars0.githubusercontent.com/u/1798364?v=4&s=120", 159 | "loginname": "helloyou2012" 160 | }, 161 | "author_id": "55e3e9f422d29223058b65d3", 162 | "content": "

前言

\n

NTFS 全称是 New Technology File System,是微软随 Windows NT 系统开发的文件系统,由于版权的问题 macOS 没有开放对 NTFS 的写权限。但是可以通过重新挂载打开写权限,方法如下:

\n
    \n
  1. 列出所有的外接存储设备
  2. \n
\n
$ diskutil list external\n/dev/disk2 (external, physical):\n #:                       TYPE NAME                    SIZE       IDENTIFIER\n 0:     FDisk_partition_scheme                        *15.9 GB    disk2\n 1:               Windows_NTFS DBand                   15.9 GB    disk2s1\n
    \n
  1. 解挂载然后重新挂载
  2. \n
\n
# 解挂载\n$ sudo diskutil umount /dev/disk2s1\n# 重新挂载\n$ sudo mount -o rw,auto,nobrowse -t ntfs /dev/disk2s1 /Volumes/DBand\n# 打开\n$ open /Volumes/DBand\n

写个小工具

\n
\n

虽然上述方法比较简单但是对于普通人操作起来还是比较麻烦的,而且每次都要输命令也比较麻烦。因此打算写个可视化的小工具。

\n
\n

第一步:列出所有的外接存储设备

\n

通过 diskutil 可以查看存储设备的基本信息,我们简单的把他封装了下,项目地址:https://github.com/d-band/ls-usb

\n
const getMediaList = require('ls-usb');\n\ngetMediaList()\n  .then(data => {\n    console.log(JSON.stringify(data, null, '  '));\n  });\n\n/*[{\n  "udid": "disk2",\n  "name": "UDisk",\n  "type": "Generic",\n  "node": "/dev/disk2",\n  "size": "15.9 GB",\n  "size_bytes": 4026531840,\n  "volumes": [{\n    "udid": "disk2s1",\n    "mount": "/Volumes/DBand",\n    "size": "15.9 GB",\n    "size_bytes": 15938355200,\n    "name": "DBand",\n    "node": "/dev/disk2s1",\n    "fs_type": "ntfs",\n    "fs_name": "NTFS",\n    "free": "15.9 GB",\n    "free_bytes": 15938355200,\n    "writable": false\n  }]\n}]*/\n

第二步:写个基于 Electron 的小工具

\n

项目地址:https://github.com/d-band/disky

\n

主要技术点:

\n\n
# 全局安装 dool\n$ npm i dool -g\n\n# 安装项目依赖\n$ npm i\n\n# 运行 dool 编译 electron-renderer 相关文件\n$ npm run dev\n\n# 再打开一个 Tab 启动 electron\n$ npm run start\n

附个图

\n

\"image.png\"

\n
", 163 | "create_at": "2017-09-21T07:28:15.797Z", 164 | "good": false, 165 | "id": "59c36a0fd7cbefc5119645a7", 166 | "last_reply_at": "2017-09-23T07:58:17.834Z", 167 | "reply_count": 5, 168 | "tab": "share", 169 | "title": "Make NTFS writable on macOS", 170 | "top": false, 171 | "visit_count": 366 172 | }, 173 | { 174 | "author": { 175 | "avatar_url": "https://avatars3.githubusercontent.com/u/3118295?v=4&s=120", 176 | "loginname": "i5ting" 177 | }, 178 | "author_id": "54009f5ccd66f2eb37190485", 179 | "content": "

我和我媳妇是在李天放和王然组织的jihua.fm的线下会上认识的,小猴子是计划的用户,我媳妇是刷了两次六级之后,觉得太无聊,被小猴子拉去的作伴的。那时候,我以为天放技术很好,这个活动应该是技术交流类的,所以我也去了,而且是第一个到后海,在那个二楼坐下。不一会,小猴子和我媳妇就来了,确认是一个活动后,她们也坐下来了,巧的是我媳妇拿着一瓶矿泉水打不开瓶盖,于是我帮了个小忙,似乎是老天给的机会,我也认真的看了看她,当时就觉得她是一个特别文静的小姑娘。

\n

后来大家都到了,玩了一下午的杀人游戏,记不住太多细节了,只记得大家分别的时候,她和小猴子在等地铁,一双白白的旅游鞋,长长的头发,特别文静的等地铁,估计她也没记得我。

\n

于是我就找小猴子要她的联系方式,然后聊啊聊,后来在北化和平西桥校区见面了,然后一起去崇文门吃了麻辣诱惑,后来就在一起了。那一年,她大三,我还记得见面的时候,我还曾逗她:“脚踏一双旅游鞋,梳着长头发,手拿火尖枪,身系浑天绫,脖戴乾坤圈。。。活脱脱一个那吒”,那天阳光很好,在毛主席像下,她美极了。

\n

我找到的记录是(2012-3-27 13:15)(5年前)给她写了下面这首诗

\n
有一种感情深陷而不能拔\n有一种美丽望尽而不能语\n许是人间绝美之情,不过爱情\n我总是寻找\n却与你不期而遇,揽羞涩入怀\n

我记得她在心形的便签纸上写了它,还署名桑,大概她也喜欢吧!

\n

喜欢一个人就是喜欢,不需要什么理由,你看到她第一眼的时候,就知道这个女人会注定陪我一生。我们也没什么物质追求,只是简简单单的,在一起,很开心,想结婚,想和她过一辈子。

\n

很多人都知道我的网名是i5ting,这里的ting就是她,张婷的婷!我在很多公众场合都是这样自我介绍的:“大家好,我是i5ting,婷是我媳妇的名字”,然后大家就笑了。

\n
写给张婷\n\n我们的愿望很简单\n在一起,会快乐\n像一双小鸟儿,在空中飞翔\n贺兰山下栖止\n为幸福筑巢\n\n我用了整整一年半的时间\n追求你,过往多少\n虽然我只是见证了你\n淳朴,善良和小心的日子\n\n我会故意的逗你\n我会很讨厌的带你逛街\n我会在喝醉酒的时候\n向你哭诉\n我会在离开你二天\n更年期一样的烦躁\n像小孩子一样,装可爱或者卖萌\n\n我喜欢你的小门牙\n我鼓励你做一些你不喜欢的事儿\n我愿把你介绍给所有人\n“你拿得出手,我们一起成长\n他们必须见证历史”\n\n我已过了说蜜语甜言的年龄\n我很明白心底的爱\n作为一个男人,我要的\n仅仅是老婆孩子热炕头的梦想\n这世间繁华与热闹\n都是人为事故\n我只想和你一起热闹\n城市和工作都可以通过努力获取更多\n而你,只有一个\n\n我没有忘了追求与事业\n而你是我更重要的人\n可以在你的世界里实现追求与事业\n有什么比这更美好呢?\n\n活着,只为你的爱\n嫁给我吧\n没有承诺\n不想让你掉眼泪\n不想你再难过\n\n嫁给我吧\n我已经准备好世界\n和你一起慢慢变老的时光\n生老病死,不离不弃\n

后来我们就在银川买了房子,期房,到15年底才交房,那时候我在天津创业,她最初也和我去了,但那时候的我是猪油蒙了心,每天只是工作,连晚饭后下楼陪她走走的时间都不给她,我能想到她是多么伤心,一个人,在晚上,一个天津很偏僻的地方,明明我在,却不能陪她。。。结婚后,她就回银川了,一个月也难见一次,她给我了很大的自由,让我能够尽力的去工作。后来因为股权问题被合伙人坑死的时候,我真是欲哭无泪,我不知道怎么告诉她,常常彻夜不眠,所有辛苦不怕,所以付出不怕,唯一怕的就是没脸面对她。

\n

我回了银川,怀了宝宝,很久之后我才敢和她坦白,她也没有问我,她那么聪明的人,大概也猜到是怎么回事儿了。她常说:“你在就好”,一年半的付出,累死累活,分文没有,又有多少人能淡定呢?这大概就是聪明人的爱吧,她什么都知道。我也知道,却什么都不敢多说,怕流泪,怕说破心酸。我常在想,能有这样一个包容我的爱我的女人,我是何德何能啊!

\n

我是一个东北人,起初来宁夏是不习惯的,在这边,几乎很多菜都是拿西红柿炒的。第一次见丈母娘的时候,家里特意收拾了一下,门窗的漆都是新刷的,丈母娘做了一桌子菜,各种肉,我一口也没动,我是一个素食主义者,大概有12年左右,当时丈母娘好不乐意,后来多去几次就好了,做菜更简单,有啥吃啥就好了,也省的麻烦。

\n

因为一个人喜欢一个地方,是不得已,也是心甘情愿,后来,我写了一首《心疼》的诗歌,是因为那边方言,心疼是美丽的意思。姑娘是美丽的,地方自然也是美丽的,大概都是这样的爱屋及乌之情吧。

\n
心疼\n\n陌生的省份陌生的人\n你明明很害怕,也很期待\n他们会是你生命里的影子\n他们也会是你最亲近的人\n\n在那里,没有大酱\n没有秋天的饭包土豆\n在那里,夸人美丽叫“心疼”\n夸人聪明不能说“奸”\n\n有太多不一样的世界呀\n你会习惯,习惯菜里的西红柿\n习惯听不懂的方言\n习惯一些人变成亲人\n\n其实不是因为习惯而习惯\n这一切的一切都是因为一个人\n因为一个心疼的姑娘\n因为姑娘的菜、方言,亲人而心庝\n

对于妻子,我有很多愧疚,最多的就是聚少离多,创业也好,迫于生计也好,是我的不甘于小城市的生活也好,一直都没能在银川真正的定居。台湾诗人洪秀丽曾说过:“寂寞给海一支笛,横竖吹着无题。不堪栖止的海啊,不堪为家”,我心里也很明白,却还迟迟的不想做。

\n

爱在远方是对称的影子,你在想她的时候,她也一定在想你,两人的辛酸才是夜里的雨,不眠不休。

\n
爱在远方是对称的影子\n\n知道今夜有雨\n知道你一个人在大大的空房子里\n知道今夜闪电必将入你的睡梦\n知道蜷缩着等一个拥抱\n\n我的胸怀空空荡荡\n我瞪大眼睛,望不尽的屋子里\n只有孤单的灯光和墙壁的声响\n神一样的存在,无法抓紧\n

在一起,很多事儿都很开心,似乎不开心的时候非常少。大多数时候都是我欺负她,有时不讲理,不理她,她来跟我道歉,以至于我特别痛恨自己。没人的时候,自己静心想想,除了妻子,还有谁能这样让着你?每个人在这社会里都特别无助,争名夺利,是是非非,有一个温暖的家才是最美好的事儿。

\n
许久不见的爱情\n\n媳妇儿去大姨妈家\n不知发生了什么\n只是肚子痛,卧床\n像是在谋划一件大事\n\n我服侍她躺下\n便去了另一个房间\n以为她一定需要休息\n需要空间去准备\n\n忽的,她跑过来\n像是要哭了一样\n跟我说:戒指丢了...\n像丢了爱情一样,害怕\n\n我第一时间抱住她\n宝贝,不怕,先别急\n翻一下卧室,客厅,购物袋里\n翻出我们许久不见的爱情\n

宝宝还没出生,我就到北京上班了,在qunar工作的那段时间里,我是最嚣张的,请了好多假。每周五晚上回银川,周日晚上回北京,卧铺车厢无情的空调,吹得我中风几次。可是,还是要回家。老婆孩子都在地方,才是家。

\n

最难过的是周一到周五,只能看媳妇通过微信发来宝宝的视频,一遍一遍的看,一遍一遍的想哭。

\n
媳妇发来宝宝的视频\n\n媳妇发来宝宝的视频\n只有九秒,一遍一遍的看\n宝宝二个月了\n已经会吃衣袖了\n\n媳妇发来宝宝的的视频\n只有九秒,既短又长\n已经有六天没有见到了\n愈加想念\n\n他不会说话,只会哭喊\n他不会动,虽然很想动\n他不乖,除了吃奶的时候\n他还不能陪爸爸一起玩\n\n媳妇发来宝宝的视频\n只有九秒,一遍一遍的看\n一路上,一遍一遍的看\n一遍一遍的想笑,想哭。。。\n\n“路人一定都觉得爸爸是个可爱的神经病”\n

我们结婚的时候什么都没有,只买了戒指,出去玩了两周。没有彩礼,连摆酒就是我老丈人出的。我们在一起,她也没享什么福,我知道她最大的想法,就是一家人在一起。我也在努力实现。前段时间有个电影,叫《从你的全世界路过》,看了还挺有感触的。想到妻子,我没有对她太好。当时在火车上,火车轨道摩擦的声音甚是刺耳,像刀子挖心一样,”我唯一剩的一点良心就是还能想你。我真的好想对你更好,让你和孩子过得更好“。

\n
《从你的全世界悔过》\n\n没有花房,也没有教堂\n没有那美丽的稻城亚丁\n也没有单膝跪地的浪漫\n一切都应是女孩最美好的梦\n\n没有承诺,也没有温柔\n没有锦衣玉食的富足\n也没有呵护备至\n一切都应是女孩最美好的梦\n\n当我在火车上一个人\n被电影里的情节感动\n我只有深深地愧疚\n对比窗外没有颜色的早春\n\n当我一个人在火车上\n听不见一点儿外面的哽咽声\n没能给你的童话\n尽余生给你和孩子补上\n

关于爱情,就是相守。细心的体会和爱着对方,你要为爱敏感,不是诗歌表达了什么,是你内心最真的感受才是真的爱。承认这世界在变化,但我能做的就是让事情往好的方向走,我爱你,我尽全力爱你和孩子。

\n

更新狼叔最近的3个动态

\n

1)受 100offer 邀请做一场知乎live《狼叔:Node.js 源码是如何执行的?》

\n

上次北京729的node party拿了人家赞助,不好意思不做。我自己认为这场live干货还是比较多的,喜欢可以来听

\n

本次 Live 主要包括以下内容(时间是2017-08-30 20:30)

\n
    \n
  1. 如何调试 node 8源码( IDE 选择和断点调试)
  2. \n
  3. 编译步骤
  4. \n
  5. 核心流程解析,构造 process 对象,加载环境
  6. \n
  7. bootstrap_node.js 与 commonjs 规范
  8. \n
  9. 举例 fs.readFile Api 调用过程
  10. \n
  11. 如何通过 node 学习成为更好的前端工程师
  12. \n
\n

地址 https://www.zhihu.com/lives/878296775587409920

\n

2)深圳9月16日腾讯举办IMWeb大会,狼叔有主题演讲《更了不起的Node.js》

\n

IMWebConf 2017前端大会的发起方是腾讯公司,组织者是腾讯公司最专业的前端团队之一IMWeb团队。\n作为国内前端圈一年一度的,有一定影响力的专业前端技术交流大会,IMWeb Conf已经成功举办五届,累计探讨的议题超过上百个,线上线下参会者数千人,累计影响前端爱好者超过数十万人。

\n

今年,IMWebConf 2017 将于2017年9月16日在深圳科兴国际会议中心召开。会议时间为一天,议题涵盖前端工程化,性能优化,Node.js,前端框架,安全等前沿方向。大会将设一个主会场和三个分会场(Node.js分会场、框架工具性能分会场、综合分会场)。

\n

http://imweb.io/topic/5975bf1152e1c21811630619\nhttp://2017.imweb.io/#home

\n

3)《更了不起的Node.js》一书已进入编辑校稿阶段

\n

做这些演讲,有一部分也是为了预热,希望大家能够喜欢。

\n

最后,分享一首《你不配爱情》,是的,我不配,老婆做的比我好,我还需要修炼。

\n
你不配爱情\n\n爱情细细的\n小雨一样\n春天一样\n\n爱情恐怖的\n雨后路上的蚯蚓一样\n春天的遗体一样\n\n你不配爱情\n你不懂雨为什么落下\n让太阳晒干蚯蚓\n\n你不知道坐守春天的美好\n让它慢慢的老成\n皱纹一样的秋\n

在七夕,我没能在家陪她和孩子。。。都是悲伤的故事,不说也罢。如果大家觉得写的还不错,可以转给朋友看看,最后祝大家开心,晚安!朋友们!

\n
", 180 | "create_at": "2017-08-28T12:44:29.492Z", 181 | "good": false, 182 | "id": "59a4102d9e95202d08c91d45", 183 | "last_reply_at": "2017-09-23T07:02:40.308Z", 184 | "reply_count": 29, 185 | "tab": "share", 186 | "title": "从你的全世界悔过", 187 | "top": false, 188 | "visit_count": 2076 189 | }, 190 | { 191 | "author": { 192 | "avatar_url": "https://avatars1.githubusercontent.com/u/7269202?v=4&s=120", 193 | "loginname": "pangguoming" 194 | }, 195 | "author_id": "545870576537f4d52c414eb6", 196 | "content": "

Neo4j简介

\n

Neo4j是一个高性能的,NOSQL图形数据库,它是一个嵌入式的、基于磁盘的、具备完全的事务特性的Java持久化引擎,但是它将结构化数据存储在网络(从数学角度叫做拓扑图)上而不是表中。Neo4j也可以被看作是一个高性能的图引擎,该引擎具有成熟数据库的所有特性。程序员工作在一个面向对象的、灵活的网络结构下而不是严格、静态的表中——但是他们可以享受到具备完全的事务特性、企业级的数据库的所有好处。\nNeo4j因其嵌入式、高性能、轻量级等优势,越来越受到关注。\n\"image.png\"\n\"northwind_import.png\"

\n

《Neo4j权威指南》出版背景

\n

本书的写作始于2016年12月,历经数月,现终于问世,是整个写作团队齐心协力、日夜耕耘的结晶。这支团队在大数据和图数据库领域具有丰富的实战经验。他们是张帜(微云数聚创始人)、庞国明(Neo4j中文社区创始人)、胡佳辉(数之联软件架构师)、赵炳(北京邮电大学研究生)、陈振宇(中科院中美联合培养博士)、苏亮(国防科技大学计算机学院博士)、李敏(中科院计算数学博士)、高兴宇(中科院中新联合培养博士)、薛述强(华为公司高级工程师)和董琴洁(微软 Office 专家)。

\n

\"image.png\"

\n

在本书的编写过程中,得到了各界朋友的倾力支持。请允许我代表整个写作团队在此表示由衷的感谢!

\n

主要内容

\n

本书基于Neo4j 3.1版本编写,共分九章,外加两个附录,涵盖基本概念、基础入门、查询语言、开发技术、管理运维、集群技术、应用案例、高级应用、中文扩展、配置设置、内建过程等内容。各章简要介绍如下:

\n\n

相关链接

\n\n
", 197 | "create_at": "2017-09-13T09:41:16.680Z", 198 | "good": false, 199 | "id": "59b8fd3c2db872766ed747e1", 200 | "last_reply_at": "2017-09-23T06:53:56.478Z", 201 | "reply_count": 4, 202 | "tab": "share", 203 | "title": "鄙人不才,参与出版《Neo4j权威指南》一书,介绍图形化数据库 Neo4j,现在正式出版。帮助大家了解 图形化数据库", 204 | "top": false, 205 | "visit_count": 762 206 | }, 207 | { 208 | "author": { 209 | "avatar_url": "https://avatars0.githubusercontent.com/u/16443836?v=4&s=120", 210 | "loginname": "ZhenhuiZhang" 211 | }, 212 | "author_id": "59c4f25a8812ce51127a9133", 213 | "content": "

\"微信图片_20170922192213.png\"\"微信图片_20170922192218.png\"\nSyntaxError: Unexpected token …\n本地node版本8.4.0,\ndocker里面node版本7.8.0

\n
", 214 | "create_at": "2017-09-22T11:26:29.728Z", 215 | "good": false, 216 | "id": "59c4f365d7cbefc511964644", 217 | "last_reply_at": "2017-09-23T06:45:37.319Z", 218 | "reply_count": 5, 219 | "tab": "ask", 220 | "title": "为什么我写一个脚本,在本地运行的时候没问题,但在docker里面就会报错?SyntaxError: Unexpected token ...", 221 | "top": false, 222 | "visit_count": 193 223 | }, 224 | { 225 | "author": { 226 | "avatar_url": "https://avatars1.githubusercontent.com/u/16103955?v=4&s=120", 227 | "loginname": "Hyurl" 228 | }, 229 | "author_id": "59c1dcee8812ce51127a9026", 230 | "content": "

Modelar 参考了 PHP laravel 框架的 Eloquent ORM 模型,使用查询语句构造器生成 SQL 语句来进行数据操作。\n这是一个轻量级的模型,仅 160 kb 的源码,所有 API 均有完善的文档和示例。\nModelar 的目标是加速 nodejs 应用的开发,通过查询构造器对象,模型关联等技术,可以非常方便地实现对数据的操作。\n模型继承、事件监听等特性同时使数据操作更为可控。使开发者更关注于应用地呈现,而不是将时间花费在数据逻辑的设计上。\n目前,Modelar 支持下面这些数据库:

\n\n

Modelar 的宗旨是 “写更少的代码,写优雅的代码”。\nAPI 链接地址:modelar.hyurl.com:3000。\nGitHub:https://github.com/Hyurl/modelar。\n安装方式:

\n
npm install modelar --save\n
", 231 | "create_at": "2017-09-20T13:25:56.740Z", 232 | "good": false, 233 | "id": "59c26c64d7cbefc511964568", 234 | "last_reply_at": "2017-09-23T05:46:15.565Z", 235 | "reply_count": 9, 236 | "tab": "share", 237 | "title": "分享自己写的 ORM 模块 Modelar", 238 | "top": false, 239 | "visit_count": 458 240 | }, 241 | { 242 | "author": { 243 | "avatar_url": "https://avatars2.githubusercontent.com/u/21024206?v=4&s=120", 244 | "loginname": "FirstYy" 245 | }, 246 | "author_id": "59c31137d7cbefc511964576", 247 | "content": "

请各位大大帮我整理一下思路,实在是不知道怎么做了。

\n
", 248 | "create_at": "2017-09-21T01:11:46.374Z", 249 | "good": false, 250 | "id": "59c311d2b53b601512be42ce", 251 | "last_reply_at": "2017-09-23T02:55:58.351Z", 252 | "reply_count": 28, 253 | "tab": "ask", 254 | "title": "node做多聊天室,添加聊天室这个功能怎么实现?", 255 | "top": false, 256 | "visit_count": 949 257 | }, 258 | { 259 | "author": { 260 | "avatar_url": "https://avatars1.githubusercontent.com/u/23180932?v=4&s=120", 261 | "loginname": "yangzaiwangzi" 262 | }, 263 | "author_id": "59b204e71b37e54f6793c3f8", 264 | "content": "

\"QQ截图20170908104853.png\"\n做个人中心是否登入的路由拦截判断,如上…\n想把所有路由含有 ‘ memberindex ’ 的页面全部拦截判断了…\n结果当你没有登入时,确实会跳到登入页面,但是node会报 Error: Can’t set headers after they are sent.\n请问大家,这是咋回事??如何解决??谢谢!!!

\n
", 265 | "create_at": "2017-09-08T02:55:09.213Z", 266 | "good": false, 267 | "id": "59b2068d7a42adf666919d65", 268 | "last_reply_at": "2017-09-23T02:18:32.995Z", 269 | "reply_count": 6, 270 | "tab": "ask", 271 | "title": "node 做登入拦截始终报错...", 272 | "top": false, 273 | "visit_count": 527 274 | }, 275 | { 276 | "author": { 277 | "avatar_url": "https://avatars3.githubusercontent.com/u/11516105?v=4&s=120", 278 | "loginname": "Gil2015" 279 | }, 280 | "author_id": "59c4d15bd7cbefc51196463b", 281 | "content": "

用koa2写了一个post请求,server端读取python脚本返回一个80MB的文本数据,想用json返回到客户端,但是客户端读取到30MB左右的时候就停止失败了。\n打印python返回的数据是完整的,服务器里log也是完整可用的。但是作为body返回就会出现问题。

\n
", 282 | "create_at": "2017-09-22T09:04:52.473Z", 283 | "good": false, 284 | "id": "59c4d234b53b601512be436a", 285 | "last_reply_at": "2017-09-23T02:13:11.086Z", 286 | "reply_count": 6, 287 | "tab": "ask", 288 | "title": "node服务器返回json数据大小限制问题", 289 | "top": false, 290 | "visit_count": 203 291 | }, 292 | { 293 | "author": { 294 | "avatar_url": "https://avatars2.githubusercontent.com/u/344175?v=4&s=120", 295 | "loginname": "wlsy" 296 | }, 297 | "author_id": "59c56865e7d9a031127eae4b", 298 | "content": "

举一个场景:\n给用户手机发送验证码,使用koa,简易示例代码如下:

\n
router.post('/code', async (ctx, next) => {\n  try {\n    await sms.code({\n      phone: form.phone\n    })\n    ctx.body = "ok"\n  } catch (e) {\n    ctx.body = e.message\n  }\n})\n

当用户手机号输错时候,验证失败时候 就直接把错误信息返回给用户。这个没有问题,\n问题是:当 sms.code 代码本身有问题时候,它也会发错误信息返回给用户,没法把错误日志保存下来,排查问题。\n这会导致线上如果代码本身有问题将很难被察觉\n各位是如何解决这个问题?

\n
", 299 | "create_at": "2017-09-22T19:57:41.702Z", 300 | "good": false, 301 | "id": "59c56b358812ce51127a913f", 302 | "last_reply_at": "2017-09-23T01:23:32.131Z", 303 | "reply_count": 1, 304 | "tab": "ask", 305 | "title": "使用 try catch 来捕获 async await 错误的时候,日志保存问题", 306 | "top": false, 307 | "visit_count": 122 308 | }, 309 | { 310 | "author": { 311 | "avatar_url": "https://avatars0.githubusercontent.com/u/17523638?v=4&s=120", 312 | "loginname": "FantasyGao" 313 | }, 314 | "author_id": "573ac2cdf610cbba1dc4519b", 315 | "content": "

##javascript 原型链

\n

示意图:

\n

\"proto\"

\n
function Person(name){\n\tthis.name = name\n}\nvar me = new Person('FantasyGao')\nvar obj = {}\n

总结

\n
    \n
  1. Object,Function,自定义函数类(Person)有prototype属性,其余没有
  2. \n
  3. Function的peototype属性与__proto__属性指向同一内容(Function.proto===Function.prototype)
  4. \n
  5. 由构造函数生成的对象与直接定义的对象原型链有差异(me.proto.proto===obj.proto)
  6. \n
  7. 对象的constructor属性即它__proto__属性被prototype指向的值(me.constructor===Person,me.proto.constructor===Person)
  8. \n
  9. 任何对象由原型链查找到顶端为null(Object.prototype.proto===null)
  10. \n
\n

有错的地方大佬们指正啊\n代码地址

\n
", 316 | "create_at": "2017-09-22T08:59:20.439Z", 317 | "good": false, 318 | "id": "59c4d0e8d7cbefc51196463a", 319 | "last_reply_at": "2017-09-23T01:05:11.849Z", 320 | "reply_count": 2, 321 | "tab": "share", 322 | "title": "理解javascript原型链", 323 | "top": false, 324 | "visit_count": 187 325 | }, 326 | { 327 | "author": { 328 | "avatar_url": "https://avatars0.githubusercontent.com/u/19640738?v=4&s=120", 329 | "loginname": "ELSS-ZION" 330 | }, 331 | "author_id": "59b79d8f3c896622428ec66a", 332 | "content": "

http://www.jianshu.com/p/341b66f99b62

\n
", 333 | "create_at": "2017-09-22T20:24:57.041Z", 334 | "good": false, 335 | "id": "59c57199b53b601512be4378", 336 | "last_reply_at": "2017-09-22T20:24:57.041Z", 337 | "reply_count": 0, 338 | "tab": "share", 339 | "title": "Node.js 零学习成本接入富文本编辑器 Ueditor(Neditor)", 340 | "top": false, 341 | "visit_count": 152 342 | }, 343 | { 344 | "author": { 345 | "avatar_url": "https://avatars1.githubusercontent.com/u/9962863?v=4&s=120", 346 | "loginname": "rysinal" 347 | }, 348 | "author_id": "5959b6cbd629da605b2f5b03", 349 | "content": "

我有一个数组groups,groups数组里面元素都是对象,在对groups进行元素值删除之前,插入了一个异步函数,groups作为这个函数的一个参数;紧接着我对groups里面的对象进行了不必要的属性删除(delete group[i].key1),另外执行比较慢的异步函数拿key1的值时,发现已经不存在了;\n即使使用了copygroups=groups.concat();然后把copygroups 作为参数传递也不行(不是深度复制吗??)\n如果希望异步函数处理时拿到的数组是完整的,怎么处理比较好呢?新人求带,不胜感激

\n

附例子:

\n
let arr1 = [{"a1":"v1","a2":"v2","a3":"v3","a4":"v4"}]\nlet arr2=arr1.concat()\nconsole.log(arr2)\t// [{a1: "v1", a2: "v2", a3: "v3", a4: "v4"}]\ndelete arr1[0]['a2']\nconsole.log(arr1)\t// [{a1: "v1", a3: "v3", a4: "v4"}]\nconsole.log(arr2)\t// [{a1: "v1", a3: "v3", a4: "v4"}]\t\t为什么arr2也会随着arr1的操作而改变??\n
", 350 | "create_at": "2017-09-22T14:47:44.764Z", 351 | "good": false, 352 | "id": "59c522908812ce51127a913b", 353 | "last_reply_at": "2017-09-22T16:00:21.390Z", 354 | "reply_count": 3, 355 | "tab": "ask", 356 | "title": "为什么数组复制不了内部的对象呢?", 357 | "top": false, 358 | "visit_count": 135 359 | }, 360 | { 361 | "author": { 362 | "avatar_url": "https://avatars1.githubusercontent.com/u/13977368?v=4&s=120", 363 | "loginname": "JerrysShan" 364 | }, 365 | "author_id": "56fb8ede8265278d59c7e304", 366 | "content": "

还有node.js怎么实现不重启服务更新配置文件?

\n

望大神们指教!!!

\n
", 367 | "create_at": "2017-09-22T10:17:35.481Z", 368 | "good": false, 369 | "id": "59c4e33fb53b601512be4370", 370 | "last_reply_at": "2017-09-22T15:14:07.059Z", 371 | "reply_count": 1, 372 | "tab": "ask", 373 | "title": "node.js 怎么实现热部署,有什么成熟的解决方案吗?", 374 | "top": false, 375 | "visit_count": 198 376 | }, 377 | { 378 | "author": { 379 | "avatar_url": "https://avatars3.githubusercontent.com/u/11769267?v=4&s=120", 380 | "loginname": "ranjin" 381 | }, 382 | "author_id": "59c23b70b53b601512be42a8", 383 | "content": "

如题。查了下url的resolve方法。看到了个例子。\nurl.resolve('/one/two/three', 'four') // '/one/two/four'\nurl.resolve('http://example.com/', '/one') // 'http://example.com/one'\nurl.resolve('http://example.com/one', '/two') // 'http://example.com/two'\n这三个结果无法理解。有人帮忙解释下吗

\n
", 384 | "create_at": "2017-09-20T09:59:02.693Z", 385 | "good": false, 386 | "id": "59c23be6b53b601512be42a9", 387 | "last_reply_at": "2017-09-22T14:44:18.301Z", 388 | "reply_count": 2, 389 | "tab": "ask", 390 | "title": "新人请教个问题,怎么理解node.js中URL的resolve方法", 391 | "top": false, 392 | "visit_count": 231 393 | }, 394 | { 395 | "author": { 396 | "avatar_url": "https://avatars3.githubusercontent.com/u/449224?v=3&s=120", 397 | "loginname": "jiyinyiyong" 398 | }, 399 | "author_id": "4efc278525fa69ac69000141", 400 | "content": "

…DigitalOcean 太慢所以在考虑国内找个机器,\n以前尝试过阿里云一个月, 因为没备案, 结果还是没定下来,\n当时大致的印象好像是要到杭州什么地方去备案下, 可惜显得我都在上海了…\n有备案过的同学讲下怎么备案方便, 等有机会了我想去试一下…

\n
", 401 | "create_at": "2014-11-18T13:08:42.393Z", 402 | "good": false, 403 | "id": "546b44da1c825b0c4d79e7cf", 404 | "last_reply_at": "2017-09-22T14:41:59.018Z", 405 | "reply_count": 16, 406 | "tab": "share", 407 | "title": "阿里云的主机备案过的同学讲讲备案麻烦吗?", 408 | "top": false, 409 | "visit_count": 3169 410 | }, 411 | { 412 | "author": { 413 | "avatar_url": "https://avatars1.githubusercontent.com/u/16065346?v=4&s=120", 414 | "loginname": "lvgithub" 415 | }, 416 | "author_id": "57b08034a4f7e29c763413ef", 417 | "content": "

项目地址: https://github.com/lvgithub/stick\n进入example目录,1、运行server.js 2、运行client.js即可测试demo

\n

server

\n
const net = require('net')\nconst stick_package = require('../index')\n\nlet tcp_server = net.createServer(function (socket) {\n    socket.stick = new stick_package(1024).setReadIntBE('32')\n    socket.on('data', function (data) {\n        socket.stick.putData(data)\n    })\n\n    socket.stick.onData(function (data) {\n        // 解析包头长度\n        let head = new Buffer(4)\n        data.copy(head, 0, 0, 4)\n\n        // 解析数据包内容\n        let body = new Buffer(head.readInt32BE())\n        data.copy(body, 0, 4, head.readInt32BE())\n\n        console.log('data length: ' + head.readInt32BE())\n        console.log('body content: ' + body.toString())\n    })\n\n    socket.stick.onError(function (error) {\n        console.log('stick data error:' + error)\n    })\n\n    socket.on('close', function (err) {\n        console.log('client disconnected')\n    })\n})\n\ntcp_server.on('error', function (err) {\n    throw err\n})\ntcp_server.listen(8080, function () {\n    console.log('tcp_server listening on 8080')\n})\n

client

\n
const net = require('net')\n\nconst client = net.createConnection({ port: 8080, host: '127.0.0.1' }, function () {\n    let body = Buffer.from('username=123&password=1234567,qwe')\n\n    // 写入包头\n    let headBuf = new Buffer(4)\n    headBuf.writeUInt32BE(body.byteLength, 0)\n    console.log('data length: ' + headBuf.readInt32BE())\n\n    // 发送包头\n    client.write(headBuf)\n    // 发送包内容\n    client.write(body)\n    console.log('data body: ' + body.toString())\n\n})\n\nclient.on('data', function (data) {\n    console.log(data.toString())\n})\nclient.on('end', function () {\n    console.log('disconnect from server')\n})\n
", 418 | "create_at": "2017-09-22T14:03:04.408Z", 419 | "good": false, 420 | "id": "59c518188812ce51127a9138", 421 | "last_reply_at": "2017-09-22T14:03:04.408Z", 422 | "reply_count": 0, 423 | "tab": "share", 424 | "title": "tcp 收发数据demo(带粘包处理解决方案)", 425 | "top": false, 426 | "visit_count": 115 427 | }, 428 | { 429 | "author": { 430 | "avatar_url": "https://avatars1.githubusercontent.com/u/23579859?v=4&s=120", 431 | "loginname": "byCrazyBStone" 432 | }, 433 | "author_id": "59c4ffabe7d9a031127eae46", 434 | "content": "

var fs = require(“fs”);\nvar zlib = require(‘zlib’);\nfs.createReadStream(‘文件.op.gz’)\n.pipe(zlib.createGunzip())\n.pipe(fs.createWriteStream(‘文件.txt’));

\n

执行后将 文件.op.gz 解压到 文件.txt\n但我不想把二进制流写进文件,我想转换后二次操作,

\n

var a=fs.createReadStream(‘文件.op.gz’)\n.pipe(zlib.createGunzip())\n返回的Gunzip对象即a中有一个_buffer属性,我执行a._buffer.toString()后却是乱码,\n这里想要直接获取解压后文件里的内容怎么破呢?

\n
", 435 | "create_at": "2017-09-22T12:37:18.958Z", 436 | "good": false, 437 | "id": "59c503fe8812ce51127a9137", 438 | "last_reply_at": "2017-09-22T12:37:18.958Z", 439 | "reply_count": 0, 440 | "tab": "ask", 441 | "title": "zlib解压gzip文件后如何直接读取文件内容?", 442 | "top": false, 443 | "visit_count": 110 444 | }, 445 | { 446 | "author": { 447 | "avatar_url": "https://avatars1.githubusercontent.com/u/26131852?v=4&s=120", 448 | "loginname": "wangzhm" 449 | }, 450 | "author_id": "59c4a8f68812ce51127a9106", 451 | "content": "

这是获取的数据:\ncomputert[ {\n‘序号’: 100,\n‘品牌’: ‘DELL’,\n‘规格型号’: ‘3040’,\n‘序列号’: ‘CDRXND2’,\n‘使用情况’: ‘在用’,\n‘备注’: ‘’ },{…}]\n输出循环:\n<% for(i;i<=computer.length;i++){%>\n<td> <%= computer[i].序号 %></td>\n<td> <%= computer[i].品牌 %></td>\n<td> <%= computer[i].规格型号 %></td>\n<td> <%= computer[i].序列号 %></td>\n<td> <%= computer[i].使用情况 %></td>\n<td> <%= computer[i].备注 %></td>\n<%}%>\n我可以怎么把 computer[i].序号 中的‘序号’用变量来表示?谢谢

\n
", 452 | "create_at": "2017-09-22T06:32:00.217Z", 453 | "good": false, 454 | "id": "59c4ae60d7cbefc51196461f", 455 | "last_reply_at": "2017-09-22T12:22:17.384Z", 456 | "reply_count": 2, 457 | "tab": "ask", 458 | "title": "请教在ejs<%%>中使用变量,或数组", 459 | "top": false, 460 | "visit_count": 167 461 | }, 462 | { 463 | "author": { 464 | "avatar_url": "https://avatars1.githubusercontent.com/u/19143357?v=4&s=120", 465 | "loginname": "gzz2000" 466 | }, 467 | "author_id": "59c48feab53b601512be4347", 468 | "content": "

Github

\n

欢迎大家提建议

\n

效果:

\n
class Point {\n    x = 0;\n    y = 0;\n\n    constructor(_x, _y) {\n\t\tif(_x) this.x = _x;\n\t\tif(_y) this.y = _y;\n    }\n\n    operatorAdd = (b) => {\n\t\tconst a = this;\n\t\treturn new Point(a.x + b.x, a.y + b.y);\n    }\n\n    operatorMul = (b) => {\n\t\tconst a = this;\n\t\treturn new Point(a.x * b, a.y * b);\n    }\n};\n\nlet a = new Point(1, 2), b = new Point(3, 4);\n\nconsole.log(a + b * 3);\n
", 469 | "create_at": "2017-09-22T04:25:10.523Z", 470 | "good": false, 471 | "id": "59c490a6e7d9a031127eae08", 472 | "last_reply_at": "2017-09-22T11:34:52.016Z", 473 | "reply_count": 1, 474 | "tab": "share", 475 | "title": "一个babel-plugin让javascript支持重载运算符", 476 | "top": false, 477 | "visit_count": 168 478 | }, 479 | { 480 | "author": { 481 | "avatar_url": "https://avatars0.githubusercontent.com/u/17523638?v=4&s=120", 482 | "loginname": "FantasyGao" 483 | }, 484 | "author_id": "573ac2cdf610cbba1dc4519b", 485 | "content": "

mongoose自己带的promise不能捕获发生的错误,求指点

\n
 var promise =await myModel.findById(info).exec()\n     .then(function (doc) {\n        console.log(doc)\n    })\n    .catch(function (err) {\n        console.log(err.toString())\n    })\n
传的info不合规则的时候不能捕获到错误
\n

\"自带promise\"

\n

不使用自带的promise,可以再catch里捕获到

\n
    var promise =new Promise(function(resolve, reject) {\n         myModel.findById(info)\n         resolve(1)\n    })\n    .then(function (doc) {\n        console.log(doc)\n    })\n    .catch(function (err) {\n        console.log(err.toString())\n    })\n

\"不使用自带promise\"

\n
", 486 | "create_at": "2017-08-21T10:13:48.055Z", 487 | "good": false, 488 | "id": "599ab25cebaa046923a8261d", 489 | "last_reply_at": "2017-09-22T11:21:33.316Z", 490 | "reply_count": 5, 491 | "tab": "ask", 492 | "title": "在mongoose中,promise的疑惑", 493 | "top": false, 494 | "visit_count": 534 495 | }, 496 | { 497 | "author": { 498 | "avatar_url": "https://avatars0.githubusercontent.com/u/10149029?v=4&s=120", 499 | "loginname": "dp199313" 500 | }, 501 | "author_id": "54fc6f351e9291e16a7b34ba", 502 | "content": "

据说es7的代理可以实现**.**运算符的重载了。有没有办法实现+—*/运算符的重载呢。\nDate类又是怎么实现的运算符重载的呢,有人给个思路么。

\n
", 503 | "create_at": "2015-11-28T11:54:11.208Z", 504 | "good": false, 505 | "id": "565995e3d0bc14ae27939993", 506 | "last_reply_at": "2017-09-22T10:22:29.764Z", 507 | "reply_count": 2, 508 | "tab": "ask", 509 | "title": "javascript能够实现运算符重载吗?", 510 | "top": false, 511 | "visit_count": 2283 512 | }, 513 | { 514 | "author": { 515 | "avatar_url": "https://avatars2.githubusercontent.com/u/25356455?v=4&s=120", 516 | "loginname": "Monisuy" 517 | }, 518 | "author_id": "59c464898812ce51127a90e4", 519 | "content": "

\"image.png\"

\n
", 520 | "create_at": "2017-09-22T05:38:43.381Z", 521 | "good": false, 522 | "id": "59c4a1e3b53b601512be4350", 523 | "last_reply_at": "2017-09-22T10:12:31.017Z", 524 | "reply_count": 7, 525 | "tab": "ask", 526 | "title": "NodeJS 端口转发问题求解", 527 | "top": false, 528 | "visit_count": 185 529 | }, 530 | { 531 | "author": { 532 | "avatar_url": "https://avatars1.githubusercontent.com/u/20507053?v=4&s=120", 533 | "loginname": "a69694510" 534 | }, 535 | "author_id": "57d216e13d3520a5387c2b53", 536 | "content": "

错误是app.use() req a genertoer function 啥是genertoer函数啊

\n

来自酷炫的 CNodeMD

\n
", 537 | "create_at": "2017-09-22T02:47:35.209Z", 538 | "good": false, 539 | "id": "59c479c7b53b601512be4339", 540 | "last_reply_at": "2017-09-22T09:43:32.086Z", 541 | "reply_count": 3, 542 | "tab": "ask", 543 | "title": "今天我用koa1 的时候,出现个app.use() req a 今天我用koa1 的时候,出现个错误不知道啥意思", 544 | "top": false, 545 | "visit_count": 150 546 | }, 547 | { 548 | "author": { 549 | "avatar_url": "https://avatars1.githubusercontent.com/u/15210887?v=4&s=120", 550 | "loginname": "Nicksapp" 551 | }, 552 | "author_id": "58365e6627d001d606ac18fc", 553 | "content": "

使用Google Sheets + JavaScript定制一个自动化天气管家

\n
\n

可以根据天气变化每日自动给我们的邮箱发送推送消息的功能?是的,今天我们将只用JavaScript实现这个功能,而GoogleSheets可以做很多事情,不仅仅只是文档的处理工作,这篇文章将给大家具体介绍这个能够让各位JSer施展拳脚的地方。

\n
\n

应用场景

\n

是否在每天出门前被猝不及防的大雨所困扰,今天我们就将使用Google Sheet - 请科学上网-推荐)定制一个判断当天是否可能下雨的邮箱通知云应用,使我们每天都能放放心心的出门!行动起来!

\n

天气预报 API

\n

我们将使用这个Web API在接下来的步骤中来完成我们的一系列网络请求动作。API将会返回标准的JSON格式最近一周内的天气具体数据,这个天气API提供两个参数:

\n\n

举例:返回西安最近5天的JSON格式的天气信息。

\n

https://f.stdlib.com/thisdavej/weather/forecast?loc=Xian&deg=C

\n

将返回如下格式的JSON数据大体结构:

\n
[\n  {\n    "low": "5",\n    "high": "14",\n    "skycodeday": "27",\n    "skytextday": "Mostly Cloudy",\n    "date": "2017-03-10",\n    "day": "Friday",\n    "shortday": "Fri",\n    "precip": "0",\n    "degType": "C"\n  },\n  {\n    "low": "7",\n    "high": "15",\n    "skycodeday": "28",\n    "skytextday": "Mostly Cloudy",\n    "date": "2017-03-11",\n    "day": "Saturday",\n    "shortday": "Sat",\n    "precip": "70",\n    "degType": "C"\n  },\n  {\n    "low": "2",\n    "high": "9",\n    "skycodeday": "11",\n    "skytextday": "Rain",\n    "date": "2017-03-12",\n    "day": "Sunday",\n    "shortday": "Sun",\n    "precip": "100",\n    "degType": "C"\n  },\n  {\n    "low": "1",\n    "high": "6",\n    "skycodeday": "11",\n    "skytextday": "Rain Showers",\n    "date": "2017-03-13",\n    "day": "Monday",\n    "shortday": "Mon",\n    "precip": "100",\n    "degType": "C"\n  },\n  {\n    "low": "2",\n    "high": "10",\n    "skycodeday": "26",\n    "skytextday": "Cloudy",\n    "date": "2017-03-14",\n    "day": "Tuesday",\n    "shortday": "Tue",\n    "precip": "100",\n    "degType": "C"\n  }\n]\n

根据可以获得天气动态数据,将为我们接下来的步骤提供便利。

\n

创建基本功能函数

\n

我们接下来将在Google Sheets中进行JavaScript的函数创建。首先打开 Google Sheets并且创建一个新的表格文档。

\n

\n

打开工具栏的脚本编辑器选项。

\n

\n

将会进入到Google Sheets自带的JavaScript脚本编辑页面。

\n

\n

让我们先创建我们需要使用的一系列功能函数。

\n
// 对小于10的数自动加前缀0\nfunction padZero (number) {\n  return number < 10 ? '0' + number:number.toString(); \n}\n

因为API返回的天气信息中的date日期中的数字小于10自带0前缀,padZero()可以让之后我们能更好的获得当日的日期。接下来我们创建我们获得天气数据的函数:

\n
function forecast_today_object(city, threshold) {\n  var url = 'http://f.stdlib.com/thisdavej/weather/forecast?deg=C&loc=' + encodeURI(city);\n  \n  var response = UrlFetchApp.fetch(url);// Google自带的网络请求Api\n  var obj = JSON.parse(response.getContentText()); //parse请求到的JSON数据\n  \n  // 获得当天的天气预报\n  // 获得当天的日期\n  var date = new Date();\n  var day = padZero(date.getDate());\n  var month = padZero(date.getMonth() + 1);\n  var year = date.getFullYear();\n  var now = year + '-' + month + '-' + day; \n  \n  var todayObj = obj.filter(function(o) {\n    return o.date === now;\n  });   // 获得当天的天气\n  \n  if(todayObj.length > 0){\n    var today = todayObj[0];\n    today.precip = parseFloat(today.precip); // 当日的降雨概率\n    today.rainLikely = today.precip >= threshold; // 可能下雨的一个判断,当超过一个指定界限时提醒\n    return today;\n  } else {\n    return { error : 'No forecast data found for today'};\n  }\n}\n

forecast_today_object()函数使用了Google自带的 UrlFetchApp.fetch方法进行网络请求操作,返回我们需要的天气数据。创建todayObj对象保存当天的天气信息,并且新增rainLikely属性,并使其在降雨概率大于预设值时为true.

\n

下一步创建可供Sheets直接使用的函数,这样可以直接让天气信息显示在表格文档中。

\n
// 此函数可只在文档中直接调用,以便于我们调试我们的数据\nfunction FORECAST_TODAY(city, threshold) {\n  var today = forecast_today_object(city, threshold);\n  return JSON.stringify(today, null, 2); // 将JSON信息转换为字符型,并添加2个空格缩进\n}\n

将函数名设为全大写形式,以便我们区分执行函数和功能函数。Ctrl + S保存我们的脚本,并在表格文档中测试我们的函数功能是否能够正确运行。

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
ABC
1CityThreshold PercentRain likely today?
2Xian20=FORECAST_TODAY(A2,B2)
\n

这样我们就能获得西安当天的天气,你应该可以获得如下结果

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
ABC
1CityThreshold PercentToday’s Forecast
2Xian20{“low”: “5”,“high”: “14”,“skycodeday”: “27”,“skytextday”: “Mostly Cloudy”,“date”: “2017-03-10”,“day”: “Friday”,“shortday”: “Fri”,“precip”: 70,“degType”: “C”,“rainLikely”: true}
\n

可以看到我们已经可以正常获取我们的网络请求了,rainLikely也在precip大于20的时候为true,即可以判断出当天是否很有可能会下雨,大体思路也已经基本完成。

\n

实现降雨邮件通知的功能

\n

创建如下函数:

\n
// 创建一个触发函数在每天指定时刻自动检查下雨几率,如果可能下雨就发送提示邮件到我们的邮箱\n// 将天气预报发送至我的邮箱\nfunction CheckForecastForRain () {\n  var recipient = "changjie22@qq.com";  // 指定接受邮箱,发送人自动设定为GoogleSheet用户的Gmail\n  var city = 'Xian';\n  var threshold = 20;\n  \n  var today = (city, threshold);\n  if(today.error === undefined) {\n    if(today.rainLikely) {\n      MailApp.sendEmail({ // Google 自带的发送邮件Api\n        to: recipient,\n        subject: city + "今天有(" + today.precip + "% 几率) 可能会下雨哟! 最好随身带一把雨伞",\n        body: "天气预报:" + today.shortday + " " + today.low + "-" + today.high + "°" + today.degType + " " + today.skytextday + " ~ Rain: " + today.precip + "%"\n      });\n    }\n  }\n}\n

在这个函数中,我们没有给他传递任何参数,因为在接下来我们要使用的项目触发器使得我们的函数在每天都会自动执行,这里不允许使用带有参数的函数,所以我们将具体信息放在了函数里面。

\n

其中我们做了today.error === undefined的判断,是因为forecast_today_object函数会返回一个对象包括error如果发生错误,这样可以使我们可以通过次来判断触发函数是否被正确执行。

\n

我们直接使用了Google MailApp.sendEmail 方法来实现发送Email的功能,这将会自动使用你得Gmail发送一封Email。现在我们就可以测试邮件是否已可以正常发送出去。选择要执行的函数,点击Run按钮即可立即执行它。

\n

\n

此时你应该已经接收到了事先编好的邮件了。比如这样:

\n

\n

创建功能自动触发器

\n

最后一步也非常简单,Google Sheets其实也已近内置了项目触发器,接下来我们只需设置它在比如每天早上7到8点的时候,根据当天天气是否可能下雨,自动向我们的邮箱发送通知。

\n

在修改菜单栏中找到’当前项目的触发器’一栏。

\n

\n

选择需要自动触发的函数,指定在每天的上午7点至8点自动触发CheckForecastForRain函数

\n

\n

这样在每天的7点到8点,我们就会根据当天的天气状况获得是否可能下雨的通知了。大功告成!!!

\n

附其他可供使用的API:

\n

获得当前城市的实时天气:\nhttps://f.stdlib.com/thisdavej/weather/current/?loc=Xian&deg=C

\n
", 554 | "create_at": "2017-03-11T12:23:48.957Z", 555 | "good": true, 556 | "id": "58c3ec5406dbd608756d0c83", 557 | "last_reply_at": "2017-09-22T09:32:03.951Z", 558 | "reply_count": 16, 559 | "tab": "share", 560 | "title": "使用Google Sheets + JavaScript定制一个自动化天气管家", 561 | "top": false, 562 | "visit_count": 3581 563 | }, 564 | { 565 | "author": { 566 | "avatar_url": "https://avatars1.githubusercontent.com/u/31922823?v=4&s=120", 567 | "loginname": "xiaodu2017" 568 | }, 569 | "author_id": "59c200498812ce51127a9031", 570 | "content": "
", 571 | "create_at": "2017-09-20T06:52:52.852Z", 572 | "good": false, 573 | "id": "59c21044b53b601512be4283", 574 | "last_reply_at": "2017-09-22T09:23:42.685Z", 575 | "reply_count": 4, 576 | "tab": "ask", 577 | "title": "免费可以测试的vps服务器资源?", 578 | "top": false, 579 | "visit_count": 339 580 | }, 581 | { 582 | "author": { 583 | "avatar_url": "https://avatars1.githubusercontent.com/u/8166360?v=4&s=120", 584 | "loginname": "jeodiong" 585 | }, 586 | "author_id": "549bd0988ade094b67f3fc3a", 587 | "content": "

TodoKit

\n

TodoKit是一个免费的高颜值产品需求和Bug管理软件,包括Mac客户端和网页版。\n官网:https://www.todokit.vip

\n

Getting Started

\n

使用TodoKit,需要你下载最新版的客户端 或者使用网页版

\n

登录/注册

\n

下载最新客户端或者网页版后,未登录的用户将引导到,手机号码登录页面。未注册的用户,在登录页面输入手机号和验证码后,将自动注册为新用户。

\n

\"登录/注册\"

\n

我的产品

\n

加入产品,有两种方式:

\n
    \n
  1. 加入已有产品
  2. \n
  3. 创建新产品
  4. \n
\n

\"我的产品\"

\n

我的任务

\n

点击我的产品后,进入当前产品,我的任务列表

\n

\"我的任务\"

\n

任务流转

\n

任务共有3种状态

\n
    \n
  1. 指派
  2. \n
  3. 完成
  4. \n
  5. 归档
  6. \n
\n

\"我的任务\"

\n

创建任务

\n

创建任务仅两个必填项

\n
    \n
  1. 指派人
  2. \n
  3. 任务标题
  4. \n
\n

\"创建任务\"

\n

统计

\n

\"统计\"

\n

成员管理

\n

因为是适合小团队的软件,所以人数限制10人,更多的人数团队建议使用更全面的管理系统,但是如果觉得TodoKit好用的话,可以联系我扩大人数限制。

\n

\"成员管理\"

\n

意见收集

\n

由于人数限制的原因,建议不要将非开发测试人员加入项目,意见收集提供对外的网址,用于收集他人意见建议。

\n

\"成员管理\"

\n

Contribution

\n

欢迎前端开发者和我一起做这件事。

\n
", 588 | "create_at": "2017-09-18T03:03:33.719Z", 589 | "good": false, 590 | "id": "59bf3785d7cbefc51196447b", 591 | "last_reply_at": "2017-09-22T09:22:20.929Z", 592 | "reply_count": 14, 593 | "tab": "share", 594 | "title": "使用electron express 写了一个免费的高颜值Bug管理软件", 595 | "top": false, 596 | "visit_count": 870 597 | }, 598 | { 599 | "author": { 600 | "avatar_url": "//gravatar.com/avatar/3576266cf19a8559b94a8e97857a986c?size=48", 601 | "loginname": "itcaptainli" 602 | }, 603 | "author_id": "516bc49d6d382773064a2ef1", 604 | "content": "

我在本地server.use(express.static(’./static’));这样设置,木有任何问题。\n但扔到服务器上,看起来就没效果了,不管是./还是直接写‘static’都没有任何作用。\n是哪里出问题了么?

\n
", 605 | "create_at": "2017-09-22T04:24:17.580Z", 606 | "good": false, 607 | "id": "59c49071e7d9a031127eae07", 608 | "last_reply_at": "2017-09-22T09:18:30.339Z", 609 | "reply_count": 4, 610 | "tab": "ask", 611 | "title": "express.static在生产环境下怎么设置?", 612 | "top": false, 613 | "visit_count": 151 614 | }, 615 | { 616 | "author": { 617 | "avatar_url": "https://avatars1.githubusercontent.com/u/10721874?v=4&s=120", 618 | "loginname": "leiwei1991" 619 | }, 620 | "author_id": "5626fe5d3f017c2b49b4153e", 621 | "content": "

RT

\n
", 622 | "create_at": "2017-09-07T03:14:00.705Z", 623 | "good": false, 624 | "id": "59b0b978b1a5852e67bb7dc9", 625 | "last_reply_at": "2017-09-22T08:57:20.755Z", 626 | "reply_count": 13, 627 | "tab": "ask", 628 | "title": "生产环境node 8.x现在用的人多吗?有没有什么大坑?", 629 | "top": false, 630 | "visit_count": 1006 631 | }, 632 | { 633 | "author": { 634 | "avatar_url": "https://avatars3.githubusercontent.com/u/30330930?v=4&s=120", 635 | "loginname": "quanpf2481" 636 | }, 637 | "author_id": "5976b9b80c87675e74674819", 638 | "content": "

node js 里面怎么样保证前一个执行完后,下一个才开始执行呢???

\n
", 639 | "create_at": "2017-09-22T05:57:58.819Z", 640 | "good": false, 641 | "id": "59c4a666b53b601512be4357", 642 | "last_reply_at": "2017-09-22T08:55:22.694Z", 643 | "reply_count": 4, 644 | "tab": "ask", 645 | "title": "顺序控制怎么样保证", 646 | "top": false, 647 | "visit_count": 172 648 | }, 649 | { 650 | "author": { 651 | "avatar_url": "https://avatars3.githubusercontent.com/u/10203487?v=4&s=120", 652 | "loginname": "NextZeus" 653 | }, 654 | "author_id": "57313dd2f69a97bb58747024", 655 | "content": "

Hello World

\n

上面的连接是我在Github上写的一些在实际项目中使用到的npm的demo, 一点一滴的积累。\n包括了express, koa2, log4js, pm2, wechaty, redis, docker, eslint等等 以后还会持续的写\n可能有的demo写的比较的浅,没有太深入。勿喷 😄

\n
", 656 | "create_at": "2017-09-19T07:57:29.474Z", 657 | "good": false, 658 | "id": "59c0cde98812ce51127a8fe3", 659 | "last_reply_at": "2017-09-22T08:51:58.681Z", 660 | "reply_count": 4, 661 | "tab": "share", 662 | "title": "一切皆从Hello World开始", 663 | "top": false, 664 | "visit_count": 459 665 | }, 666 | { 667 | "author": { 668 | "avatar_url": "https://avatars.githubusercontent.com/u/7091496?v=3&s=120", 669 | "loginname": "qiushijie" 670 | }, 671 | "author_id": "56fe0b4593a6967159553b01", 672 | "content": "

拖动用了jquery ui,里面好像没有相应的辅助线api,有没有其他插件或者要自己实现是什么思路呢?

\n
", 673 | "create_at": "2016-09-13T04:17:54.333Z", 674 | "good": false, 675 | "id": "57d77df2cb441239368989e8", 676 | "last_reply_at": "2017-09-22T08:24:42.605Z", 677 | "reply_count": 2, 678 | "tab": "ask", 679 | "title": "要实现拖动的辅助线功能,有没有什么比较好的推荐", 680 | "top": false, 681 | "visit_count": 580 682 | } 683 | ], 684 | "success": true 685 | } -------------------------------------------------------------------------------- /testxlsx/testxml.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cgxxv/xj2go/51f89335baddcdbe1e0e3ca411beb61ad323a6fc/testxlsx/testxml.xlsx -------------------------------------------------------------------------------- /testxml/[Content_Types].xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /testxml/_rels/.rels: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /testxml/docProps/app.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /testxml/docProps/core.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /testxml/sample.xml: -------------------------------------------------------------------------------- 1 | 2 | some text... 3 | some text... 4 | 5 | some text... 6 | 7 | -------------------------------------------------------------------------------- /testxml/test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | dns1.domain.com 4 | dns2.domain.com 5 | -------------------------------------------------------------------------------- /testxml/xl/_rels/workbook.xml.rels: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /testxml/xl/sharedStrings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ID 5 | 6 | 7 | Class 8 | 9 | 10 | Subject 11 | 12 | 13 | Student 14 | 15 | 16 | Score 17 | 18 | 19 | English 20 | 21 | 22 | Lucy 23 | 24 | 25 | Chinese 26 | 27 | 28 | XiaoMing 29 | 30 | 31 | Math 32 | 33 | 34 | David 35 | 36 | 37 | Sara 38 | 39 | 40 | -------------------------------------------------------------------------------- /testxml/xl/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /testxml/xl/theme/theme1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | -------------------------------------------------------------------------------- /testxml/xl/workbook.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /testxml/xl/worksheets/sheet1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 0 23 | 24 | 25 | 1 26 | 27 | 28 | 2 29 | 30 | 31 | 3 32 | 33 | 34 | 4 35 | 36 | 37 | 38 | 39 | 1 40 | 41 | 42 | 111 43 | 44 | 45 | 5 46 | 47 | 48 | 6 49 | 50 | 51 | 9.800000000000001 52 | 53 | 54 | 55 | 56 | 2 57 | 58 | 59 | 222 60 | 61 | 62 | 7 63 | 64 | 65 | 8 66 | 67 | 68 | 9.9 69 | 70 | 71 | 72 | 73 | 3 74 | 75 | 76 | 222 77 | 78 | 79 | 9 80 | 81 | 82 | 10 83 | 84 | 85 | 8.5 86 | 87 | 88 | 89 | 90 | 4 91 | 92 | 93 | 111 94 | 95 | 96 | 9 97 | 98 | 99 | 11 100 | 101 | 102 | 7.7 103 | 104 | 105 | 106 | 107 | 108 | 109 | &C&"Helvetica Neue,Regular"&12&K000000&P 110 | 111 | 112 | -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | package xj2go 2 | 3 | import ( 4 | "os" 5 | "reflect" 6 | "regexp" 7 | "strings" 8 | ) 9 | 10 | // https://github.com/golang/lint/blob/39d15d55e9777df34cdffde4f406ab27fd2e60c0/lint.go#L695-L731 11 | var commonInitialisms = map[string]bool{ 12 | "API": true, 13 | "ASCII": true, 14 | "CPU": true, 15 | "CSS": true, 16 | "DNS": true, 17 | "EOF": true, 18 | "GUID": true, 19 | "HTML": true, 20 | "HTTP": true, 21 | "HTTPS": true, 22 | "ID": true, 23 | "IP": true, 24 | "JSON": true, 25 | "LHS": true, 26 | "QPS": true, 27 | "RAM": true, 28 | "RHS": true, 29 | "RPC": true, 30 | "SLA": true, 31 | "SMTP": true, 32 | "SSH": true, 33 | "TCP": true, 34 | "TLS": true, 35 | "TTL": true, 36 | "UDP": true, 37 | "UI": true, 38 | "UID": true, 39 | "UUID": true, 40 | "URI": true, 41 | "URL": true, 42 | "UTF8": true, 43 | "VM": true, 44 | "XML": true, 45 | "XSRF": true, 46 | "XSS": true, 47 | } 48 | 49 | func max(nodes []leafNode) int { 50 | n := 0 51 | for _, node := range nodes { 52 | t := strings.Count(node.path, ".") 53 | if n < t { 54 | n = t 55 | } 56 | } 57 | 58 | return n + 1 59 | } 60 | 61 | func pathExists(path string) (bool, error) { 62 | _, err := os.Stat(path) 63 | if err == nil { 64 | return true, nil 65 | } 66 | if os.IsNotExist(err) { 67 | return false, nil 68 | } 69 | return false, err 70 | } 71 | 72 | var toProperCaseRE = regexp.MustCompile(`([A-Z])([a-z]+)`) 73 | 74 | var toProperCaseCache = make(map[string]string) 75 | 76 | func toProperCase(str string) string { 77 | 78 | // Check if already cached 79 | cached, found := toProperCaseCache[str] 80 | if found { 81 | return cached 82 | } 83 | 84 | subProperCase := func(v string) string { 85 | if commonInitialisms[strings.ToTitle(v)] { 86 | v = strings.ToTitle(v) 87 | } else { 88 | v = strings.Title(v) 89 | } 90 | 91 | return v 92 | } 93 | replaced := toProperCaseRE.ReplaceAllStringFunc(str, subProperCase) 94 | s := strings.Split(replaced, "_") 95 | 96 | result := "" 97 | for _, v := range s { 98 | result += subProperCase(v) 99 | } 100 | 101 | // Keep in cache for future call 102 | toProperCaseCache[str] = result 103 | 104 | return result 105 | } 106 | 107 | var toProperTypeRE = regexp.MustCompile(`\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(\.\d+)?(\+\d\d:\d\d|Z)`) 108 | 109 | //TODO: should be optimize for time type 110 | func toProperType(v interface{}) string { 111 | t := reflect.TypeOf(v) 112 | if t.Kind() == reflect.String { 113 | if toProperTypeRE.MatchString(v.(string)) { 114 | return "time.Time" 115 | } 116 | } 117 | 118 | if t.Kind() == reflect.Struct { 119 | //detect xmlVal struct val field 120 | for i := 0; i < t.NumField(); i++ { 121 | if t.Field(i).Name == "val" { 122 | return t.Field(i).Type.String() 123 | } 124 | } 125 | } 126 | // if _, isTime := v.(time.Time); isTime { 127 | // return "time.Time" 128 | // } 129 | 130 | return t.String() 131 | } 132 | -------------------------------------------------------------------------------- /utils_test.go: -------------------------------------------------------------------------------- 1 | package xj2go 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func Test_max(t *testing.T) { 8 | type args struct { 9 | nodes []leafNode 10 | } 11 | tests := []struct { 12 | name string 13 | args args 14 | want int 15 | }{ 16 | { 17 | name: "max test", 18 | args: args{ 19 | nodes: []leafNode{ 20 | { 21 | path: "a.b.c.d.e.f.g.h", 22 | }, 23 | { 24 | path: "a.b.c.d.e", 25 | }, 26 | { 27 | path: "a.b.c", 28 | }, 29 | }, 30 | }, 31 | want: 8, 32 | }, 33 | } 34 | for _, tt := range tests { 35 | t.Run(tt.name, func(t *testing.T) { 36 | if got := max(tt.args.nodes); got != tt.want { 37 | t.Errorf("max() = %v, want %v", got, tt.want) 38 | } 39 | }) 40 | } 41 | } 42 | 43 | func Test_pathExists(t *testing.T) { 44 | tests := []struct { 45 | name string 46 | path string 47 | want bool 48 | wantErr bool 49 | }{ 50 | { 51 | name: "not existed directory", 52 | path: "./temp", 53 | want: false, 54 | wantErr: false, 55 | }, 56 | { 57 | name: "existed directory", 58 | path: "./testjson", 59 | want: true, 60 | wantErr: false, 61 | }, 62 | } 63 | for _, tt := range tests { 64 | t.Run(tt.name, func(t *testing.T) { 65 | got, err := pathExists(tt.path) 66 | if (err != nil) != tt.wantErr { 67 | t.Errorf("pathExists() error = %v, wantErr %v", err, tt.wantErr) 68 | return 69 | } 70 | if got != tt.want { 71 | t.Errorf("pathExists() = %v, want %v", got, tt.want) 72 | } 73 | }) 74 | } 75 | } 76 | 77 | func Test_toProperCase(t *testing.T) { 78 | type args struct { 79 | str string 80 | } 81 | tests := []struct { 82 | name string 83 | args args 84 | want string 85 | }{ 86 | {"toProperCase", args{"read_count"}, "ReadCount"}, 87 | {"toProperCase", args{"read_id"}, "ReadID"}, 88 | {"toProperCase", args{"readIdUrl"}, "ReadIDURL"}, 89 | {"toProperCase", args{"readIdUrl_ip_xss"}, "ReadIDURLIPXSS"}, 90 | {"toProperCase", args{"readIdUrl_ip_xssCpu"}, "ReadIDURLIPXssCPU"}, 91 | {"toProperCase", args{"id"}, "ID"}, 92 | } 93 | for _, tt := range tests { 94 | t.Run(tt.name, func(t *testing.T) { 95 | if got := toProperCase(tt.args.str); got != tt.want { 96 | t.Errorf("toProperCase() = %v, want %v", got, tt.want) 97 | } 98 | }) 99 | } 100 | } 101 | 102 | func Test_toProperType(t *testing.T) { 103 | tests := []struct { 104 | name string 105 | val interface{} 106 | want string 107 | }{ 108 | { 109 | name: "string", 110 | val: "this is a test", 111 | want: "string", 112 | }, 113 | { 114 | name: "int", 115 | val: 1, 116 | want: "int", 117 | }, 118 | { 119 | name: "time", 120 | val: "2017-10-31T11:59:17+08:00", 121 | want: "time.Time", 122 | }, 123 | } 124 | for _, tt := range tests { 125 | t.Run(tt.name, func(t *testing.T) { 126 | if got := toProperType(tt.val); got != tt.want { 127 | t.Errorf("toProperType() = %v, want %v", got, tt.want) 128 | } 129 | }) 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /xj2go.go: -------------------------------------------------------------------------------- 1 | package xj2go 2 | 3 | import ( 4 | "bytes" 5 | "encoding/xml" 6 | "log" 7 | ) 8 | 9 | // XJ define xj2go struct 10 | type XJ struct { 11 | // xml or json file 12 | Filepath string 13 | // the pkg name for struct 14 | Pkgname string 15 | // the root name for json bytes 16 | Rootname string 17 | } 18 | 19 | // New return a xj2go instance 20 | func New(filepath, pkgname, rootname string) *XJ { 21 | return &XJ{ 22 | Filepath: filepath, 23 | Pkgname: pkgname, 24 | Rootname: rootname, 25 | } 26 | } 27 | 28 | // XMLToGo convert xml to go struct, then write this struct to a go file 29 | func (xj *XJ) XMLToGo() error { 30 | filename, err := checkFile(xj.Filepath, xj.Pkgname) 31 | if err != nil { 32 | log.Fatal(err) 33 | return err 34 | } 35 | 36 | nodes, err := xmlToLeafNodes(xj.Filepath) 37 | if err != nil { 38 | log.Fatal(err) 39 | return err 40 | } 41 | strcts := leafNodesToStrcts("xml", nodes) 42 | 43 | return writeStruct(filename, xj.Pkgname, strcts) 44 | } 45 | 46 | // XMLBytesToGo convert xml bytes to struct, then the struct will be writed to ./{pkg}/{filename}.go 47 | func XMLBytesToGo(filename, pkgname string, b *[]byte) error { 48 | filename, err := checkFile(filename, pkgname) 49 | if err != nil { 50 | log.Fatal(err) 51 | return err 52 | } 53 | 54 | r := bytes.NewReader(*b) 55 | m, err := decodeXML(xml.NewDecoder(r), "", nil) 56 | if err != nil { 57 | log.Fatal(err) 58 | return err 59 | } 60 | 61 | nodes, err := leafNodes(m) 62 | if err != nil { 63 | log.Fatal(err) 64 | return err 65 | } 66 | strcts := leafNodesToStrcts("xml", nodes) 67 | 68 | return writeStruct(filename, pkgname, strcts) 69 | } 70 | 71 | // JSONToGo convert json to go struct, then write this struct to a go file 72 | func (xj *XJ) JSONToGo() error { 73 | filename, err := checkFile(xj.Filepath, xj.Pkgname) 74 | if err != nil { 75 | log.Fatal(err) 76 | return err 77 | } 78 | 79 | nodes, err := jsonToLeafNodes(xj.Rootname, xj.Filepath) 80 | if err != nil { 81 | log.Fatal(err) 82 | return err 83 | } 84 | strcts := leafNodesToStrcts("json", nodes) 85 | 86 | return writeStruct(filename, xj.Pkgname, strcts) 87 | } 88 | 89 | // JSONBytesToGo convert json bytes to struct, then the struct will be writed to ./{pkg}/{filename}.go 90 | func JSONBytesToGo(filename, pkgname, rootname string, b *[]byte) error { 91 | filename, err := checkFile(filename, pkgname) 92 | if err != nil { 93 | log.Fatal(err) 94 | return err 95 | } 96 | 97 | m, err := jsonBytesToMap(pkgname, rootname, b) 98 | if err != nil { 99 | log.Fatal(err) 100 | return err 101 | } 102 | 103 | ns, err := leafNodes(m) 104 | if err != nil { 105 | log.Fatal(err) 106 | return err 107 | } 108 | 109 | nodes, err := reLeafNodes(ns, rootname) 110 | if err != nil { 111 | log.Fatal(err) 112 | return err 113 | } 114 | 115 | strcts := leafNodesToStrcts("json", nodes) 116 | 117 | return writeStruct(filename, pkgname, strcts) 118 | } 119 | -------------------------------------------------------------------------------- /xj2go_test.go: -------------------------------------------------------------------------------- 1 | package xj2go 2 | 3 | import ( 4 | "archive/zip" 5 | "bytes" 6 | "io" 7 | "io/ioutil" 8 | "path" 9 | "strconv" 10 | "strings" 11 | "testing" 12 | ) 13 | 14 | func Test_xmlToPaths(t *testing.T) { 15 | fs := []string{ 16 | "./testxml/[Content_Types].xml", 17 | "./testxml/xl/workbook.xml", 18 | "./testxml/xl/styles.xml", 19 | "./testxml/xl/sharedStrings.xml", 20 | "./testxml/xl/_rels/workbook.xml.rels", 21 | "./testxml/xl/theme/theme1.xml", 22 | "./testxml/xl/worksheets/sheet1.xml", 23 | "./testxml/docProps/app.xml", 24 | "./testxml/docProps/core.xml", 25 | "./testxml/test.xml", 26 | "./testxml/sample.xml", 27 | } 28 | 29 | for k, v := range fs { 30 | t.Run("xml to paths"+strconv.Itoa(k), func(t *testing.T) { 31 | _, err := xmlToLeafNodes(v) 32 | if err != nil { 33 | t.Errorf("xmlToMap() error = %v", err) 34 | return 35 | } 36 | }) 37 | } 38 | } 39 | 40 | func Test_XMLToGo(t *testing.T) { 41 | fs := []string{ 42 | "./testxml/[Content_Types].xml", 43 | "./testxml/xl/workbook.xml", 44 | "./testxml/xl/styles.xml", 45 | "./testxml/xl/sharedStrings.xml", 46 | "./testxml/xl/_rels/workbook.xml.rels", 47 | "./testxml/xl/theme/theme1.xml", 48 | "./testxml/xl/worksheets/sheet1.xml", 49 | "./testxml/docProps/app.xml", 50 | "./testxml/docProps/core.xml", 51 | "./testxml/test.xml", 52 | "./testxml/sample.xml", 53 | } 54 | 55 | pkgname := "excel" 56 | for k, v := range fs { 57 | t.Run("xml to go "+strconv.Itoa(k), func(t *testing.T) { 58 | xj := New(v, pkgname, "") 59 | err := xj.XMLToGo() 60 | if err != nil { 61 | t.Errorf("XMLToGo() error = %v", err) 62 | return 63 | } 64 | }) 65 | } 66 | } 67 | 68 | func Test_XMLBytesToGo(t *testing.T) { 69 | filename := "./testxlsx/testxml.xlsx" 70 | b, err := ioutil.ReadFile(filename) 71 | if err != nil { 72 | t.Errorf("ioutil.ReadFile() error = %v", err) 73 | } 74 | 75 | zr, err := zip.NewReader(bytes.NewReader(b), int64(len(b))) 76 | if err != nil { 77 | t.Errorf("zip.NewReader() error = %v", err) 78 | } 79 | 80 | pkgname := "excel2" 81 | for _, file := range zr.File { 82 | if path.Base(file.Name)[:1] == "." { 83 | continue 84 | } 85 | 86 | rc, err := file.Open() 87 | if err != nil { 88 | t.Errorf("file.Open() error = %v", err) 89 | } 90 | buff := bytes.NewBuffer(nil) 91 | io.Copy(buff, rc) 92 | rc.Close() 93 | b := buff.Bytes() 94 | if err := XMLBytesToGo(file.Name, pkgname, &b); err != nil { 95 | t.Errorf("XMLBytesToGo() error = %v", err) 96 | } 97 | } 98 | } 99 | 100 | func TestXJ_JSONToGo(t *testing.T) { 101 | fs := []string{ 102 | "./testjson/topics.json", 103 | "./testjson/smartStreetsAPI.json", 104 | "./testjson/githubAPI.json", 105 | } 106 | 107 | pkgname := "xjson" 108 | for k, v := range fs { 109 | t.Run("json to go "+strconv.Itoa(k), func(t *testing.T) { 110 | root := strings.TrimSuffix(path.Base(v), path.Ext(v)) 111 | xj := New(v, pkgname, root) 112 | err := xj.JSONToGo() 113 | if err != nil { 114 | t.Errorf("JSONToGo() error = %v", err) 115 | return 116 | } 117 | }) 118 | } 119 | } 120 | 121 | func Test_JSONBytesToGo(t *testing.T) { 122 | fs := []string{ 123 | "./testjson/topics.json", 124 | "./testjson/smartStreetsAPI.json", 125 | "./testjson/githubAPI.json", 126 | } 127 | 128 | pkgname := "xjson2" 129 | for k, v := range fs { 130 | t.Run("json bytes to go "+strconv.Itoa(k), func(t *testing.T) { 131 | root := strings.TrimSuffix(path.Base(v), path.Ext(v)) 132 | b, err := ioutil.ReadFile(v) 133 | if err != nil { 134 | t.Errorf("ioutil.ReadFile() error = %v", err) 135 | } 136 | if err := JSONBytesToGo(path.Base(v), pkgname, root, &b); err != nil { 137 | t.Errorf("JSONBytesToGo() error = %v", err) 138 | return 139 | } 140 | }) 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /xml.go: -------------------------------------------------------------------------------- 1 | package xj2go 2 | 3 | import ( 4 | "encoding/xml" 5 | "errors" 6 | "io" 7 | "log" 8 | "os" 9 | "strings" 10 | 11 | "golang.org/x/net/html/charset" 12 | ) 13 | 14 | type xmlVal struct { 15 | val string 16 | attr bool 17 | } 18 | 19 | func xmlToLeafNodes(filename string) ([]leafNode, error) { 20 | m, err := xmlToMap(filename) 21 | if err != nil { 22 | log.Fatal(err) 23 | return nil, err 24 | } 25 | 26 | return leafNodes(m) 27 | } 28 | 29 | func xmlToMap(filename string) (map[string]interface{}, error) { 30 | file, err := os.Open(filename) 31 | if err != nil { 32 | log.Fatal(err) 33 | return nil, err 34 | } 35 | 36 | m, err := decodeXML(xml.NewDecoder(file), "", nil) 37 | if err != nil { 38 | return nil, err 39 | } 40 | 41 | return m, nil 42 | } 43 | 44 | func decodeXML(d *xml.Decoder, sk string, attr []xml.Attr) (map[string]interface{}, error) { 45 | if d.CharsetReader == nil { 46 | d.CharsetReader = func(c string, i io.Reader) (io.Reader, error) { 47 | return charset.NewReaderLabel(c, i) 48 | } 49 | } 50 | m := make(map[string]interface{}) 51 | ma := make(map[string]interface{}) 52 | if sk != "" { 53 | for _, v := range attr { 54 | ma[v.Name.Local] = xmlVal{v.Value, true} 55 | } 56 | } 57 | 58 | for { 59 | t, err := d.Token() 60 | if err != nil { 61 | if err != io.EOF { 62 | return nil, errors.New("xml.Decoder.Token() - " + err.Error()) 63 | } 64 | return nil, err 65 | } 66 | switch element := t.(type) { 67 | case xml.StartElement: 68 | if sk == "" { 69 | return decodeXML(d, element.Name.Local, element.Attr) 70 | } 71 | mm, err := decodeXML(d, element.Name.Local, element.Attr) 72 | if err != nil { 73 | return nil, err 74 | } 75 | 76 | var k string 77 | var v interface{} 78 | for k, v = range mm { 79 | break 80 | } 81 | 82 | if vv, ok := ma[k]; ok { 83 | var a []interface{} 84 | switch vv.(type) { 85 | case []interface{}: 86 | a = vv.([]interface{}) 87 | default: 88 | a = []interface{}{vv} 89 | } 90 | a = append(a, v) 91 | ma[k] = a 92 | } else { 93 | ma[k] = v 94 | } 95 | case xml.EndElement: 96 | if len(ma) > 0 { 97 | m[sk] = ma 98 | } 99 | return m, nil 100 | case xml.CharData: 101 | tt := strings.Trim(string(element), "\t\r\b\n ") 102 | if tt != "" { 103 | if sk != "" { 104 | m[sk] = tt 105 | } 106 | } 107 | default: 108 | } 109 | } 110 | } 111 | --------------------------------------------------------------------------------