Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tsgen adjust and add phpgen. #4479

Open
wants to merge 29 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
d142ccf
tsgen adjust: field name '-' add " and change request and response 'i…
chaosannals Dec 5, 2024
3f26ff8
tsgen: request(header,params,path) optional or omit.
chaosannals Dec 5, 2024
b6ee69c
phpgen.
chaosannals Dec 10, 2024
3872732
fix: php gen request.
chaosannals Dec 11, 2024
67d09f2
php gen custom body.
chaosannals Dec 11, 2024
c429c67
fix: php gen exception.
chaosannals Dec 11, 2024
4cee12c
fix: curl method upper.
chaosannals Dec 11, 2024
c6a5687
ts gen request custom url prefix and body.
chaosannals Dec 11, 2024
ac394eb
php gen return $result when not Response Type.
chaosannals Dec 11, 2024
d8e2fde
Merge branch 'zeromicro:master' into master
chaosannals Dec 26, 2024
7090dc1
tsgen adjust: field name '-' add " and change request and response 'i…
chaosannals Dec 5, 2024
83102c5
tsgen: request(header,params,path) optional or omit.
chaosannals Dec 5, 2024
4e0ecaf
phpgen.
chaosannals Dec 10, 2024
a3c9544
fix: php gen request.
chaosannals Dec 11, 2024
759c713
php gen custom body.
chaosannals Dec 11, 2024
02b9bf2
fix: php gen exception.
chaosannals Dec 11, 2024
805e568
fix: curl method upper.
chaosannals Dec 11, 2024
57eb003
ts gen request custom url prefix and body.
chaosannals Dec 11, 2024
c904c69
php gen return $result when not Response Type.
chaosannals Dec 11, 2024
7fe982e
c# gen.
chaosannals Dec 28, 2024
821d5a8
fix file close.
chaosannals Dec 28, 2024
d864b3e
解决冲突。
chaosannals Dec 28, 2024
7d8aa43
fix: cs gen client.
chaosannals Dec 30, 2024
88e43b2
fix: http message parse.
chaosannals Dec 30, 2024
1e1691b
fix: client options.
chaosannals Jan 2, 2025
50d4ada
php gen. scheme.
chaosannals Jan 10, 2025
9de232e
kt gen optional.
chaosannals Jan 13, 2025
9846cc9
uni app gen.
chaosannals Jan 20, 2025
5d749be
define struct.
chaosannals Jan 23, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion tools/goctl/api/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@ package api
import (
"github.com/spf13/cobra"
"github.com/zeromicro/go-zero/tools/goctl/api/apigen"
"github.com/zeromicro/go-zero/tools/goctl/api/csgen"
"github.com/zeromicro/go-zero/tools/goctl/api/dartgen"
"github.com/zeromicro/go-zero/tools/goctl/api/docgen"
"github.com/zeromicro/go-zero/tools/goctl/api/format"
"github.com/zeromicro/go-zero/tools/goctl/api/gogen"
"github.com/zeromicro/go-zero/tools/goctl/api/javagen"
"github.com/zeromicro/go-zero/tools/goctl/api/ktgen"
"github.com/zeromicro/go-zero/tools/goctl/api/new"
"github.com/zeromicro/go-zero/tools/goctl/api/phpgen"
"github.com/zeromicro/go-zero/tools/goctl/api/tsgen"
"github.com/zeromicro/go-zero/tools/goctl/api/unigen"
"github.com/zeromicro/go-zero/tools/goctl/api/validate"
"github.com/zeromicro/go-zero/tools/goctl/config"
"github.com/zeromicro/go-zero/tools/goctl/internal/cobrax"
Expand All @@ -31,6 +34,9 @@ var (
ktCmd = cobrax.NewCommand("kt", cobrax.WithRunE(ktgen.KtCommand))
pluginCmd = cobrax.NewCommand("plugin", cobrax.WithRunE(plugin.PluginCommand))
tsCmd = cobrax.NewCommand("ts", cobrax.WithRunE(tsgen.TsCommand))
phpCmd = cobrax.NewCommand("php", cobrax.WithRunE(phpgen.PhpCommand))
csCmd = cobrax.NewCommand("cs", cobrax.WithRunE(csgen.CSharpCommand))
uniCmd = cobrax.NewCommand("uni", cobrax.WithRunE(unigen.UniAppCommand))
)

func init() {
Expand All @@ -46,6 +52,9 @@ func init() {
pluginCmdFlags = pluginCmd.Flags()
tsCmdFlags = tsCmd.Flags()
validateCmdFlags = validateCmd.Flags()
phpCmdFlags = phpCmd.Flags()
csCmdFlags = csCmd.Flags()
uniCmdFlags = uniCmd.Flags()
)

apiCmdFlags.StringVar(&apigen.VarStringOutput, "o")
Expand Down Expand Up @@ -95,9 +104,22 @@ func init() {
tsCmdFlags.StringVar(&tsgen.VarStringAPI, "api")
tsCmdFlags.StringVar(&tsgen.VarStringCaller, "caller")
tsCmdFlags.BoolVar(&tsgen.VarBoolUnWrap, "unwrap")
tsCmdFlags.StringVar(&tsgen.VarStringUrlPrefix, "url")
tsCmdFlags.BoolVar(&tsgen.VarBoolCustomBody, "body")

validateCmdFlags.StringVar(&validate.VarStringAPI, "api")

phpCmdFlags.StringVar(&phpgen.VarStringDir, "dir")
phpCmdFlags.StringVar(&phpgen.VarStringAPI, "api")
phpCmdFlags.StringVar(&phpgen.VarStringNS, "ns")

csCmdFlags.StringVar(&csgen.VarStringDir, "dir")
csCmdFlags.StringVar(&csgen.VarStringAPI, "api")
csCmdFlags.StringVar(&csgen.VarStringNS, "ns")

uniCmdFlags.StringVar(&unigen.VarStringDir, "dir")
uniCmdFlags.StringVar(&unigen.VarStringAPI, "api")

// Add sub-commands
Cmd.AddCommand(dartCmd, docCmd, formatCmd, goCmd, javaCmd, ktCmd, newCmd, pluginCmd, tsCmd, validateCmd)
Cmd.AddCommand(dartCmd, docCmd, formatCmd, goCmd, javaCmd, ktCmd, newCmd, pluginCmd, tsCmd, validateCmd, phpCmd, csCmd, uniCmd)
}
43 changes: 43 additions & 0 deletions tools/goctl/api/csgen/ApiAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
public sealed class HeaderPropertyName : Attribute
{
public HeaderPropertyName(string name)
{
Name = name;
}

/// <summary>
/// The name of the property.
/// </summary>
public string Name { get; }
}


[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
public sealed class PathPropertyName : Attribute
{
public PathPropertyName(string name)
{
Name = name;
}

/// <summary>
/// The name of the property.
/// </summary>
public string Name { get; }
}


[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
public sealed class FormPropertyName : Attribute
{
public FormPropertyName(string name)
{
Name = name;
}

/// <summary>
/// The name of the property.
/// </summary>
public string Name { get; }
}
121 changes: 121 additions & 0 deletions tools/goctl/api/csgen/ApiBaseClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json;
using System.Reflection;
using System.Web;

public abstract class ApiBaseClient
{
protected readonly HttpClient httpClient = new HttpClient();


public ApiBaseClient(string host, short port, string scheme)
{
httpClient.BaseAddress = new Uri($"{scheme}://{host}:{port}");
}

protected async Task<R> CallResultAsync<R>(HttpMethod method, string path, CancellationToken cancellationToken, HttpContent? body) where R : new()
{
var response = await CallAsync(HttpMethod.Post, path, cancellationToken, body);
return await ParseResponseAsync<R>(response, null, cancellationToken);
}

protected async Task<HttpResponseMessage> CallAsync(HttpMethod method, string path, CancellationToken cancellationToken, HttpContent? body)
{
using var request = new HttpRequestMessage(method, path);
if (body != null)
{
request.Content = body;
}
return await httpClient.SendAsync(request, cancellationToken);
}


protected async Task<R> RequestResultAsync<T, R>(HttpMethod method, string path, T? param, CancellationToken cancellationToken, HttpContent? body) where R : new()
{
var response = await RequestAsync(HttpMethod.Post, path, param, cancellationToken, body);
return await ParseResponseAsync<R>(response, null, cancellationToken);
}

protected async Task<HttpResponseMessage> RequestAsync<T>(HttpMethod method, string path, T? param, CancellationToken cancellationToken, HttpContent? body)
{
using var request = new HttpRequestMessage();
request.Method = method;
var query = HttpUtility.ParseQueryString(string.Empty);

if (param != null)
{
foreach (var p in param.GetType().GetProperties())
{
var pv = p.GetValue(param);

// 头部
var hpn = p.GetCustomAttribute<HeaderPropertyName>();
if (hpn != null)
{
var hv = pv?.ToString() ?? "";
if (hpn.Name.ToLower() == "user-agent")
{
request.Headers.UserAgent.Clear();
if (!string.IsNullOrEmpty(hv))
{
request.Headers.UserAgent.Add(new ProductInfoHeaderValue(hv));
}
}
else
{
request.Headers.Add(hpn.Name, hv);
}
continue;
}

// 请求参数
var fpn = p.GetCustomAttribute<FormPropertyName>();
if (fpn != null)
{
query.Add(fpn.Name, pv?.ToString() ?? "");
continue;
}

// 路径参数
var ppn = p.GetCustomAttribute<PathPropertyName>();
if (ppn != null)
{
path.Replace($":{ppn.Name}", pv?.ToString() ?? "");
continue;
}
}

// 请求链接
request.RequestUri = new Uri(httpClient.BaseAddress!, query.Count > 0 ? $"{path}?{query}" : path);

// JSON 内容
if (HttpMethod.Get != method)
{
request.Content = body ?? ApiBodyJsonContent.Create(param);
}
}

return await httpClient.SendAsync(request, cancellationToken);
}

protected static async Task<R> ParseResponseAsync<R>(HttpResponseMessage response, JsonSerializerOptions? options, CancellationToken cancellationToken) where R : new()
{
R result = await response.Content.ReadFromJsonAsync<R>(options, cancellationToken) ?? new R();
foreach (var p in typeof(R).GetProperties())
{
// 头部
var hpn = p.GetCustomAttribute<HeaderPropertyName>();
if (hpn != null && response.Headers.Contains(hpn.Name))
{
var v = response.Headers.GetValues(hpn.Name);
if (v != null && v.Count() > 0)
{
p.SetValue(result, v.First());
}
continue;
}
}
return result;
}
}
33 changes: 33 additions & 0 deletions tools/goctl/api/csgen/ApiBodyJsonContent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System.Net;
using System.Text.Json;

public sealed class ApiBodyJsonContent : HttpContent
{
public MemoryStream Stream { get; private set; }

private ApiBodyJsonContent()
{
Stream = new MemoryStream();
Headers.Add("Content-Type", "application/json");
}

protected override Task SerializeToStreamAsync(Stream stream, TransportContext? context)
{
Stream.CopyTo(stream);
return Task.CompletedTask;
}

protected override bool TryComputeLength(out long length)
{
length = Stream.Length;
return true;
}

public static ApiBodyJsonContent Create<T>(T t, JsonSerializerOptions? settings=null)
{
var content = new ApiBodyJsonContent();
JsonSerializer.Serialize(content.Stream, t, settings);
content.Stream.Position = 0;
return content;
}
}
7 changes: 7 additions & 0 deletions tools/goctl/api/csgen/ApiException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
public class ApiException : Exception
{
public ApiException(string message, Exception? inner=null): base(message, inner)
{

}
}
120 changes: 120 additions & 0 deletions tools/goctl/api/csgen/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package csgen

import (
"fmt"
"os"
"path/filepath"
"strings"

_ "embed"

"github.com/zeromicro/go-zero/tools/goctl/api/spec"
)

//go:embed ApiAttribute.cs
var apiAttributeTemplate string

//go:embed ApiBodyJsonContent.cs
var apiBodyJsonContentTemplate string

//go:embed ApiException.cs
var apiExceptionTemplate string

//go:embed ApiBaseClient.cs
var apiBaseClientTemplate string

func genClient(dir string, ns string, api *spec.ApiSpec) error {
if err := writeTemplate(dir, ns, "ApiAttribute", apiAttributeTemplate); err != nil {
return err
}
if err := writeTemplate(dir, ns, "ApiBodyJsonContent", apiBodyJsonContentTemplate); err != nil {
return err
}
if err := writeTemplate(dir, ns, "ApiException", apiExceptionTemplate); err != nil {
return err
}
if err := writeTemplate(dir, ns, "ApiBaseClient", apiBaseClientTemplate); err != nil {
return err
}

return writeClient(dir, ns, api)
}

func writeTemplate(dir string, ns string, name string, template string) error {
fp := filepath.Join(dir, fmt.Sprintf("%s.cs", name))
f, err := os.OpenFile(fp, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o644)
if err != nil {
return err
}
defer f.Close()
fmt.Fprintf(f, "namespace %s;\r\n\r\n", ns)
fmt.Fprint(f, template)
return nil
}

func writeClient(dir string, ns string, api *spec.ApiSpec) error {
name := camelCase(api.Service.Name, true)
fp := filepath.Join(dir, fmt.Sprintf("%sClient.cs", name))
f, err := os.OpenFile(fp, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o644)
if err != nil {
return err
}
defer f.Close()

fmt.Fprintf(f, "namespace %s;\r\n\r\n", ns)

// 类
fmt.Fprintf(f, "public sealed class %sClient : ApiBaseClient\r\n{\r\n", name)

// 构造函数
fmt.Fprintf(f, " public %sClient(string host, short port, string scheme = \"http\") : base(host, port, scheme){}\r\n", name)

// 组
for _, g := range api.Service.Groups {
prefix := g.GetAnnotation("prefix")
p := camelCase(prefix, true)

// 路由
for _, r := range g.Routes {
an := camelCase(r.Path, true)
method := upperHead(strings.ToLower(r.Method), 1)

writeIndent(f, 4)
fmt.Fprint(f, "public async ")
if r.ResponseType != nil {
fmt.Fprintf(f, "Task<%s>", r.ResponseType.Name())
} else {
fmt.Fprint(f, "Task<HttpResponseMessage>")
}
fmt.Fprintf(f, " %s%s%sAsync(", method, p, an)
if r.RequestType != nil {
fmt.Fprintf(f, "%s request,", r.RequestType.Name())
}
fmt.Fprint(f, "CancellationToken cancellationToken, HttpContent? body=null)\r\n {\r\n")

writeIndent(f, 8)
fmt.Fprint(f, "return await ")

if r.RequestType != nil {
if r.ResponseType != nil {
fmt.Fprintf(f, "RequestResultAsync<%s,%s>(HttpMethod.%s, \"%s%s\", request, cancellationToken, body);\r\n", r.RequestType.Name(), r.ResponseType.Name(), method, prefix, r.Path)
} else {
fmt.Fprintf(f, "RequestAsync(HttpMethod.%s, \"%s%s\", request, cancellationToken, body);\r\n", method, prefix, r.Path)
}
} else {
if r.ResponseType != nil {
fmt.Fprintf(f, "CallResultAsync<%s>(HttpMethod.%s, \"%s%s\", cancellationToken, body);\r\n", r.ResponseType.Name(), method, prefix, r.Path)
} else {
fmt.Fprintf(f, "CallAsync(HttpMethod.%s, \"%s%s\", cancellationToken, body);\r\n", method, prefix, r.Path)
}
}

writeIndent(f, 4)
fmt.Fprint(f, "}\r\n")
}
}

fmt.Fprint(f, "}\r\n")

return nil
}
Loading