gdoc.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547
  1. package main
  2. import (
  3. "encoding/json"
  4. "errors"
  5. "flag"
  6. "fmt"
  7. "go/ast"
  8. "go/parser"
  9. "go/token"
  10. "os"
  11. "path"
  12. "path/filepath"
  13. "reflect"
  14. "runtime"
  15. "strings"
  16. )
  17. // gloabl var.
  18. var (
  19. ErrParams = errors.New("err params")
  20. _gopath = filepath.SplitList(os.Getenv("GOPATH"))
  21. )
  22. var (
  23. dir string
  24. pkgs = make(map[string]*ast.Package)
  25. rlpkgs = make(map[string]*ast.Package)
  26. definitions = make(map[string]*Schema)
  27. swagger = Swagger{
  28. Definitions: make(map[string]*Schema),
  29. Paths: make(map[string]*Item),
  30. SwaggerVersion: "2.0",
  31. Infos: Information{
  32. Title: "go-common api",
  33. Description: "api",
  34. Version: "1.0",
  35. Contact: Contact{
  36. EMail: "lintanghui@bilibili.com",
  37. },
  38. License: &License{
  39. Name: "Apache 2.0",
  40. URL: "http://www.apache.org/licenses/LICENSE-2.0.html",
  41. },
  42. },
  43. }
  44. stdlibObject = map[string]string{
  45. "&{time Time}": "time.Time",
  46. }
  47. )
  48. // refer to builtin.go
  49. var basicTypes = map[string]string{
  50. "bool": "boolean:",
  51. "uint": "integer:int32",
  52. "uint8": "integer:int32",
  53. "uint16": "integer:int32",
  54. "uint32": "integer:int32",
  55. "uint64": "integer:int64",
  56. "int": "integer:int64",
  57. "int8": "integer:int32",
  58. "int16": "integer:int32",
  59. "int32": "integer:int32",
  60. "int64": "integer:int64",
  61. "uintptr": "integer:int64",
  62. "float32": "number:float",
  63. "float64": "number:double",
  64. "string": "string:",
  65. "complex64": "number:float",
  66. "complex128": "number:double",
  67. "byte": "string:byte",
  68. "rune": "string:byte",
  69. // builtin golang objects
  70. "time.Time": "string:string",
  71. }
  72. func main() {
  73. flag.StringVar(&dir, "d", "./", "specific project dir")
  74. flag.Parse()
  75. err := ParseFromDir(dir)
  76. if err != nil {
  77. panic(err)
  78. }
  79. parseModel(pkgs)
  80. parseModel(rlpkgs)
  81. parseRouter()
  82. fd, err := os.Create(path.Join(dir, "swagger.json"))
  83. if err != nil {
  84. panic(err)
  85. }
  86. b, _ := json.MarshalIndent(swagger, "", " ")
  87. fd.Write(b)
  88. }
  89. // ParseFromDir parse ast pkg from dir.
  90. func ParseFromDir(dir string) (err error) {
  91. filepath.Walk(dir, func(fpath string, fileInfo os.FileInfo, err error) error {
  92. if err != nil {
  93. return nil
  94. }
  95. if !fileInfo.IsDir() {
  96. return nil
  97. }
  98. err = parseFromDir(fpath)
  99. return err
  100. })
  101. return
  102. }
  103. func parseFromDir(dir string) (err error) {
  104. fset := token.NewFileSet()
  105. pkgFolder, err := parser.ParseDir(fset, dir, func(info os.FileInfo) bool {
  106. name := info.Name()
  107. return !info.IsDir() && !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".go")
  108. }, parser.ParseComments)
  109. if err != nil {
  110. return
  111. }
  112. for k, p := range pkgFolder {
  113. pkgs[k] = p
  114. }
  115. return
  116. }
  117. func parseImport(dir string) (err error) {
  118. fset := token.NewFileSet()
  119. pkgFolder, err := parser.ParseDir(fset, dir, func(info os.FileInfo) bool {
  120. name := info.Name()
  121. return !info.IsDir() && !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".go")
  122. }, parser.ParseComments)
  123. if err != nil {
  124. return
  125. }
  126. for k, p := range pkgFolder {
  127. rlpkgs[k] = p
  128. }
  129. return
  130. }
  131. func parseModel(pkgs map[string]*ast.Package) {
  132. for _, p := range pkgs {
  133. for _, f := range p.Files {
  134. for _, im := range f.Imports {
  135. if !isSystemPackage(im.Path.Value) {
  136. for _, gp := range _gopath {
  137. path := gp + "/src/" + strings.Trim(im.Path.Value, "\"")
  138. if isExist(path) {
  139. parseImport(path)
  140. }
  141. }
  142. }
  143. }
  144. scom := parseStructComment(f)
  145. for _, obj := range f.Scope.Objects {
  146. if obj.Kind == ast.Typ {
  147. objName := obj.Name
  148. schema := &Schema{
  149. Title: objName,
  150. Type: "object",
  151. }
  152. ts, ok := obj.Decl.(*ast.TypeSpec)
  153. if !ok {
  154. fmt.Printf("obj type error %v ", obj.Kind)
  155. }
  156. st, ok := ts.Type.(*ast.StructType)
  157. if !ok {
  158. continue
  159. }
  160. properites := make(map[string]*Propertie)
  161. for _, fd := range st.Fields.List {
  162. if len(fd.Names) == 0 {
  163. continue
  164. }
  165. name, required, omit, desc := parseFieldTag(fd)
  166. if omit {
  167. continue
  168. }
  169. isSlice, realType, sType := typeAnalyser(fd)
  170. if (isSlice && isBasicType(realType)) || sType == "object" {
  171. if len(strings.Split(realType, " ")) > 1 {
  172. realType = strings.Replace(realType, " ", ".", -1)
  173. realType = strings.Replace(realType, "&", "", -1)
  174. realType = strings.Replace(realType, "{", "", -1)
  175. realType = strings.Replace(realType, "}", "", -1)
  176. }
  177. }
  178. mp := &Propertie{}
  179. if isSlice {
  180. mp.Type = "array"
  181. if isBasicType(strings.Replace(realType, "[]", "", -1)) {
  182. typeFormat := strings.Split(sType, ":")
  183. mp.Items = &Propertie{
  184. Type: typeFormat[0],
  185. Format: typeFormat[1],
  186. }
  187. } else {
  188. ss := strings.Split(realType, ".")
  189. mp.RefImport = ss[len(ss)-1]
  190. mp.Type = "array"
  191. mp.Items = &Propertie{
  192. Ref: "#/definitions/" + mp.RefImport,
  193. Type: sType,
  194. }
  195. }
  196. } else {
  197. if sType == "object" {
  198. ss := strings.Split(realType, ".")
  199. mp.RefImport = ss[len(ss)-1]
  200. mp.Type = sType
  201. mp.Ref = "#/definitions/" + mp.RefImport
  202. } else if isBasicType(realType) {
  203. typeFormat := strings.Split(sType, ":")
  204. mp.Type = typeFormat[0]
  205. mp.Format = typeFormat[1]
  206. } else if realType == "map" {
  207. typeFormat := strings.Split(sType, ":")
  208. mp.AdditionalProperties = &Propertie{
  209. Type: typeFormat[0],
  210. Format: typeFormat[1],
  211. }
  212. }
  213. }
  214. if name == "" {
  215. name = fd.Names[0].Name
  216. }
  217. if required {
  218. schema.Required = append(schema.Required, name)
  219. }
  220. mp.Description = desc
  221. if scm, ok := scom[obj.Name]; ok {
  222. if cm, ok := scm.field[fd.Names[0].Name]; ok {
  223. mp.Description = cm + desc
  224. }
  225. }
  226. properites[name] = mp
  227. }
  228. if scm, ok := scom[obj.Name]; ok {
  229. schema.Description = scm.comment
  230. }
  231. schema.Properties = properites
  232. definitions[schema.Title] = schema
  233. }
  234. }
  235. }
  236. }
  237. }
  238. func parseFieldTag(field *ast.Field) (name string, required, omit bool, tagDes string) {
  239. if field.Tag == nil {
  240. return
  241. }
  242. tag := reflect.StructTag(strings.Trim(field.Tag.Value, "`"))
  243. param := tag.Get("form")
  244. if param != "" {
  245. params := strings.Split(param, ",")
  246. if len(params) > 0 {
  247. name = params[0]
  248. }
  249. if len(params) == 2 && params[1] == "split" {
  250. tagDes = "数组,按逗号分隔"
  251. }
  252. }
  253. if def := tag.Get("default"); def != "" {
  254. tagDes = fmt.Sprintf("%s 默认值 %s", tagDes, def)
  255. }
  256. validate := tag.Get("validate")
  257. if validate != "" {
  258. params := strings.Split(validate, ",")
  259. for _, param := range params {
  260. switch {
  261. case param == "required":
  262. required = true
  263. case strings.HasPrefix(param, "min"):
  264. tagDes = fmt.Sprintf("%s 最小值 %s", tagDes, strings.Split(param, "=")[1])
  265. case strings.HasPrefix(param, "max"):
  266. tagDes = fmt.Sprintf("%s 最大值 %s", tagDes, strings.Split(param, "=")[1])
  267. }
  268. }
  269. }
  270. // parse json response.
  271. json := tag.Get("json")
  272. if json != "" {
  273. jsons := strings.Split(json, ",")
  274. if len(jsons) > 0 {
  275. if jsons[0] == "-" {
  276. omit = true
  277. return
  278. }
  279. }
  280. }
  281. return
  282. }
  283. func parseRouter() {
  284. for _, p := range pkgs {
  285. if p.Name != "http" {
  286. continue
  287. }
  288. fmt.Printf("开始解析生成swagger文档\n")
  289. for _, f := range p.Files {
  290. for _, decl := range f.Decls {
  291. if fdecl, ok := decl.(*ast.FuncDecl); ok {
  292. if fdecl.Doc != nil {
  293. path, req, resp, item, err := parseFuncDoc(fdecl.Doc)
  294. if err != nil {
  295. fmt.Printf("解析失败 注解错误 %v\n", err)
  296. continue
  297. }
  298. if path != "" && err == nil {
  299. fmt.Printf("解析 %s 完成 请求参数为 %s 返回结构为 %s\n", path, req, resp)
  300. swagger.Paths[path] = item
  301. }
  302. }
  303. }
  304. }
  305. }
  306. }
  307. }
  308. func parseFuncDoc(f *ast.CommentGroup) (path, reqObj, respObj string, item *Item, err error) {
  309. item = new(Item)
  310. op := new(Operation)
  311. params := make([]*Parameter, 0)
  312. response := make(map[string]*Response)
  313. for _, d := range f.List {
  314. t := strings.TrimSpace(strings.TrimPrefix(d.Text, "//"))
  315. content := strings.Split(t, " ")
  316. switch content[0] {
  317. case "@params":
  318. if len(content) < 2 {
  319. err = fmt.Errorf("err params %s", content)
  320. return
  321. }
  322. reqObj = content[1]
  323. if model, ok := definitions[content[1]]; ok {
  324. for n, p := range model.Properties {
  325. param := &Parameter{
  326. In: "query",
  327. Name: n,
  328. Description: p.Description,
  329. Type: p.Type,
  330. Format: p.Format,
  331. }
  332. for _, p := range model.Required {
  333. if p == n {
  334. param.Required = true
  335. }
  336. }
  337. params = append(params, param)
  338. }
  339. } else {
  340. err = ErrParams
  341. return
  342. }
  343. case "@router":
  344. if len(content) != 3 {
  345. err = ErrParams
  346. return
  347. }
  348. switch content[1] {
  349. case "get":
  350. item.Get = op
  351. case "post":
  352. item.Post = op
  353. }
  354. path = content[2]
  355. op.OperationID = path
  356. case "@response":
  357. if len(content) < 2 {
  358. err = fmt.Errorf("err response %s", content)
  359. return
  360. }
  361. var (
  362. isarray bool
  363. ismap bool
  364. )
  365. if strings.HasPrefix(content[1], "[]") {
  366. isarray = true
  367. respObj = content[1][2:]
  368. } else if strings.HasPrefix(content[1], "map[]") {
  369. ismap = true
  370. respObj = content[1][5:]
  371. } else {
  372. respObj = content[1]
  373. }
  374. defini, ok := definitions[respObj]
  375. if !ok {
  376. err = ErrParams
  377. return
  378. }
  379. var resp *Propertie
  380. if isarray {
  381. resp = &Propertie{
  382. Type: "array",
  383. Items: &Propertie{
  384. Type: "object",
  385. Ref: "#/definitions/" + respObj,
  386. },
  387. }
  388. } else if ismap {
  389. resp = &Propertie{
  390. Type: "object",
  391. AdditionalProperties: &Propertie{
  392. Ref: "#/definitions/" + respObj,
  393. },
  394. }
  395. } else {
  396. resp = &Propertie{
  397. Type: "object",
  398. Ref: "#/definitions/" + respObj,
  399. }
  400. }
  401. response["200"] = &Response{
  402. Schema: &Schema{
  403. Type: "object",
  404. Properties: map[string]*Propertie{
  405. "code": &Propertie{
  406. Type: "integer",
  407. Description: "错误码描述",
  408. },
  409. "data": resp,
  410. "message": &Propertie{
  411. Type: "string",
  412. Description: "错误码文本描述",
  413. },
  414. "ttl": &Propertie{
  415. Type: "integer",
  416. Format: "int64",
  417. Description: "客户端限速时间",
  418. },
  419. },
  420. },
  421. Description: "服务成功响应内容",
  422. }
  423. op.Responses = response
  424. for _, rl := range defini.Properties {
  425. if rl.RefImport != "" {
  426. swagger.Definitions[rl.RefImport] = definitions[rl.RefImport]
  427. }
  428. }
  429. swagger.Definitions[respObj] = defini
  430. case "@description":
  431. op.Description = content[1]
  432. }
  433. }
  434. op.Parameters = params
  435. return
  436. }
  437. type structComment struct {
  438. comment string
  439. field map[string]string
  440. }
  441. func parseStructComment(f *ast.File) (scom map[string]structComment) {
  442. scom = make(map[string]structComment)
  443. for _, d := range f.Decls {
  444. switch specDecl := d.(type) {
  445. case *ast.GenDecl:
  446. if specDecl.Tok == token.TYPE {
  447. for _, s := range specDecl.Specs {
  448. switch tp := s.(*ast.TypeSpec).Type.(type) {
  449. case *ast.StructType:
  450. fcom := make(map[string]string)
  451. for _, fd := range tp.Fields.List {
  452. if len(fd.Names) == 0 {
  453. continue
  454. }
  455. if len(fd.Comment.Text()) > 0 {
  456. fcom[fd.Names[0].Name] = strings.TrimSuffix(fd.Comment.Text(), "\n")
  457. }
  458. }
  459. sspec := s.(*ast.TypeSpec)
  460. scom[sspec.Name.String()] = structComment{comment: strings.TrimSuffix(specDecl.Doc.Text(), "\n"), field: fcom}
  461. }
  462. }
  463. }
  464. }
  465. }
  466. return
  467. }
  468. func isBasicType(Type string) bool {
  469. if _, ok := basicTypes[Type]; ok {
  470. return true
  471. }
  472. return false
  473. }
  474. func typeAnalyser(f *ast.Field) (isSlice bool, realType, swaggerType string) {
  475. if arr, ok := f.Type.(*ast.ArrayType); ok {
  476. if isBasicType(fmt.Sprint(arr.Elt)) {
  477. return true, fmt.Sprintf("[]%v", arr.Elt), basicTypes[fmt.Sprint(arr.Elt)]
  478. }
  479. if mp, ok := arr.Elt.(*ast.MapType); ok {
  480. return false, fmt.Sprintf("map[%v][%v]", mp.Key, mp.Value), "object"
  481. }
  482. if star, ok := arr.Elt.(*ast.StarExpr); ok {
  483. return true, fmt.Sprint(star.X), "object"
  484. }
  485. basicType := fmt.Sprint(arr.Elt)
  486. if object, isStdLibObject := stdlibObject[basicType]; isStdLibObject {
  487. basicType = object
  488. }
  489. if k, ok := basicTypes[basicType]; ok {
  490. return true, basicType, k
  491. }
  492. return true, fmt.Sprint(arr.Elt), "object"
  493. }
  494. switch t := f.Type.(type) {
  495. case *ast.StarExpr:
  496. basicType := fmt.Sprint(t.X)
  497. if k, ok := basicTypes[basicType]; ok {
  498. return false, basicType, k
  499. }
  500. return false, basicType, "object"
  501. case *ast.MapType:
  502. val := fmt.Sprintf("%v", t.Value)
  503. if isBasicType(val) {
  504. return false, "map", basicTypes[val]
  505. }
  506. return false, val, "object"
  507. }
  508. basicType := fmt.Sprint(f.Type)
  509. if object, isStdLibObject := stdlibObject[basicType]; isStdLibObject {
  510. basicType = object
  511. }
  512. if k, ok := basicTypes[basicType]; ok {
  513. return false, basicType, k
  514. }
  515. return false, basicType, "object"
  516. }
  517. func isSystemPackage(pkgpath string) bool {
  518. goroot := os.Getenv("GOROOT")
  519. if goroot == "" {
  520. goroot = runtime.GOROOT()
  521. }
  522. wg, _ := filepath.EvalSymlinks(filepath.Join(goroot, "src", "pkg", pkgpath))
  523. if isExist(wg) {
  524. return true
  525. }
  526. wg, _ = filepath.EvalSymlinks(filepath.Join(goroot, "src", pkgpath))
  527. return isExist(wg)
  528. }
  529. func isExist(path string) bool {
  530. _, err := os.Stat(path)
  531. return err == nil || os.IsExist(err)
  532. }