framework1/framework/command/provider.go

511 lines
13 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package command
import (
"fmt"
"github.com/AlecAivazis/survey/v2"
"github.com/Superdanda/hade/framework"
"github.com/Superdanda/hade/framework/cobra"
"github.com/Superdanda/hade/framework/contract"
"github.com/Superdanda/hade/framework/util"
"os"
"path/filepath"
"reflect"
"strings"
"text/template"
"github.com/jianfengye/collection"
"github.com/pkg/errors"
)
func initProviderCommand() *cobra.Command {
providerCommand.AddCommand(providerCreateCommand)
providerCommand.AddCommand(providerListCommand)
return providerCommand
}
var providerCommand = &cobra.Command{
Use: "provider",
Short: "服务相关命令",
RunE: func(c *cobra.Command, args []string) error {
if len(args) == 0 {
c.Help()
}
return nil
},
}
var providerListCommand = &cobra.Command{
Use: "list",
Short: "列出容器内的所有服务,列出它们的字符串凭证",
RunE: func(c *cobra.Command, args []string) error {
container := c.GetContainer()
nameList := container.NameList()
// 打印
for _, line := range nameList {
println(line)
}
return nil
},
}
var providerCreateCommand = &cobra.Command{
Use: "create",
Aliases: []string{"create", "init"},
Short: "创建服务",
RunE: func(c *cobra.Command, args []string) error {
container := c.GetContainer()
fmt.Println("创建一个服务")
var name, folder string
interfaceNames := &RouteNode{}
{
prompt := &survey.Input{
Message: "请输入服务名称(服务凭证)",
}
err := survey.AskOne(prompt, &name)
if err != nil {
return err
}
}
{
prompt := &survey.Input{
Message: "请输入服务所在目录名称(默认: 同服务名称):",
}
err := survey.AskOne(prompt, &folder)
if err != nil {
return err
}
}
// 检查服务是否存在
providers := container.(*framework.HadeContainer).NameList()
providerColl := collection.NewStrCollection(providers)
if providerColl.Contains(name) {
fmt.Println("服务名称已经存在")
return nil
}
if folder == "" {
folder = name
}
app := container.MustMake(contract.AppKey).(contract.App)
pFolder := app.ProviderFolder()
subFolders, err := util.SubDir(pFolder)
if err != nil {
return err
}
subColl := collection.NewStrCollection(subFolders)
if subColl.Contains(folder) {
fmt.Println("目录名称已经存在")
return nil
}
// 收集用户输入并填充嵌套映射
for {
prompt := &survey.Input{
Message: "请输入接口路径(格式:/user/login直接按回车结束输入",
}
var input string
err := survey.AskOne(prompt, &input)
if err != nil {
return err
}
// 如果用户直接按回车,结束输入
if strings.TrimSpace(input) == "" {
fmt.Println("接口输入结束")
break
}
// 解析输入的路径为路径部分
pathParts := strings.Split(strings.TrimPrefix(input, "/"), "/")
if len(pathParts) == 0 {
fmt.Println("路径格式错误,请输入正确格式:/节点/接口")
continue
}
// 将路径部分插入到路由树中
insertIntoRouteTree(pathParts, []string{}, interfaceNames)
fmt.Printf("已添加接口:%s\n", input)
}
interfaceNames.NeedExtra = false
// 打印所有添加的接口(测试用)
printRouteTree(interfaceNames, 0)
// 开始创建文件
if err := os.Mkdir(filepath.Join(pFolder, folder), 0700); err != nil {
return err
}
// 模板数据
config := container.MustMake(contract.ConfigKey).(contract.Config)
data := map[string]interface{}{
"appName": config.GetAppName(),
"packageName": name,
"interfaces": interfaceNames,
"structName": name,
}
// 创建title这个模版方法
funcs := template.FuncMap{
"title": strings.Title,
"dict": func(values ...interface{}) (map[string]interface{}, error) {
if len(values)%2 != 0 {
return nil, fmt.Errorf("invalid dict call: missing key or value")
}
dict := make(map[string]interface{}, len(values)/2)
for i := 0; i < len(values); i += 2 {
key, ok := values[i].(string)
if !ok {
return nil, fmt.Errorf("dict keys must be strings")
}
dict[key] = values[i+1]
}
return dict, nil
},
"len": func(v interface{}) int {
return reflect.ValueOf(v).Len()
},
}
{
// 创建contract.go
file := filepath.Join(pFolder, folder, "contract.go")
f, err := os.Create(file)
if err != nil {
return errors.Cause(err)
}
// 使用contractTmp模版来初始化template并且让这个模版支持title方法即支持{{.packageName | title}}
t := template.Must(template.New("contract").Funcs(funcs).Parse(contractTmp))
// 将name传递进入到template中渲染并且输出到contract.go 中
if err := t.Execute(f, data); err != nil {
return errors.Cause(err)
}
}
{
// 创建provider.go
file := filepath.Join(pFolder, folder, "provider.go")
f, err := os.Create(file)
if err != nil {
return err
}
t := template.Must(template.New("provider").Funcs(funcs).Parse(providerTmp))
if err := t.Execute(f, data); err != nil {
return err
}
}
{
// 创建service.go
file := filepath.Join(pFolder, folder, "service.go")
f, err := os.Create(file)
if err != nil {
return err
}
t := template.Must(template.New("service").Funcs(funcs).Parse(serviceTmp))
if err := t.Execute(f, data); err != nil {
return err
}
}
if interfaceNames.Children == nil || len(interfaceNames.Children) == 0 {
return nil
}
moduleFolder := app.HttpModuleFolder()
pModuleFolder := filepath.Join(moduleFolder, name)
util.EnsureDir(pModuleFolder)
{
// module 目录下 创建 服务包
// 创建api 我呢见
{
// 创建 api.go
file := filepath.Join(pModuleFolder, "api.go")
f, err := os.Create(file)
if err != nil {
return err
}
data["interfaces"] = interfaceNames // 传递嵌套的接口名称映射
t := template.Must(template.New("api").Funcs(funcs).Parse(apiTmp))
if err := t.Execute(f, data); err != nil {
return err
}
}
// 创建api_controller文件
{
tmpl := template.Must(template.New("controller").Funcs(funcs).Parse(apiControllerTmp))
data["packageName"] = name
data["structName"] = name
// 递归生成控制器文件
if err := generateControllers(interfaceNames, []string{}, tmpl, data, pModuleFolder); err != nil {
fmt.Println("生成控制器失败:", err)
return err
}
}
//创建 dto 文件
{
// 创建dto.go
file := filepath.Join(pModuleFolder, "dto.go")
f, err := os.Create(file)
if err != nil {
return err
}
t := template.Must(template.New("dto").Funcs(funcs).Parse(dtoTmp))
if err := t.Execute(f, data); err != nil {
return err
}
}
//创建 mapper 文件
{
// 创建mapper.go
file := filepath.Join(pModuleFolder, "mapper.go")
f, err := os.Create(file)
if err != nil {
return err
}
t := template.Must(template.New("mapper").Funcs(funcs).Parse(mapperTmp))
if err := t.Execute(f, data); err != nil {
return err
}
}
}
fmt.Println("创建服务成功, 文件夹地址:", filepath.Join(pFolder, folder))
fmt.Println("请不要忘记挂载新创建的服务")
return nil
},
}
func generateControllers(node *RouteNode, pathParts []string, tmpl *template.Template, data map[string]interface{}, moduleFolder string) error {
// 更新路径部分
newPathParts := append(pathParts, node.Path)
// 如果当前节点需要生成处理函数
if node.NeedExtra {
data["interfaceName"] = strings.Join(newPathParts, "_")
data["methodName"] = node.HandlerName
// 生成文件名,例如 api_user_edit_auto.go
fileName := "api" + strings.Join(newPathParts, "_") + ".go"
filePath := filepath.Join(moduleFolder, fileName)
// 创建控制器文件
file, err := os.Create(filePath)
if err != nil {
return fmt.Errorf("创建文件失败: %s", filePath)
}
defer file.Close()
// 渲染模板
if err := tmpl.Execute(file, data); err != nil {
return fmt.Errorf("渲染模板失败: %s", filePath)
}
fmt.Printf("生成接口控制器文件: %s\n", filePath)
}
// 递归处理子节点
for _, child := range node.Children {
if err := generateControllers(child, newPathParts, tmpl, data, moduleFolder); err != nil {
return err
}
}
return nil
}
func insertIntoRouteTree(pathParts []string, fullPathParts []string, currentNode *RouteNode) {
if len(pathParts) == 0 {
currentNode.NeedExtra = true
return
}
// 获取当前路径部分
part := pathParts[0]
fullPathParts = append(fullPathParts, part)
// 查找是否已存在该路径部分的子节点
var child *RouteNode
for _, node := range currentNode.Children {
if node.Path == part {
child = node
break
}
}
// 如果子节点不存在,则创建新的子节点
if child == nil {
handlerName := ""
for _, v := range fullPathParts {
handlerName += strings.Title(v)
}
child = &RouteNode{
Path: part,
HandlerName: handlerName,
}
currentNode.Children = append(currentNode.Children, child)
}
// 递归处理剩余路径部分
insertIntoRouteTree(pathParts[1:], fullPathParts, child)
}
func printRouteTree(node *RouteNode, level int) {
indent := strings.Repeat(" ", level)
fmt.Printf("%s- %s\n", indent, node.Path)
for _, child := range node.Children {
printRouteTree(child, level+1)
}
}
type RouteNode struct {
Path string // 路由路径
Children []*RouteNode // 子路由节点列表
NeedExtra bool // 是否需要额外生成接口
HandlerName string
}
var contractTmp = `package {{.packageName}}
const {{.packageName | title}}Key = "{{.appName}}:{{.packageName}}"
type Service interface {
// 请在这里定义你的方法
Foo() string
}
type {{.packageName | title}} struct {}
`
var providerTmp = `package {{.packageName}}
import (
"github.com/Superdanda/hade/framework"
)
type {{.packageName | title}}Provider struct {
framework.ServiceProvider
c framework.Container
}
func (sp *{{.packageName | title}}Provider) Name() string {
return {{.packageName | title}}Key
}
func (sp *{{.packageName | title}}Provider) Register(c framework.Container) framework.NewInstance {
return New{{.packageName | title}}Service
}
func (sp *{{.packageName | title}}Provider) IsDefer() bool {
return false
}
func (sp *{{.packageName | title}}Provider) Params(c framework.Container) []interface{} {
return []interface{}{c}
}
func (sp *{{.packageName | title}}Provider) Boot(c framework.Container) error {
return nil
}
`
var serviceTmp = `package {{.packageName}}
import "github.com/Superdanda/hade/framework"
type {{.packageName | title}}Service struct {
container framework.Container
}
func New{{.packageName | title}}Service(params ...interface{}) (interface{}, error) {
container := params[0].(framework.Container)
return &{{.packageName | title}}Service{container: container}, nil
}
func (s *{{.packageName | title}}Service) Foo() string {
return ""
}
`
var apiTmp = `package {{.packageName}}
import (
"github.com/Superdanda/hade/framework/gin"
)
type {{.packageName | title}}Api struct{}
// 注册路由
func RegisterRoutes(r *gin.Engine) error {
api := {{.packageName | title}}Api{}
if !r.IsBind({{.packageName}}.{{.packageName | title}}Key) {
r.Bind(&{{.packageName}}.{{.packageName | title}}Provider{})
}
{{template "registerRoutes" dict "node" .interfaces "groupVar" "r" "apiVar" "api"}}
return nil
}
{{- define "registerRoutes"}}
{{- $node := .node -}}
{{- $groupVar := .groupVar -}}
{{- $apiVar := .apiVar -}}
{{- if ne $node.Path "root" -}}
{{- $hasChildren := gt (len $node.Children) 0 -}}
{{- $groupName := (printf "%sGroup" $node.Path) -}}
{{- if $hasChildren -}}
{{$groupName}} := {{$groupVar}}.Group("/{{$node.Path}}")
{
{{- if $node.NeedExtra -}}
{{$groupName}}.POST("/", {{$apiVar}}.{{ $node.HandlerName }})
{{- end}}
{{range $child := $node.Children}}
{{template "registerRoutes" dict "node" $child "groupVar" $groupName "apiVar" $apiVar}}
{{- end}}
}
{{- else}}
{{- if $node.NeedExtra}}
{{$groupVar}}.POST("/{{$node.Path}}", {{$apiVar}}.{{ $node.HandlerName }})
{{- end}}
{{end}}
{{- else}}
{{range $child := $node.Children}}
{{template "registerRoutes" dict "node" $child "groupVar" $groupVar "apiVar" $apiVar}}
{{- end}}
{{- end}}
{{- end}}
`
var apiControllerTmp = `package {{.packageName}}
import (
"github.com/Superdanda/hade/framework/gin"
)
// {{.methodName}} handler
func (api *{{.structName | title}}Api) {{.methodName}}(c *gin.Context) {
// TODO: Implement {{.methodName}}
}
`
var dtoTmp = `package {{.packageName}}
type {{.packageName | title}}DTO struct {}
`
var mapperTmp = `package {{.packageName}}
func Convert{{.packageName | title}}ToDTO({{.packageName}} *{{.packageName}}.{{.packageName | title}}) *{{.packageName | title}}DTO {
if {{.packageName}} == nil {
return nil
}
return &{{.packageName | title}}DTO{}
}
`