Przeglądaj źródła

feature:添加自动生成 swagger

liuyaqi 2 lat temu
rodzic
commit
2cd0d8f97f

+ 1 - 0
opms_parent/.gitignore

@@ -28,3 +28,4 @@ admin
 
 .idea/
 gen.sh
+swaggerui/swagger.yml

+ 9 - 0
opms_parent/app/handler/contract/ctr_contract.go

@@ -12,6 +12,7 @@ import (
 
 type CtrContract struct{}
 
+// swagger:route 合同,测试tag 合同详情
 func (c *CtrContract) Get(ctx context.Context, req *model.IdRequiredReq, rsp *comm_def.CommonMsg) error {
 	g.Log().Infof("CtrContract.Get request %#v ", *req)
 	s, err := service.NewCtrContractService(ctx)
@@ -29,6 +30,7 @@ func (c *CtrContract) Get(ctx context.Context, req *model.IdRequiredReq, rsp *co
 	return nil
 }
 
+// swagger:route 合同,测试tag 查询合同
 func (c *CtrContract) List(ctx context.Context, req *model.CtrContractListReq, rsp *comm_def.CommonMsg) error {
 	g.Log().Infof("CtrContract.List request %#v ", *req)
 	s, err := service.NewCtrContractService(ctx)
@@ -52,6 +54,7 @@ func (c *CtrContract) List(ctx context.Context, req *model.CtrContractListReq, r
 	return nil
 }
 
+// swagger:route 合同,测试tag 查询合同动态列表
 func (c *CtrContract) DynamicsList(ctx context.Context, req *model.CtrContractDynamicsListReq, rsp *comm_def.CommonMsg) error {
 	g.Log().Infof("CtrContract.GetDynamicsList request %#v ", *req)
 	s, err := service.NewCtrContractService(ctx)
@@ -75,6 +78,7 @@ func (c *CtrContract) DynamicsList(ctx context.Context, req *model.CtrContractDy
 	return nil
 }
 
+// swagger:route 合同,测试tag 新增合同
 func (c *CtrContract) Add(ctx context.Context, req *model.CtrContractAddReq, rsp *comm_def.CommonMsg) error {
 	g.Log().Infof("CtrContract.Add request %#v ", *req)
 	s, err := service.NewCtrContractService(ctx)
@@ -92,6 +96,7 @@ func (c *CtrContract) Add(ctx context.Context, req *model.CtrContractAddReq, rsp
 	return nil
 }
 
+// swagger:route 合同,测试tag 更新合同
 func (c *CtrContract) Update(ctx context.Context, req *model.CtrContractUpdateReq, rsp *comm_def.CommonMsg) error {
 	g.Log().Infof("CtrContract.Update request %#v ", *req)
 	s, err := service.NewCtrContractService(ctx)
@@ -108,6 +113,7 @@ func (c *CtrContract) Update(ctx context.Context, req *model.CtrContractUpdateRe
 	return nil
 }
 
+// swagger:route 合同,测试tag 转移合同
 func (c *CtrContract) Transfer(ctx context.Context, req *model.CtrContractTransferReq, rsp *comm_def.CommonMsg) error {
 	g.Log().Infof("CtrContract.Transfer request %#v ", *req)
 	s, err := service.NewCtrContractService(ctx)
@@ -124,6 +130,9 @@ func (c *CtrContract) Transfer(ctx context.Context, req *model.CtrContractTransf
 	return nil
 }
 
+// 多行注释测试
+// swagger:route 合同,测试tag 删除合同
+// 多行注释测试
 func (c *CtrContract) Delete(ctx context.Context, req *model.IdsReq, rsp *comm_def.CommonMsg) error {
 	g.Log().Infof("CtrContract.Delete request %#v ", *req)
 	s, err := service.NewCtrContractService(ctx)

+ 4 - 0
opms_parent/app/handler/contract/ctr_contract_append.go

@@ -12,6 +12,7 @@ import (
 
 type CtrContractAppend struct{}
 
+// swagger:route 合同附件 查询合同附件
 func (c *CtrContractAppend) List(ctx context.Context, req *model.CtrContractAppendListReq, rsp *comm_def.CommonMsg) error {
 	g.Log().Infof("CtrContractAppend.List request %#v ", *req)
 	s, err := service.NewCtrContractAppendService(ctx)
@@ -35,6 +36,7 @@ func (c *CtrContractAppend) List(ctx context.Context, req *model.CtrContractAppe
 	return nil
 }
 
+// swagger:route 合同附件 添加合同附件
 func (c *CtrContractAppend) Add(ctx context.Context, req *model.CtrContractAppendAddReq, rsp *comm_def.CommonMsg) error {
 	g.Log().Infof("CtrContractAppend.Add request %#v ", *req)
 	s, err := service.NewCtrContractAppendService(ctx)
@@ -52,6 +54,7 @@ func (c *CtrContractAppend) Add(ctx context.Context, req *model.CtrContractAppen
 	return nil
 }
 
+// swagger:route 合同附件 更新合同附件
 func (c *CtrContractAppend) Update(ctx context.Context, req *model.CtrContractAppendUpdateReq, rsp *comm_def.CommonMsg) error {
 	g.Log().Infof("CtrContractAppend.Update request %#v ", *req)
 	s, err := service.NewCtrContractAppendService(ctx)
@@ -68,6 +71,7 @@ func (c *CtrContractAppend) Update(ctx context.Context, req *model.CtrContractAp
 	return nil
 }
 
+// swagger:route 合同附件 删除合同附件
 func (c *CtrContractAppend) Delete(ctx context.Context, req *model.IdsReq, rsp *comm_def.CommonMsg) error {
 	g.Log().Infof("CtrContractAppend.Delete request %#v ", *req)
 	s, err := service.NewCtrContractAppendService(ctx)

+ 4 - 0
opms_parent/app/handler/contract/ctr_contract_collection.go

@@ -12,6 +12,7 @@ import (
 
 type CtrContractCollection struct{}
 
+// swagger:route 合同回款 查询合同回款
 func (c *CtrContractCollection) List(ctx context.Context, req *model.CtrContractCollectionListReq, rsp *comm_def.CommonMsg) error {
 	g.Log().Infof("CtrContractCollection.List request %#v ", *req)
 	s, err := service.NewCtrContractCollectionService(ctx)
@@ -35,6 +36,7 @@ func (c *CtrContractCollection) List(ctx context.Context, req *model.CtrContract
 	return nil
 }
 
+// swagger:route 合同回款 添加合同回款
 func (c *CtrContractCollection) Add(ctx context.Context, req *model.CtrContractCollectionAddReq, rsp *comm_def.CommonMsg) error {
 	g.Log().Infof("CtrContractCollection.Add request %#v ", *req)
 	s, err := service.NewCtrContractCollectionService(ctx)
@@ -52,6 +54,7 @@ func (c *CtrContractCollection) Add(ctx context.Context, req *model.CtrContractC
 	return nil
 }
 
+// swagger:route 合同回款 更新合同回款
 func (c *CtrContractCollection) Update(ctx context.Context, req *model.CtrContractCollectionUpdateReq, rsp *comm_def.CommonMsg) error {
 	g.Log().Infof("CtrContractCollection.Update request %#v ", *req)
 	s, err := service.NewCtrContractCollectionService(ctx)
@@ -68,6 +71,7 @@ func (c *CtrContractCollection) Update(ctx context.Context, req *model.CtrContra
 	return nil
 }
 
+// swagger:route 合同回款 删除合同回款
 func (c *CtrContractCollection) Delete(ctx context.Context, req *model.IdsReq, rsp *comm_def.CommonMsg) error {
 	g.Log().Infof("CtrContractCollection.Delete request %#v ", *req)
 	s, err := service.NewCtrContractCollectionService(ctx)

+ 5 - 0
opms_parent/app/handler/contract/ctr_contract_collection_plan.go

@@ -12,6 +12,7 @@ import (
 
 type CtrContractCollectionPlan struct{}
 
+// swagger:route 合同回款计划 合同回款计划详情
 func (c *CtrContractCollectionPlan) Get(ctx context.Context, req *model.IdRequiredReq, rsp *comm_def.CommonMsg) error {
 	g.Log().Infof("CtrContractCollectionPlan.Get request %#v ", *req)
 	s, err := service.NewCtrContractCollectionPlanService(ctx)
@@ -29,6 +30,7 @@ func (c *CtrContractCollectionPlan) Get(ctx context.Context, req *model.IdRequir
 	return nil
 }
 
+// swagger:route 合同回款计划 查询合同回款计划
 func (c *CtrContractCollectionPlan) List(ctx context.Context, req *model.CtrContractCollectionPlanListReq, rsp *comm_def.CommonMsg) error {
 	g.Log().Infof("CtrContractCollectionPlan.List request %#v ", *req)
 	s, err := service.NewCtrContractCollectionPlanService(ctx)
@@ -52,6 +54,7 @@ func (c *CtrContractCollectionPlan) List(ctx context.Context, req *model.CtrCont
 	return nil
 }
 
+// swagger:route 合同回款计划 添加合同回款计划
 func (c *CtrContractCollectionPlan) Add(ctx context.Context, req *model.CtrContractCollectionPlanAddReq, rsp *comm_def.CommonMsg) error {
 	g.Log().Infof("CtrContractCollectionPlan.Add request %#v ", *req)
 	s, err := service.NewCtrContractCollectionPlanService(ctx)
@@ -69,6 +72,7 @@ func (c *CtrContractCollectionPlan) Add(ctx context.Context, req *model.CtrContr
 	return nil
 }
 
+// swagger:route 合同回款计划 更新合同回款计划
 func (c *CtrContractCollectionPlan) Update(ctx context.Context, req *model.CtrContractCollectionPlanUpdateReq, rsp *comm_def.CommonMsg) error {
 	g.Log().Infof("CtrContractCollectionPlan.Update request %#v ", *req)
 	s, err := service.NewCtrContractCollectionPlanService(ctx)
@@ -85,6 +89,7 @@ func (c *CtrContractCollectionPlan) Update(ctx context.Context, req *model.CtrCo
 	return nil
 }
 
+// swagger:route 合同回款计划 删除合同回款计划
 func (c *CtrContractCollectionPlan) Delete(ctx context.Context, req *model.IdsReq, rsp *comm_def.CommonMsg) error {
 	g.Log().Infof("CtrContractCollectionPlan.Delete request %#v ", *req)
 	s, err := service.NewCtrContractCollectionPlanService(ctx)

+ 4 - 0
opms_parent/app/handler/contract/ctr_contract_invoice.go

@@ -12,6 +12,7 @@ import (
 
 type CtrContractInvoice struct{}
 
+// swagger:route 合同发票 查询合同发票
 func (c *CtrContractInvoice) List(ctx context.Context, req *model.CtrContractInvoiceListReq, rsp *comm_def.CommonMsg) error {
 	g.Log().Infof("CtrContractInvoice.List request %#v ", *req)
 	s, err := service.NewCtrContractInvoiceService(ctx)
@@ -35,6 +36,7 @@ func (c *CtrContractInvoice) List(ctx context.Context, req *model.CtrContractInv
 	return nil
 }
 
+// swagger:route 合同发票 添加合同发票
 func (c *CtrContractInvoice) Add(ctx context.Context, req *model.CtrContractInvoiceAddReq, rsp *comm_def.CommonMsg) error {
 	g.Log().Infof("CtrContractInvoice.Add request %#v ", *req)
 	s, err := service.NewCtrContractInvoiceService(ctx)
@@ -52,6 +54,7 @@ func (c *CtrContractInvoice) Add(ctx context.Context, req *model.CtrContractInvo
 	return nil
 }
 
+// swagger:route 合同发票 更新合同发票
 func (c *CtrContractInvoice) Update(ctx context.Context, req *model.CtrContractInvoiceUpdateReq, rsp *comm_def.CommonMsg) error {
 	g.Log().Infof("CtrContractInvoice.Update request %#v ", *req)
 	s, err := service.NewCtrContractInvoiceService(ctx)
@@ -68,6 +71,7 @@ func (c *CtrContractInvoice) Update(ctx context.Context, req *model.CtrContractI
 	return nil
 }
 
+// swagger:route 合同发票 删除合同发票
 func (c *CtrContractInvoice) Delete(ctx context.Context, req *model.IdsReq, rsp *comm_def.CommonMsg) error {
 	g.Log().Infof("CtrContractInvoice.Delete request %#v ", *req)
 	s, err := service.NewCtrContractInvoiceService(ctx)

+ 1 - 2
opms_parent/app/model/contract/ctr_contract.go

@@ -19,7 +19,6 @@ type CtrContractGetRsp struct {
 	Product []*CtrContractProduct `json:"product"` // 产品
 }
 
-// swagger:model CtrContractListReq
 type CtrContractListReq struct {
 	request.PageReq
 	SearchText   string `json:"searchText"`   // 合同编号,合同名称,客户名称,项目名称
@@ -109,7 +108,7 @@ type CtrContractUpdateReq struct {
 }
 
 type CtrContractTransferReq struct {
-	Id           int    `json:"id" v:"required#请输入Id"`
+	Id           []int  `json:"id" v:"required#请输入Id"`
 	InchargeId   int    `json:"inchargeId"`   // 负责人ID
 	InchargeName string `json:"inchargeName"` // 负责人
 }

+ 0 - 43
opms_parent/app/model/contract/ctr_contract_product.go

@@ -6,52 +6,9 @@ package contract
 
 import (
 	"dashoo.cn/micro/app/model/contract/internal"
-	"dashoo.cn/opms_libary/request"
 )
 
 // CtrContractProduct is the golang structure for table ctr_contract_product.
 type CtrContractProduct internal.CtrContractProduct
 
 // Fill with you ideas below.
-type CtrContractProductListReq struct {
-	request.PageReq
-	ContractId    int     `json:"contractId"`    // 关联合同
-	ProdId        int     `json:"prodId"`        // 关联产品
-	ProdCode      string  `json:"prodCode"`      // 产品型号
-	ProdName      string  `json:"prodName"`      // 产品名称
-	ProdClass     string  `json:"prodClass"`     // 产品类别
-	ProdNum       int     `json:"prodNum"`       // 产品数量
-	MaintTerm     int     `json:"maintTerm"`     // 维保期
-	SugSalesPrice float64 `json:"sugSalesPrice"` // 建议成交价
-	TranPrice     float64 `json:"tranPrice"`     // 成交价格
-	ContractPrive float64 `json:"contractPrive"` // 合同总价
-}
-
-type CtrContractProductAddReq struct {
-	ContractId    int     `json:"contractId"`    // 关联合同
-	ProdId        int     `json:"prodId"`        // 关联产品
-	ProdCode      string  `json:"prodCode"`      // 产品型号
-	ProdName      string  `json:"prodName"`      // 产品名称
-	ProdClass     string  `json:"prodClass"`     // 产品类别
-	ProdNum       int     `json:"prodNum"`       // 产品数量
-	MaintTerm     int     `json:"maintTerm"`     // 维保期
-	SugSalesPrice float64 `json:"sugSalesPrice"` // 建议成交价
-	TranPrice     float64 `json:"tranPrice"`     // 成交价格
-	ContractPrive float64 `json:"contractPrive"` // 合同总价
-	Remark        string  `json:"remark"`        // 备注
-}
-
-type CtrContractProductUpdateReq struct {
-	Id            int     `json:"id" v:"required#请输入Id"`
-	ContractId    int     `json:"contractId"`    // 关联合同
-	ProdId        int     `json:"prodId"`        // 关联产品
-	ProdCode      string  `json:"prodCode"`      // 产品型号
-	ProdName      string  `json:"prodName"`      // 产品名称
-	ProdClass     string  `json:"prodClass"`     // 产品类别
-	ProdNum       int     `json:"prodNum"`       // 产品数量
-	MaintTerm     int     `json:"maintTerm"`     // 维保期
-	SugSalesPrice float64 `json:"sugSalesPrice"` // 建议成交价
-	TranPrice     float64 `json:"tranPrice"`     // 成交价格
-	ContractPrive float64 `json:"contractPrive"` // 合同总价
-	Remark        *string `json:"remark"`
-}

+ 29 - 18
opms_parent/app/service/contract/ctr_contract.go

@@ -232,7 +232,7 @@ func (s CtrContractService) BindProduct(tx *gdb.TX, id int, product []model.CtrA
 		amount += p.TranPrice
 	}
 
-	_, err := s.CtrProductDao.Delete("contract_id = ?", id)
+	_, err := tx.Delete("ctr_contract_product", "contract_id = ?", id)
 	if err != nil {
 		return err
 	}
@@ -307,7 +307,7 @@ func (s CtrContractService) Add(ctx context.Context, req *model.CtrContractAddRe
 	}
 
 	if req.ContractCode == "" {
-		req.ContractCode = time.Now().Format("20060102150405")
+		req.ContractCode = "HT" + time.Now().Format("20060102150405")
 	}
 
 	c, err := s.Dao.Where("contract_code = ?", req.ContractCode).One()
@@ -526,12 +526,20 @@ func (s CtrContractService) Update(ctx context.Context, req *model.CtrContractUp
 }
 
 func (s CtrContractService) Transfer(ctx context.Context, req *model.CtrContractTransferReq) error {
-	ent, err := s.Dao.Where("id = ?", req.Id).One()
-	if err != nil {
-		return err
+	if len(req.Id) == 0 {
+		return nil
 	}
-	if ent == nil {
-		return myerrors.NewMsgError(nil, fmt.Sprintf("合同不存在: %d", req.Id))
+
+	ents := map[int]*model.CtrContract{}
+	for _, i := range req.Id {
+		ent, err := s.Dao.Where("id = ?", i).One()
+		if err != nil {
+			return err
+		}
+		if ent == nil {
+			return myerrors.NewMsgError(nil, fmt.Sprintf("合同不存在: %d", req.Id))
+		}
+		ents[ent.Id] = ent
 	}
 
 	txerr := s.Dao.DB.Transaction(ctx, func(ctx context.Context, tx *gdb.TX) error {
@@ -539,20 +547,23 @@ func (s CtrContractService) Transfer(ctx context.Context, req *model.CtrContract
 			"incharge_id":   req.InchargeId,
 			"incharge_name": req.InchargeName,
 		}
-		_, err := tx.Update("ctr_contract", toupdate, "id = ?", req.Id)
+		_, err := tx.Update("ctr_contract", toupdate, "id in (?)", req.Id)
 		if err != nil {
 			return err
 		}
-		err = s.AddDynamicsByCurrentUser(tx, req.Id, "转移合同", map[string]interface{}{
-			"toInchargeId":     req.InchargeId,
-			"toInchargeName":   req.InchargeName,
-			"fromInchargeId":   ent.InchargeId,
-			"fromInchargeName": ent.InchargeName,
-			"operatedId":       s.userInfo.Id,
-			"operatedName":     s.userInfo.NickName,
-		})
-		if err != nil {
-			return err
+
+		for _, ent := range ents {
+			err = s.AddDynamicsByCurrentUser(tx, ent.Id, "转移合同", map[string]interface{}{
+				"toInchargeId":     req.InchargeId,
+				"toInchargeName":   req.InchargeName,
+				"fromInchargeId":   ent.InchargeId,
+				"fromInchargeName": ent.InchargeName,
+				"operatedId":       s.userInfo.Id,
+				"operatedName":     s.userInfo.NickName,
+			})
+			if err != nil {
+				return err
+			}
 		}
 		return nil
 	})

+ 4 - 0
opms_parent/main.go

@@ -84,6 +84,10 @@ func swaggerui() {
 			glog.Error(r)
 		}
 	}()
+	if !g.Config().GetBool("setting.swagger") {
+		return
+	}
+	glog.Info("start swagger at :8080")
 	glog.Error(http.ListenAndServe(":8080",
 		http.FileServer(http.Dir("./swaggerui/"))))
 }

+ 19 - 0
opms_parent/swaggerui/readme.md

@@ -0,0 +1,19 @@
+
+
+- 在 handler 中要生成文档的函数上添加注释
+
+    `// swagger:route tag1,tag2 接口名称`
+
+    例如
+
+    `// swagger:route 合同 转移合同`
+
+- 然后运行下面的命令生成 swagger.yml
+
+    `go run swaggerui/swagger.go -o swaggerui/swagger.yml`
+
+- 在配置文件的 setting 中添加
+
+    `swagger = true`
+
+    启动服务即可在 8080 端口访问到 swagger 文档

+ 0 - 11
opms_parent/swaggerui/server.go

@@ -1,11 +0,0 @@
-package main
-
-import (
-	"log"
-	"net/http"
-)
-
-func main() {
-	log.Fatal(http.ListenAndServe(":8080",
-		http.FileServer(http.Dir("./swaggerui/"))))
-}

+ 4 - 2
opms_parent/swaggerui/swagger.yml → opms_parent/swaggerui/swagger.bak.yml

@@ -1017,8 +1017,10 @@ components:
         - id
       properties:
         id:
-          type: integer
+          type: array
           description: Id
+          items:
+            type: integer
         inchargeId:
           type: integer
           description: 负责人ID
@@ -1585,7 +1587,7 @@ components:
         product: []
     CtrContractTransfer:
       value:
-        id: 2
+        id: [2]
         inchargeId: 2
         inchargeName: "管理员2"
     CtrContractCollectionPlanList:

+ 797 - 0
opms_parent/swaggerui/swagger.go

@@ -0,0 +1,797 @@
+package main
+
+import (
+	"flag"
+	"fmt"
+	"go/ast"
+	"go/parser"
+	"go/token"
+	"go/types"
+	"io"
+	"log"
+	"os"
+	"reflect"
+	"strconv"
+	"strings"
+
+	"golang.org/x/tools/go/ast/astutil"
+	"golang.org/x/tools/go/packages"
+	"gopkg.in/yaml.v3"
+)
+
+var swagger = map[string]interface{}{
+	"openapi": "3.0.0",
+	"info": map[string]string{
+		"title":       "CRM",
+		"description": "CRM",
+		"version":     "0.0.1",
+	},
+	"paths": map[string]interface{}{},
+	"security": []map[string]interface{}{
+		{
+			"bearerAuth": []interface{}{},
+		},
+	},
+	"components": map[string]interface{}{
+		"securitySchemes": map[string]interface{}{
+			"basicAuth": map[string]interface{}{
+				"type":   "http",
+				"scheme": "basic",
+			},
+			"bearerAuth": map[string]interface{}{
+				"type":   "http",
+				"scheme": "bearer",
+			},
+		},
+		"schemas": map[string]interface{}{},
+		"examples": map[string]interface{}{
+			"success": map[string]interface{}{
+				"summary": "请求成功",
+				"value": map[string]interface{}{
+					"code": 200,
+					"msg":  "success",
+				},
+			},
+		},
+	},
+}
+
+type pathModel struct {
+	OperationId string
+	Summary     string
+	Tags        []string
+	Request     string
+}
+
+func newPath(m pathModel) interface{} {
+	schemaref := fmt.Sprintf("#/components/schemas/%s", m.Request)
+	examplesref := fmt.Sprintf("#/components/examples/%s", m.Request)
+	return map[string]interface{}{
+		"post": map[string]interface{}{
+			"tags":        m.Tags,
+			"operationId": m.OperationId,
+			"summary":     m.Summary,
+			"requestBody": map[string]interface{}{
+				"required": true,
+				"content": map[string]interface{}{
+					"application/json": map[string]interface{}{
+						"schema": map[string]interface{}{
+							"oneOf": []map[string]interface{}{
+								{
+									"$ref": schemaref,
+								},
+							},
+						},
+						"examples": map[string]interface{}{
+							m.Request: map[string]interface{}{
+								"$ref": examplesref,
+							},
+						},
+					},
+				},
+				"responses": map[string]interface{}{
+					"200": map[string]interface{}{
+						"description": "请求成功",
+						"content": map[string]interface{}{
+							"application/json": map[string]interface{}{
+								"examples": map[string]interface{}{
+									"success": map[string]interface{}{
+										"$ref": "#/components/examples/success",
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+}
+
+func testGoParser() error {
+	sourceFile, err := os.Open("/home/lai/code/working/opms_backend/opms_parent/app/handler/contract/ctr_contract.go")
+	if err != nil {
+		return err
+	}
+	sourceContent, err := io.ReadAll(sourceFile)
+	if err != nil {
+		return err
+	}
+
+	// Create the AST by parsing src.
+	fset := token.NewFileSet() // positions are relative to fset
+	f, err := parser.ParseFile(fset, "", sourceContent, parser.ParseComments)
+	if err != nil {
+		panic(err)
+	}
+
+	// Print the imports from the file's AST.
+	for _, s := range f.Imports {
+		debugLog(s.Path.Value)
+		fmt.Printf("%#v\n", s.Path)
+	}
+	debugLog("-----------------------")
+	if Debug {
+		ast.Print(fset, f)
+	}
+	debugLog("-----------------------")
+	for _, c := range f.Comments {
+		for _, l := range c.List {
+			fmt.Printf("%#v%#v\n", l.Text, l.Slash)
+		}
+	}
+	return nil
+}
+
+type SwaggerModel struct {
+	Type        string                   `yaml:"type"`
+	Description string                   `yaml:"description"`
+	Properties  map[string]*SwaggerModel `yaml:"properties,omitempty"`
+	Items       *SwaggerModel            `yaml:"items,omitempty"`
+	Ref         string                   `yaml:"ref,omitempty"`
+}
+
+func RangeSwaggerModel(node *SwaggerModel) []*SwaggerModel {
+	if node == nil {
+		return nil
+	}
+	allnode := []*SwaggerModel{}
+	nodes := []*SwaggerModel{node}
+	for len(nodes) != 0 {
+		newnode := []*SwaggerModel{}
+		for _, n := range nodes {
+			allnode = append(allnode, n)
+			if n.Items != nil {
+				newnode = append(newnode, n.Items)
+			}
+			for _, p := range n.Properties {
+				newnode = append(newnode, p)
+			}
+		}
+		nodes = newnode
+	}
+	return allnode
+}
+
+const pkgLoadMode = packages.NeedName | packages.NeedFiles | packages.NeedImports | packages.NeedDeps | packages.NeedTypes | packages.NeedSyntax | packages.NeedTypesInfo
+
+var AllPackages = map[string]*packages.Package{}
+var Models = map[*ast.Ident]*entityDecl{}
+var AllStructModel = map[string]*SwaggerModel{}
+var AllSwaggerModel = map[string]*SwaggerModel{}
+var SpecifiedSwaggerModel = []map[string]string{}
+var Routes = map[string]pathModel{}
+
+type entityDecl struct {
+	Type *types.Named
+	File *ast.File
+	Pkg  *packages.Package
+}
+
+var outfile string
+
+func init() {
+	flag.StringVar(&outfile, "o", "swagger.yml", "output file")
+}
+
+func main() {
+	// testGoParser()
+	// return
+	flag.Parse()
+	toload := []string{"dashoo.cn/micro/app/model/contract", "dashoo.cn/micro/app/handler/contract"}
+	cfg := &packages.Config{Mode: pkgLoadMode}
+	pkgs, err := packages.Load(cfg, toload...)
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "load: %v\n", err)
+		os.Exit(1)
+	}
+	if packages.PrintErrors(pkgs) > 0 {
+		os.Exit(1)
+	}
+
+	for _, pkg := range pkgs {
+		AllPackages[pkg.PkgPath] = pkg
+		for _, file := range pkg.Syntax {
+			for _, dt := range file.Decls {
+				switch fd := dt.(type) {
+				case *ast.GenDecl:
+					for _, sp := range fd.Specs {
+						switch ts := sp.(type) {
+						case *ast.TypeSpec:
+							def, ok := pkg.TypesInfo.Defs[ts.Name]
+							if !ok {
+								debugLog("couldn't find type info for %s", ts.Name)
+								continue
+							}
+							nt, isNamed := def.Type().(*types.Named)
+							if !isNamed {
+								debugLog("%s is not a named type but a %T", ts.Name, def.Type())
+								continue
+							}
+							key := ts.Name
+							Models[key] = &entityDecl{
+								Type: nt,
+								File: file,
+								Pkg:  pkg,
+							}
+						}
+					}
+				}
+			}
+
+		}
+	}
+	// Print the names of the source files
+	// for each package listed on the command line.
+	for _, pkg := range pkgs {
+		debugLog("-----------%s", pkg.Name)
+		for _, f := range pkg.Syntax {
+			detectModels(pkg, f)
+		}
+	}
+
+	for n := range AllStructModel {
+		m := AllStructModel[n]
+		if m == nil {
+			continue
+		}
+		// fmt.Println(m.Ref, AllStructModel[m.Ref])
+		for _, subm := range RangeSwaggerModel(m) {
+			if subm.Ref != "" && AllStructModel[subm.Ref] != nil {
+				subm.Items = AllStructModel[subm.Ref].Items
+				subm.Properties = AllStructModel[subm.Ref].Properties
+			}
+		}
+
+		// b, err := yaml.Marshal(m)
+		// if err != nil {
+		// 	panic(err)
+		// }
+		// fmt.Println(n)
+		// fmt.Println(string(b))
+	}
+	genconfig(outfile)
+}
+
+func genconfig(filename string) {
+	f, err := os.Create(filename)
+	if err != nil {
+		panic(err)
+	}
+	defer f.Close()
+
+	for n := range AllStructModel {
+		m := AllStructModel[n]
+		if m == nil {
+			continue
+		}
+		namelist := strings.Split(n, ".")
+		name := namelist[len(namelist)-1]
+
+		swagger["components"].(map[string]interface{})["schemas"].(map[string]interface{})[name] = m
+		swagger["components"].(map[string]interface{})["examples"].(map[string]interface{})[name] = map[string]interface{}{
+			"value": map[string]interface{}{
+				"placeholder": "",
+			},
+		}
+	}
+
+	for p, m := range Routes {
+		swagger["paths"].(map[string]interface{})[p] = newPath(m)
+	}
+
+	encoder := yaml.NewEncoder(f)
+	encoder.SetIndent(2)
+	err = encoder.Encode(swagger)
+	if err != nil {
+		panic(err)
+	}
+}
+
+func getFuncRecv(fd *ast.FuncDecl) string {
+	defer func() {
+		if r := recover(); r != nil {
+			debugLog("%s", r)
+		}
+	}()
+
+	return fd.Recv.List[0].Type.(*ast.StarExpr).X.(*ast.Ident).Name
+}
+
+func getFuncParams(fd *ast.FuncDecl) string {
+	defer func() {
+		if r := recover(); r != nil {
+			debugLog("%s", r)
+		}
+	}()
+
+	return fd.Type.Params.List[1].Type.(*ast.StarExpr).X.(*ast.SelectorExpr).Sel.Name
+}
+
+func detectPath(fd *ast.FuncDecl) {
+	recv := getFuncRecv(fd)
+	fname := fd.Name.String()
+	req := getFuncParams(fd)
+	debugLog("//////////%v %s %s %s %s", fd, fname, recv, req, fd.Doc.Text())
+	commentText := fd.Doc.Text()
+	for _, c := range strings.Split(commentText, "\n") {
+		if !strings.HasPrefix(c, "swagger:route") {
+			continue
+		}
+		names := strings.Split(c, " ")
+		tags := []string{}
+		summary := ""
+		if len(names) == 3 {
+			tags = strings.Split(names[1], ",")
+			summary = names[2]
+		}
+		path := fmt.Sprintf("/%s.%s", recv, fname)
+		operationId := recv + fname
+		Routes[path] = pathModel{
+			OperationId: operationId,
+			Summary:     summary,
+			Tags:        tags,
+			Request:     req,
+		}
+		break
+	}
+}
+
+func detectModels(pkg *packages.Package, file *ast.File) {
+	for _, dt := range file.Decls {
+		switch fd := dt.(type) {
+		case *ast.BadDecl:
+			continue
+		case *ast.FuncDecl:
+			detectPath(fd)
+		case *ast.GenDecl:
+			processDecl(pkg, file, fd)
+		}
+	}
+}
+
+func processDecl(pkg *packages.Package, file *ast.File, gd *ast.GenDecl) error {
+	for _, sp := range gd.Specs {
+		switch ts := sp.(type) {
+		case *ast.ValueSpec:
+			return nil
+		case *ast.ImportSpec:
+			return nil
+		case *ast.TypeSpec:
+			def, ok := pkg.TypesInfo.Defs[ts.Name]
+			if !ok {
+				debugLog("couldn't find type info for %s", ts.Name)
+				continue
+			}
+			nt, isNamed := def.Type().(*types.Named)
+			if !isNamed {
+				debugLog("%s is not a named type but a %T", ts.Name, def.Type())
+				continue
+			}
+
+			comments := ts.Doc // type ( /* doc */ Foo struct{} )
+			if comments == nil {
+				comments = gd.Doc // /* doc */  type ( Foo struct{} )
+			}
+
+			// decl := &entityDecl{
+			// 	Comments: comments,
+			// 	Type:     nt,
+			// 	Ident:    ts.Name,
+			// 	Spec:     ts,
+			// 	File:     file,
+			// 	Pkg:      pkg,
+			// }
+			// key := ts.Name
+			// if n&modelNode != 0 && decl.HasModelAnnotation() {
+			// 	a.Models[key] = decl
+			// }
+			// if n&parametersNode != 0 && decl.HasParameterAnnotation() {
+			// 	a.Parameters = append(a.Parameters, decl)
+			// }
+			// if n&responseNode != 0 && decl.HasResponseAnnotation() {
+			// 	a.Responses = append(a.Responses, decl)
+			// }
+
+			switch tpe := nt.Obj().Type().(type) {
+			case *types.Struct:
+				st := tpe
+				for i := 0; i < st.NumFields(); i++ {
+					fld := st.Field(i)
+					if !fld.Anonymous() {
+						debugLog("skipping field %q for allOf scan because not anonymous\n", fld.Name())
+						continue
+					}
+					tg := st.Tag(i)
+					debugLog(fld.Name(), tg)
+				}
+			case *types.Slice:
+				debugLog("Slice")
+			case *types.Basic:
+				debugLog("Basic")
+			case *types.Interface:
+				debugLog("Interface")
+			case *types.Array:
+				debugLog("Array")
+			case *types.Map:
+				debugLog("Map")
+			case *types.Named:
+				debugLog("Named")
+				o := tpe.Obj()
+				if o != nil {
+					debugLog("got the named type object: %s.%s | isAlias: %t | exported: %t //////////// %s", o.Pkg().Path(), o.Name(), o.IsAlias(), o.Exported(), comments.Text())
+					if o.Pkg().Name() == "time" && o.Name() == "Time" {
+						// schema.Typed("string", "date-time")
+						return nil
+					}
+
+					for {
+						ti := pkg.TypesInfo.Types[ts.Type]
+						if ti.IsBuiltin() {
+							break
+						}
+						if ti.IsType() {
+							err, ret := buildFromType(ti.Type, file, comments.Text())
+							if err != nil {
+								return err
+							}
+							AllStructModel[fmt.Sprintf("%s.%s", o.Pkg().Path(), o.Name())] = ret
+							break
+						}
+					}
+				}
+			}
+
+		}
+	}
+	return nil
+}
+
+func buildFromType(tpe types.Type, declFile *ast.File, desc string) (error, *SwaggerModel) {
+	switch titpe := tpe.(type) {
+	case *types.Basic:
+		return nil, &SwaggerModel{
+			Type:        swaggerSchemaForType(titpe.String()),
+			Description: desc,
+		}
+	case *types.Pointer:
+		return buildFromType(titpe.Elem(), declFile, desc)
+	case *types.Struct:
+		return buildFromStruct(declFile, titpe, desc)
+	case *types.Slice:
+		err, ret := buildFromType(titpe.Elem(), declFile, desc)
+		if err != nil {
+			return err, nil
+		}
+		return nil, &SwaggerModel{
+			Type:        "array",
+			Items:       ret,
+			Description: desc,
+		}
+	case *types.Named:
+		tio := titpe.Obj()
+		debugLog("%s", tio)
+		if tio.Pkg() == nil && tio.Name() == "error" {
+			return nil, &SwaggerModel{
+				Type:        swaggerSchemaForType(tio.Name()),
+				Description: desc,
+			}
+		}
+		if tpe.String() == "github.com/gogf/gf/os/gtime.Time" {
+			return nil, &SwaggerModel{
+				Type:        "string",
+				Description: desc + "DATETIME",
+			}
+		}
+
+		debugLog("named refined type %s.%s", tio.Pkg().Path(), tio.Name())
+		pkg, found := PkgForType(tpe)
+		if !found {
+			// this must be a builtin
+			debugLog("skipping because package is nil: %s", tpe.String())
+			return nil, nil
+		}
+		if pkg.Name == "time" && tio.Name() == "Time" {
+			return nil, &SwaggerModel{
+				Type:        "string",
+				Description: desc,
+			}
+		}
+
+		switch utitpe := tpe.Underlying().(type) {
+		case *types.Struct:
+			if decl, ok := FindModel(tio.Pkg().Path(), tio.Name()); ok {
+				if decl.Type.Obj().Pkg().Path() == "time" && decl.Type.Obj().Name() == "Time" {
+					return nil, &SwaggerModel{
+						Type:        "string",
+						Description: desc,
+					}
+				}
+
+				debugLog("!!!!!!!%s", decl.Type.String())
+				return nil, &SwaggerModel{
+					Type: "object",
+					Ref:  decl.Type.String(),
+				}
+			}
+		case *types.Slice:
+			debugLog("!!!!!!!slice %s", utitpe.Elem())
+
+			// decl, ok := FindModel(tio.Pkg().Path(), tio.Name())
+			// return buildFromType(utitpe.Elem(), tgt.Items())
+
+		}
+
+	default:
+		panic(fmt.Sprintf("WARNING: can't determine refined type %s (%T)", titpe.String(), titpe))
+	}
+
+	return nil, nil
+}
+
+func buildFromStruct(declFile *ast.File, st *types.Struct, desc string) (error, *SwaggerModel) {
+	// for i := 0; i < st.NumFields(); i++ {
+	// 	fld := st.Field(i)
+	// 	if !fld.Anonymous() {
+	// 		debugLog("skipping field %q for allOf scan because not anonymous", fld.Name())
+	// 		continue
+	// 	}
+	// 	tg := st.Tag(i)
+
+	// 	debugLog("maybe allof field(%t) %s: %s (%T) [%q](anon: %t, embedded: %t)", fld.IsField(), fld.Name(), fld.Type().String(), fld.Type(), tg, fld.Anonymous(), fld.Embedded())
+	// 	var afld *ast.Field
+	// 	ans, _ := astutil.PathEnclosingInterval(declFile, fld.Pos(), fld.Pos())
+	// 	// debugLog("got %d nodes (exact: %t)", len(ans), isExact)
+	// 	for _, an := range ans {
+	// 		at, valid := an.(*ast.Field)
+	// 		if !valid {
+	// 			continue
+	// 		}
+
+	// 		debugLog("maybe allof field %s: %s(%T) [%q]", fld.Name(), fld.Type().String(), fld.Type(), tg)
+	// 		afld = at
+	// 		break
+	// 	}
+
+	// 	if afld == nil {
+	// 		debugLog("can't find source associated with %s for %s", fld.String(), st.String())
+	// 		continue
+	// 	}
+
+	// 	_, ignore, _, err := parseJSONTag(afld)
+	// 	if err != nil {
+	// 		return err
+	// 	}
+	// 	if ignore {
+	// 		continue
+	// 	}
+	// }
+
+	properties := map[string]*SwaggerModel{}
+	for i := 0; i < st.NumFields(); i++ {
+		fld := st.Field(i)
+		tg := st.Tag(i)
+
+		if fld.Embedded() {
+			if fld.Name() == "PageReq" {
+				properties["beginTime"] = &SwaggerModel{Type: "string", Description: "开始时间"}
+				properties["endTime"] = &SwaggerModel{Type: "string", Description: "结束时间"}
+				properties["pageNum"] = &SwaggerModel{Type: "int", Description: "当前页码"}
+				properties["pageSize"] = &SwaggerModel{Type: "int", Description: "每页数"}
+				properties["orderBy"] = &SwaggerModel{Type: "string", Description: "排序方式"}
+			}
+			debugLog("Embedded %s, %s", fld.Name(), fld.Pkg())
+			continue
+		}
+
+		if !fld.Exported() {
+			debugLog("skipping field %s because it's not exported", fld.Name())
+			continue
+		}
+
+		var afld *ast.Field
+		ans, _ := astutil.PathEnclosingInterval(declFile, fld.Pos(), fld.Pos())
+		// debugLog("got %d nodes (exact: %t)", len(ans), isExact)
+		for _, an := range ans {
+			at, valid := an.(*ast.Field)
+			if !valid {
+				continue
+			}
+
+			debugLog("field %s: %s(%T) [%q] ==> %s", fld.Name(), fld.Type().String(), fld.Type(), tg, at.Doc.Text())
+			afld = at
+			break
+		}
+
+		if afld == nil {
+			debugLog("can't find source associated with %s", fld.String())
+			continue
+		}
+
+		name, _, isString, err := parseJSONTag(afld)
+		if err != nil {
+			return err, nil
+		}
+		commentText := ""
+		if afld.Comment != nil {
+			commentText = afld.Comment.Text()
+		}
+		err, ret := buildFromType(fld.Type(), declFile, commentText)
+		if err != nil {
+			return err, nil
+		}
+		debugLog("****** %s %v %s", fld.Type(), ret, commentText)
+		if isString {
+			return nil, &SwaggerModel{
+				Type:        "string",
+				Description: desc,
+			}
+		}
+		properties[name] = ret
+	}
+	return nil, &SwaggerModel{
+		Type:        "object",
+		Properties:  properties,
+		Description: desc,
+	}
+}
+
+func swaggerSchemaForType(typeName string) string {
+	switch typeName {
+	case "bool":
+		return "boolean"
+	case "byte":
+		return "integer"
+	case "float32":
+		return "number"
+	case "float64":
+		return "number"
+	case "int":
+		return "integer"
+	case "int16":
+		return "integer"
+	case "int32":
+		return "integer"
+	case "int64":
+		return "integer"
+	case "int8":
+		return "integer"
+	case "rune":
+		return "integer"
+	case "string":
+		return "string"
+	case "uint":
+		return "integer"
+	case "uint16":
+		return "integer"
+	case "uint32":
+		return "integer"
+	case "uint64":
+		return "integer"
+	case "uint8":
+		return "integer"
+	}
+	return ""
+}
+
+func FindModel(pkgPath, name string) (*entityDecl, bool) {
+	for _, cand := range Models {
+		ct := cand.Type.Obj()
+		if ct.Name() == name && ct.Pkg().Path() == pkgPath {
+			return cand, true
+		}
+	}
+	return nil, false
+}
+
+func PkgForType(t types.Type) (*packages.Package, bool) {
+	switch tpe := t.(type) {
+	// case *types.Basic:
+	// case *types.Struct:
+	// case *types.Pointer:
+	// case *types.Interface:
+	// case *types.Array:
+	// case *types.Slice:
+	// case *types.Map:
+	case *types.Named:
+		v, ok := AllPackages[tpe.Obj().Pkg().Path()]
+		return v, ok
+	default:
+		log.Printf("unknown type to find the package for [%T]: %s", t, t.String())
+		return nil, false
+	}
+}
+
+func parseJSONTag(field *ast.Field) (name string, ignore bool, isString bool, err error) {
+	if len(field.Names) > 0 {
+		name = field.Names[0].Name
+	}
+	if field.Tag == nil || len(strings.TrimSpace(field.Tag.Value)) == 0 {
+		return name, false, false, nil
+	}
+
+	tv, err := strconv.Unquote(field.Tag.Value)
+	if err != nil {
+		return name, false, false, err
+	}
+
+	if strings.TrimSpace(tv) != "" {
+		st := reflect.StructTag(tv)
+		jsonParts := tagOptions(strings.Split(st.Get("json"), ","))
+
+		if jsonParts.Contain("string") {
+			// Need to check if the field type is a scalar. Otherwise, the
+			// ",string" directive doesn't apply.
+			isString = isFieldStringable(field.Type)
+		}
+
+		switch jsonParts.Name() {
+		case "-":
+			return name, true, isString, nil
+		case "":
+			return name, false, isString, nil
+		default:
+			return jsonParts.Name(), false, isString, nil
+		}
+	}
+	return name, false, false, nil
+}
+
+func isFieldStringable(tpe ast.Expr) bool {
+	if ident, ok := tpe.(*ast.Ident); ok {
+		switch ident.Name {
+		case "int", "int8", "int16", "int32", "int64",
+			"uint", "uint8", "uint16", "uint32", "uint64",
+			"float64", "string", "bool":
+			return true
+		}
+	} else if starExpr, ok := tpe.(*ast.StarExpr); ok {
+		return isFieldStringable(starExpr.X)
+	} else {
+		return false
+	}
+	return false
+}
+
+type tagOptions []string
+
+func (t tagOptions) Contain(option string) bool {
+	for i := 1; i < len(t); i++ {
+		if t[i] == option {
+			return true
+		}
+	}
+	return false
+}
+
+func (t tagOptions) Name() string {
+	return t[0]
+}
+
+var Debug = false
+
+func debugLog(format string, args ...interface{}) {
+	if Debug {
+		log.Printf(format, args...)
+	}
+}