è·šæå¡è°çšïŒgen项ç®ç"éæ³"
è¿æ¯æ¶æç³»åç第6ç¯ïŒæä»¬æ¥èè埮æå¡æ¶æäžæåºç¡åŽä¹æå®¹æ"èž©å"çç¯èââè·šæå¡è°çšã
äœ ææ²¡æç»åè¿è¿æ ·ç婿¢ŠïŒ
åšäžæ©äžïŒäœ ååäžå倿žé±ŒïŒäº§åç»çå°±å²è¿æ¥ïŒ"çšæ·äžå¿æäºïŒ"
äœ äžæ¥ïŒå¥œå®¶äŒââ订åæå¡è°çšæ·æå¡çæ¥å£ïŒåæ°æ¹äºïŒäœè®¢åå¢é没æ¶å°éç¥ã
äºæ¯çº¿äž500é误ïŒçšæ·æè¯ïŒèæ¿é»èžã
åšåŸ®æå¡æ¶æäžïŒæå¡ä¹éŽçè°çšå°±åååžéç亀éââè·¯å£è¶å€ïŒäºæ æŠçè¶é«ã
æ¯å€äžäžªæå¡ïŒå°±å€äžä»œ"ææ¹äºæ¥å£ïŒäœ äžç¥é"çé£é©ã
æåHTTP客æ·ç«¯çäžå€§çç¹
1. éå€å³åšïŒæçäœäž
æ¯äžªæå¡èŠè°çšå«çæå¡ïŒéœåŸæåäžéHTTP客æ·ç«¯ä»£ç ã
æé 请æ±ãè§£æååºãå€çåŒåžžâŠâŠ
åæ ·çé»èŸïŒæ¢äžªæå¡åèŠéåäžéã
å°±åæ¯æ¬¡ç¹å€åéœèŠèªå·±ç»äžåŒ é€å å°åŸââ环äžçޝïŒ
ççè¿ç§æå代ç ç"çŸéŸç°åº"ïŒ
// 订åæå¡è°çšçšæ·æå¡ - æåçæ¬
func GetUserByID(userID string) (*User, error) {
// ç¬¬äžæ¥ïŒæé URL
url := fmt.Sprintf("http://user-service/api/v1/users/%s", userID)
// ç¬¬äºæ¥ïŒå建请æ±
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
// ç¬¬äžæ¥ïŒè®Ÿçœ®è¯·æ±å€Ž
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+getToken())
// ç¬¬åæ¥ïŒåé请æ±
client := &http.Client{Timeout: 10 * time.Second}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
// ç¬¬äºæ¥ïŒè¯»åååºäœ
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
// 第å
æ¥ïŒè§£æJSON
var result struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data User `json:"data"`
}
if err := json.Unmarshal(body, &result); err != nil {
return nil, err
}
// ç¬¬äžæ¥ïŒæ£æ¥äžå¡é误ç
if result.Code != 0 {
return nil, fmt.Errorf("äžå¡é误: %s", result.Msg)
}
return &result.Data, nil
}
èäžè¿åªæ¯GET请æ±ïŒPOSTãPUTãDELETEå¢ïŒæ¯äžªéœèŠéå€è¿å¥æµçšã
æŽå¯æçæ¯ïŒæ¯äžªæå¡å¢ééœåšéå€åè¿æ ·ç代ç ã
10䞪æå¡ïŒ50䞪æ¥å£ïŒå°±æ¯3000è¡éå€ä»£ç ã
2. æ¥å£åæŽïŒåæ¥å°éŸ
æå¡Aæ¹äºæ¥å£ïŒæå¡Bæä¹ç¥éïŒ
é å£å€Žéç¥ïŒé çŸ€æ¶æ¯ïŒé "åºé®é¢å诎"ïŒ
ç°å®åŸåŸæ¯ïŒåºé®é¢æåç°ã
è¿ç§"åç¥åè§"ç代价ïŒå¯èœæ¯çº¿äžæ éã
䞟䞪çå®çäŸåïŒ
çšæ·æå¡æ GetUserRequest çåæ®µä» user_id æ¹æäº userIdïŒé©Œå³°åœåïŒã
订åæå¡è¿åšçš user_id å请æ±ã
ç»æå¢ïŒç𿷿塿¶äžå°åæ°ïŒè¿åç©ºæ°æ®ã
3. ç±»åäžå®å šïŒè¿è¡æ¶æåç°é®é¢
æåç客æ·ç«¯ïŒåæ°ç±»åå šé èªè§ã
åç¬Šäž²äŒ ææ°åïŒè¿è¡æ¶æ¥éæç¥éã
åæ®µååéäžäžªåæ¯ïŒè°äºå倩æåç°ã
// è¿ç§é误ïŒçŒè¯åšäžäŒåè¯äœ
type GetUserRequest struct {
UserID string `json:"user_id"` // æ£ç¡®
}
// äœæ¯æåçæ¶åïŒåŸå®¹æåæïŒ
type GetUserRequest struct {
UserID string `json:"userId"` // éäºïŒäœçŒè¯åšäžç¥é
}
åŠææäººåž®äœ "èªåšå代ç "å¢ïŒ
æ³è±¡äžäžïŒ
äœ åªéèŠå®ä¹äžæ¬¡æ¥å£ïŒç¶åæäžªæé®ïŒæææå¡ç客æ·ç«¯ä»£ç èªåšçæã
åæ°ç±»åïŒèªåšæ ¡éªã
æ¥å£åæŽïŒéæ°çæå°±è¡ã
è¿å°±åââäœ åªéèŠç»äžåŒ 讟计åŸïŒå·¥åèªåšåž®äœ ç产é¶ä»¶ã
è¿å°±æ¯ä»£ç çæçæ žå¿æè·¯ã
gen项ç®ïŒAPIçç"ç¿»è¯å®"
[é åŸå»ºè®®ïŒäžäžªç¿»è¯å®ç«åšäž€äžªæå¡ä¹éŽïŒå·Šææ¿"æ¥å£å®ä¹"ïŒå³ææ¿"çæç客æ·ç«¯ä»£ç "]
gen项ç®çå®äœåŸæž æ°ïŒ
æ¶ææ»è§
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â gen é¡¹ç®æ¶æ â
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ€
â â
â ââââââââââââââââ ââââââââââââââââ ââââââââââââââââ â
â â æ¥å£å®ä¹å± âââââ¶â 代ç çæåš âââââ¶â å€è¯èšå®¢æ·ç«¯ â â
â â (API Specs) â â (Generator) â â (Clients) â â
â ââââââââââââââââ ââââââââââââââââ ââââââââââââââââ â
â â â â â
â ⌠⌠⌠â
â ââââââââââââââââ ââââââââââââââââ ââââââââââââââââ â
â â YAML/JSON â â æš¡æ¿åŒæ â â Go Client â â
â â Protoå®ä¹ â â ç±»åæ å° â â Java Client â â
â â OpenAPI â â 代ç çæ â â TS Client â â
â ââââââââââââââââ ââââââââââââââââ ââââââââââââââââ â
â â
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
宿¯æä¹å·¥äœçïŒ
1. æ¥å£å®ä¹éäžå
æææå¡çæ¥å£å®ä¹ïŒç»äžæŸåšäžäžªå°æ¹ç®¡çã
å°±åäžäžª"äžå€®æ¡£æ¡éŠ"ïŒè°æ³æ¥æ¥å£ïŒæ¥è¿éå°±è¡ã
# services/user/api/get_user.yaml
name: GetUser
description: æ ¹æ®çšæ·IDè·åçšæ·ä¿¡æ¯
path: /api/v1/users/{user_id}
method: GET
auth: required
request:
path_params:
user_id:
type: string
description: çšæ·ID
required: true
pattern: "^[a-zA-Z0-9]{16}$"
query_params:
fields:
type: string[]
description: éèŠè¿åçåæ®µå衚
required: false
example: ["nickname", "avatar"]
response:
success:
code: 0
message: "success"
data:
type: User
description: çšæ·ä¿¡æ¯å¯¹è±¡
errors:
- code: 1001
message: "çšæ·äžååš"
- code: 1002
message: "çšæ·å·²è¢«çŠçš"
types:
User:
user_id: string
nickname: string
avatar: string
level: integer
vip_expire_time: timestamp
created_at: timestamp
ææå®¢æ·ç«¯ä»£ç éœä»è¿éçæïŒä¿è¯äžèŽæ§ã
2. ä»å®ä¹çæä»£ç
æäºæ¥å£å®ä¹ïŒgen项ç®å°±èœèªåšçæåè¯èšçæ¬ç客æ·ç«¯ä»£ç ã
JavaãGoãTypeScriptâŠâŠäœ èŠä»ä¹ïŒå®çæä»ä¹ã
// Code generated by gen-project. DO NOT EDIT.
// Source: services/user/api/get_user.yaml
package userclient
import (
"context"
"fmt"
"net/http"
"time"
)
// GetUserRequest è·åçšæ·ä¿¡æ¯è¯·æ±
type GetUserRequest struct {
// UserID çšæ·IDïŒå¿
å¡«ïŒ
UserID string `json:"user_id" validate:"required,len=16,alphanum"`
// Fields éèŠè¿åçåæ®µå衚ïŒå¯éïŒ
Fields []string `json:"fields,omitempty"`
}
// GetUserResponse è·åçšæ·ä¿¡æ¯ååº
type GetUserResponse struct {
Code int `json:"code"`
Message string `json:"message"`
Data *User `json:"data,omitempty"`
}
// User çšæ·ä¿¡æ¯
type User struct {
UserID string `json:"user_id"`
Nickname string `json:"nickname"`
Avatar string `json:"avatar"`
Level int `json:"level"`
VIPExpireTime time.Time `json:"vip_expire_time"`
CreatedAt time.Time `json:"created_at"`
}
// GetUser æ ¹æ®çšæ·IDè·åçšæ·ä¿¡æ¯
func (c *Client) GetUser(ctx context.Context, req *GetUserRequest) (*User, error) {
// åæ°æ ¡éª
if err := c.validator.Struct(req); err != nil {
return nil, fmt.Errorf("åæ°æ ¡éªå€±èŽ¥: %w", err)
}
// æé URL
path := fmt.Sprintf("/api/v1/users/%s", req.UserID)
// åé请æ±
var resp GetUserResponse
if err := c.doRequest(ctx, http.MethodGet, path, req, &resp); err != nil {
return nil, fmt.Errorf("请æ±å€±èŽ¥: %w", err)
}
// æ£æ¥äžå¡é误ç
if resp.Code != 0 {
return nil, &APIError{
Code: resp.Code,
Message: resp.Message,
}
}
return resp.Data, nil
}
// Code generated by gen-project. DO NOT EDIT.
// Source: services/user/api/get_user.yaml
package com.gameplatform.user.client;
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Path;
import retrofit2.http.Query;
public interface UserService {
/**
* æ ¹æ®çšæ·IDè·åçšæ·ä¿¡æ¯
*
* @param userId çšæ·IDïŒå¿
å¡«ïŒ16äœåæ¯æ°åïŒ
* @param fields éèŠè¿åçåæ®µå衚ïŒå¯éïŒ
* @return çšæ·ä¿¡æ¯
*/
@GET("/api/v1/users/{user_id}")
Call<ApiResponse<User>> getUser(
@Path("user_id") String userId,
@Query("fields") List<String> fields
);
}
// 䜿çšç€ºäŸ
UserService userService = retrofit.create(UserService.class);
Call<ApiResponse<User>> call = userService.getUser("abc123def456ghi7",
Arrays.asList("nickname", "avatar"));
Response<ApiResponse<User>> response = call.execute();
// Code generated by gen-project. DO NOT EDIT.
// Source: services/user/api/get_user.yaml
export interface GetUserRequest {
/** çšæ·IDïŒå¿
å¡«ïŒ16äœåæ¯æ°åïŒ */
user_id: string;
/** éèŠè¿åçåæ®µå衚ïŒå¯éïŒ */
fields?: string[];
}
export interface User {
user_id: string;
nickname: string;
avatar: string;
level: number;
vip_expire_time: Date;
created_at: Date;
}
export interface ApiResponse<T> {
code: number;
message: string;
data?: T;
}
/**
* æ ¹æ®çšæ·IDè·åçšæ·ä¿¡æ¯
*/
export async function getUser(request: GetUserRequest): Promise<User> {
const response = await fetch(`/api/v1/users/${request.user_id}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${getAuthToken()}`,
},
});
const result: ApiResponse<User> = await response.json();
if (result.code !== 0) {
throw new APIError(result.code, result.message);
}
return result.data!;
}
3. 区类åä¿é
çæçä»£ç æ¯åŒºç±»åçã
åæ°äŒ éç±»åïŒçŒè¯æçŽæ¥æ¥éïŒäžçšçå°è¿è¡æ¶æåç°ã
// è°çšçæç客æ·ç«¯
user, err := userClient.GetUser(ctx, &GetUserRequest{
UserID: "abc123def456ghi7", // IDEæèªåšè¡¥å
š
Fields: []string{"nickname", "avatar"},
})
// åŠæåéç±»åïŒçŒè¯åšçŽæ¥æ¥é
user, err := userClient.GetUser(ctx, &GetUserRequest{
UserID: 12345, // çŒè¯é误ïŒäžèœæintèµç»string
})
代ç çæåšç讟计åç
gen项ç®çæ žå¿æ¯äžäžªæš¡æ¿é©±åšç代ç çæåšã
æ¶æåå±
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â 代ç çæåšæ žå¿æ¶æ â
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ€
â â
â âââââââââââââââ âââââââââââââââ âââââââââââââââ â
â â å®ä¹è§£æåš â â äžéŽè¡šç€º â â æš¡æ¿åŒæ â â
â â Parser âââ¶â IR âââ¶â Template â â
â âââââââââââââââ âââââââââââââââ âââââââââââââââ â
â â â â â
â ⌠⌠⌠â
â âââââââââââââââ âââââââââââââââ âââââââââââââââ â
â â YAML Parser â â Type System â â Go Template â â
â â Proto Parserâ â Validations â â Mustache â â
â â OpenAPI â â Endpoints â â Custom DSL â â
â âââââââââââââââ âââââââââââââââ âââââââââââââââ â
â â
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
第äžå±ïŒå®ä¹è§£æåšïŒParserïŒ
èŽèŽ£æåç§æ ŒåŒçæ¥å£å®ä¹ïŒè§£ææç»äžçå éšç»æã
// è§£æåšæ¥å£
type Parser interface {
// Parse è§£ææ¥å£å®ä¹æä»¶
Parse(content []byte) (*APIDefinition, error)
// SupportedFormats è¿åæ¯æçæ ŒåŒ
SupportedFormats() []string
}
// APIDefinition ç»äžçæ¥å£å®ä¹ç»æ
type APIDefinition struct {
Name string
Description string
Path string
Method string
Auth AuthConfig
Request RequestSpec
Response ResponseSpec
Types map[string]TypeSpec
}
æ¯æå€ç§å®ä¹æ ŒåŒïŒ
// YAMLè§£æåš
type YAMLParser struct{}
func (p *YAMLParser) Parse(content []byte) (*APIDefinition, error) {
var def APIDefinition
if err := yaml.Unmarshal(content, &def); err != nil {
return nil, err
}
return &def, nil
}
// OpenAPIè§£æåš
type OpenAPIParser struct{}
func (p *OpenAPIParser) Parse(content []byte) (*APIDefinition, error) {
var spec openapi3.T
if err := json.Unmarshal(content, &spec); err != nil {
return nil, err
}
// 蜬æ¢äžºç»äžçAPIDefinition
return convertOpenAPIToDefinition(&spec), nil
}
// Protobufè§£æåš
type ProtoParser struct{}
func (p *ProtoParser) Parse(content []byte) (*APIDefinition, error) {
// è§£æ.protoæä»¶
protoFile, err := protoparser.ParseBytes(content)
if err != nil {
return nil, err
}
// 蜬æ¢äžºç»äžçAPIDefinition
return convertProtoToDefinition(protoFile), nil
}
第äºå±ïŒäžéŽè¡šç€ºïŒIRïŒ
æè§£æåçå®ä¹ïŒèœ¬æ¢æè¯èšæ å ³çäžéŽè¡šç€ºã
// ç±»åç³»ç»
type TypeSpec struct {
Name string
Kind TypeKind // Primitive, Array, Map, Struct, Enum
Fields []FieldSpec
Element *TypeSpec // for Array/Map
EnumValues []EnumValue
}
type FieldSpec struct {
Name string
Type *TypeSpec
Required bool
Default interface{}
Validation []ValidationRule
Tags map[string]string // json, xml, validateç
}
type ValidationRule struct {
Rule string // required, min, max, pattern, etc.
Value interface{}
Message string
}
// 端ç¹å®ä¹
type EndpointSpec struct {
Name string
Path string
Method string
Request *TypeSpec
Response *TypeSpec
Errors []ErrorSpec
Middleware []string // 讀è¯ãéæµç
}
第äžå±ïŒæš¡æ¿åŒæïŒTemplateïŒ
åºäºäžéŽè¡šç€ºïŒçšæš¡æ¿çæåè¯èšä»£ç ã
// æš¡æ¿åŒæ
type TemplateEngine struct {
templates map[string]*template.Template
}
func (e *TemplateEngine) GenerateGoClient(def *APIDefinition) (string, error) {
return e.execute("go_client.tmpl", def)
}
func (e *TemplateEngine) GenerateJavaClient(def *APIDefinition) (string, error) {
return e.execute("java_client.tmpl", def)
}
func (e *TemplateEngine) GenerateTypeScriptClient(def *APIDefinition) (string, error) {
return e.execute("typescript_client.tmpl", def)
}
// {{ .Name }} {{ .Description }}
// Code generated by gen-project. DO NOT EDIT.
package {{ .PackageName }}
import (
"context"
"net/http"
)
{{ range .Types }}
// {{ .Name }} {{ .Description }}
type {{ .Name }} struct {
{{ range .Fields }}
{{ .Name }} {{ .Type.GoType }} `json:"{{ .JSONName }}"{{ if .Required }} validate:"required"{{ end }}`
{{ end }}
}
{{ end }}
{{ range .Endpoints }}
// {{ .Name }} {{ .Description }}
func (c *Client) {{ .Name }}(ctx context.Context, req *{{ .Request.Name }}) (*{{ .Response.Name }}, error) {
var resp {{ .Response.Name }}
err := c.doRequest(ctx, http.Method{{ .Method }}, "{{ .Path }}", req, &resp)
if err != nil {
return nil, err
}
return &resp, nil
}
{{ end }}
æš¡æ¿è®Ÿè®¡äžæ©å±æºå¶
æš¡æ¿ç»§æ¿äœç³»
gené¡¹ç®æ¯ææš¡æ¿ç»§æ¿ïŒæ¹äŸ¿å®å¶ïŒ
templates/
âââ base/
â âââ client_base.tmpl # åºç¡å®¢æ·ç«¯æš¡æ¿
â âââ types_base.tmpl # åºç¡ç±»åæš¡æ¿
â âââ validation_base.tmpl # åºç¡æ ¡éªæš¡æ¿
âââ go/
â âââ client.tmpl # Go客æ·ç«¯æš¡æ¿ïŒç»§æ¿baseïŒ
â âââ types.tmpl # Goç±»åæš¡æ¿
â âââ validation.tmpl # Goæ ¡éªæš¡æ¿
âââ java/
â âââ client.tmpl # Java客æ·ç«¯æš¡æ¿
â âââ types.tmpl # Javaç±»åæš¡æ¿
â âââ validation.tmpl # Javaæ ¡éªæš¡æ¿
âââ custom/
âââ company_client.tmpl # å
¬åžå®å¶æš¡æ¿
âââ company_types.tmpl
èªå®ä¹æš¡æ¿ç€ºäŸ
# gen-config.yaml
generator:
base_template: templates/base
custom_template: templates/custom/company
# èªå®ä¹ç±»åæ å°
type_mappings:
datetime: "time.Time" # Go
datetime_java: "LocalDateTime" # Java
datetime_ts: "Date" # TypeScript
# èªå®ä¹å¯Œå
¥å
imports:
go:
- "github.com/company/common/errors"
- "github.com/company/common/logger"
java:
- "com.company.common.exception"
# èªå®ä¹çæé©å
hooks:
pre_generate:
- "scripts/pre_gen.sh"
post_generate:
- "scripts/post_gen.sh"
- "gofmt -w ."
æä»¶æ©å±æºå¶
// æä»¶æ¥å£
type GeneratorPlugin interface {
// Name æä»¶åç§°
Name() string
// BeforeGenerate çæåé©å
BeforeGenerate(ctx *GenerateContext) error
// AfterGenerate çæåé©å
AfterGenerate(ctx *GenerateContext) error
// CustomTypes èªå®ä¹ç±»åå€çåš
CustomTypes() map[string]TypeHandler
}
// 瀺äŸïŒèªå®ä¹IDç±»åæä»¶
type IDTypePlugin struct{}
func (p *IDTypePlugin) Name() string {
return "id_type_plugin"
}
func (p *IDTypePlugin) CustomTypes() map[string]TypeHandler {
return map[string]TypeHandler{
"SnowflakeID": {
GoType: "int64",
JavaType: "Long",
TypeScriptType: "number",
Validator: "snowflake",
},
"UUID": {
GoType: "string",
JavaType: "String",
TypeScriptType: "string",
Validator: "uuid",
},
}
}
// 泚åæä»¶
func init() {
generator.RegisterPlugin(&IDTypePlugin{})
}
è·šè¯è𿝿
ç±»åæ å°è¡š
gen项ç®å 眮äºå®æŽçè·šè¯èšç±»åæ å°ïŒ
| éçšç±»å | Go | Java | TypeScript | Python |
|---|---|---|---|---|
| string | string | String | string | str |
| integer | int | Integer | number | int |
| long | int64 | Long | number | int |
| float | float64 | Double | number | float |
| boolean | bool | Boolean | boolean | bool |
| datetime | time.Time | LocalDateTime | Date | datetime |
| array |
[]T | List |
T[] | List[T] |
| map |
map[K]V | Map |
Record |
Dict[K,V] |
çæåœä»€
# çæææè¯èšç客æ·ç«¯
gen generate --all
# åªçæGo客æ·ç«¯
gen generate --lang go
# çææå®æå¡çJava客æ·ç«¯
gen generate --service user --lang java
# çæå°æå®ç®åœ
gen generate --lang typescript --output ./clients/ts
CI/CDéæ
# .github/workflows/gen-api.yml
name: Generate API Clients
on:
push:
paths:
- 'services/**/api/*.yaml'
jobs:
generate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Go
uses: actions/setup-go@v4
with:
go-version: '1.21'
- name: Install gen CLI
run: go install github.com/company/gen-project/cmd/gen@latest
- name: Generate clients
run: |
gen generate --all
- name: Create Pull Request
uses: peter-evans/create-pull-request@v5
with:
title: "chore: regenerate API clients"
branch: auto/gen-clients
commit-message: "auto: regenerate API clients from updated definitions"
gen项ç®çäžå€§æ žå¿ä»·åŒ
ð ç±»åå®å š
çæç客æ·ç«¯ä»£ç æ¯åŒºç±»åçã
äŒ åæ¶ïŒIDEäŒåè¯äœ è¿äžªå段æ¯ä»ä¹ç±»åïŒè¯¥å¡«ä»ä¹åŒã
// æå代ç ïŒè¿è¡æ¶æåç°é误
params := map[string]interface{}{
"user_id": 12345, // è¿è¡æ¶æç¥éç±»åé误
}
// çæä»£ç ïŒçŒè¯æå°±æ¥é
req := &GetUserRequest{
UserID: 12345, // çŒè¯é误ïŒ
}
ð æ¥å£äžèŽæ§
æ¥å£å®ä¹æ¯å¯äžççæ¥æºã
æææå¡ç客æ·ç«¯ä»£ç ïŒéœä»è¿äžªå®ä¹çæã
äžäŒåºç°"æä»¥äžºæ¥å£æ¯è¿æ ·ïŒå®é æ¯é£æ ·"çæ åµã
ââââââââââââââââ
â APIå®ä¹ â â å¯äžççæ¥æº
â (YAMLæä»¶) â
ââââââââ¬ââââââââ
â
âŒ
ââââââââââââââââââââââââââââââââââââââââ
â 代ç çæåš â
ââââââââââââââââââââââââââââââââââââââââ
â â â
⌠⌠âŒ
ââââââââââââ ââââââââââââ ââââââââââââ
â Go Clientâ âJava Clientâ â TS Clientâ
ââââââââââââ ââââââââââââ ââââââââââââ
â â â
⌠⌠âŒ
订åæå¡ æ¯ä»æå¡ å端åºçš
â¡ åŒåæç
äžçšæåHTTP客æ·ç«¯äºã
å®ä¹å¥œæ¥å£ïŒçæä»£ç ïŒçŽæ¥è°çšã
çäžçæ¶éŽïŒå¯ä»¥çšæ¥åäžå¡é»èŸââæè æžé±Œã
| æäœ | æå | çæ |
|---|---|---|
| æ°å¢æ¥å£ | 2å°æ¶ | 10åéïŒåå®ä¹+çæïŒ |
| ä¿®æ¹æ¥å£ | 1å°æ¶ | 5åéïŒæ¹å®ä¹+éæ°çæïŒ |
| æ°å¢æå¡è°çš | 30åé | 1åéïŒåŒå ¥äŸèµïŒ |
| æ¥å£ææ¡£ | æåïŒå®¹æè¿æ¶ | èªåšçæïŒæ°žè¿åæ¥ |
çæ¬ç®¡çïŒæ¥å£åäºæä¹åïŒ
[é åŸå»ºè®®ïŒäžäžªçæ¬å·ä»v1.0.0åæv2.0.0ïŒæèŸ¹æ æ³š"å Œå®¹"å"äžå Œå®¹"çåæŽ]
æ¥å£äžå¯èœäžæäžåã
éæ±åšåïŒäžå¡åšåïŒæ¥å£ä¹åšåã
äœåæŽæ¥å£ïŒææçå°±æ¯"æ¹äºè¿èŸ¹ïŒåŽ©äºé£èŸ¹"ã
è¯ä¹åçæ¬æ§å¶
gen项ç®äœ¿çšè¯ä¹åçæ¬ïŒSemVerïŒïŒMAJOR.MINOR.PATCH
v2.3.1
â â â
â â ââ PATCH: ä¿®å€bugïŒå®å
šå
Œå®¹
â ââââ MINOR: æ°å¢åèœïŒååå
Œå®¹
ââââââ MAJOR: éå€§åæŽïŒå¯èœäžå
Œå®¹
åæŽç±»åäžå€ççç¥
# æ¥å£çæ¬é
眮
api_version: v1.2.0
# å
Œå®¹æ§è§å
compatibility:
# æ·»å å¯éåæ®µ â MINORçæ¬å级
add_optional_field: minor
# æ·»å å¿
å¡«åæ®µ â MAJORçæ¬å级
add_required_field: major
# å é€å段 â MAJORçæ¬å级
remove_field: major
# ä¿®æ¹å段类å â MAJORçæ¬å级
change_field_type: major
# æ·»å æäžŸåŒ â MINORçæ¬å级
add_enum_value: minor
# å é€æäžŸåŒ â MAJORçæ¬å级
remove_enum_value: major
gené¡¹ç®æä¹å€ççæ¬ïŒ
- æ°å¢åæ®µïŒæ²¡é®é¢ïŒè客æ·ç«¯å¿œç¥å°±è¡ã
- å é€å段ïŒäžè¡ïŒè客æ·ç«¯äŒæ¥éã
- æ¹å段类åïŒèŠçæ åµïŒå¯èœéèŠåå€§çæ¬ã
å®è·µå»ºè®®
1. å°æ¹åšïŒå°çæ¬å级
å 䞪å¯éåæ®µïŒpatchçæ¬å·+1å°±è¡ã
# v1.2.0 â v1.2.1
types:
User:
user_id: string
nickname: string
avatar: string
level: integer
vip_expire_time: timestamp
created_at: timestamp
# æ°å¢å¯éåæ®µ
bio:
type: string
required: false # å¯éïŒè客æ·ç«¯äžå圱å
2. 倧æ¹åšïŒå€§çæ¬å级
æ¹äºæ žå¿é»èŸïŒmajorçæ¬å·+1ïŒæç¡®åç¥è°çšæ¹éèŠå级ã
# v1.2.1 â v2.0.0
# éå€§åæŽïŒuser_idä»stringæ¹äžºint64
types:
User:
user_id: int64 # ç±»ååäºïŒè客æ·ç«¯éèŠå级
nickname: string
avatar: string
# ...
# åæ¶ç»Žæ€v1çæ¬äžæ®µæ¶éŽ
deprecated:
version: v1.2.1
sunset_date: 2026-06-01 # ç»è°çšæ¹3䞪æå级æ¶éŽ
migration_guide: docs/migration/v1-to-v2.md
3. è¿æž¡æå ±å
å€§çæ¬å级æ¶ïŒæ°èæ¥å£å¯ä»¥å ±åäžæ®µæ¶éŽã
# gen-config.yaml
versioning:
strategy: coexist # å
±åçç¥
versions:
v1:
path_prefix: /api/v1
deprecated: true
sunset: 2026-06-01
v2:
path_prefix: /api/v2
current: true
// çæç客æ·ç«¯èªåšéæ©çæ¬
client := userclient.New(userclient.Config{
Version: "v2", // 䜿çšv2çæ¬
// åŠæéèŠïŒä¹å¯ä»¥fallbackå°v1
FallbackVersion: "v1",
})
æžžæè¡äžçå®è·µïŒå€äžªåŸ®æå¡åŠäœé«æåäœïŒ
æžžæè¡äžçå¹³å°ïŒæå¡æ°éåŸåŸåŸå€ã
çšæ·æå¡ã订åæå¡ãæ¯ä»æå¡ãæžžææå¡ãæŽ»åšæå¡âŠâŠ
æ¯äžªæå¡éœèŠè°çšå ¶ä»æå¡ïŒè°çšå ³ç³»éç»Œå€æã
å žåæžžæå¹³å°æå¡ææ
âââââââââââââââ
â çœå
³æå¡ â
â Gateway â
ââââââââ¬âââââââ
â
ââââââââââââââââââââââââŒâââââââââââââââââââââââ
â â â
⌠⌠âŒ
âââââââââââââââ âââââââââââââââ âââââââââââââââ
â çšæ·æå¡ âââââââââ 订åæå¡ âââââââââ æ¯ä»æå¡ â
â User â â Order â â Payment â
ââââââââ¬âââââââ ââââââââ¬âââââââ âââââââââââââââ
â â
â â
⌠âŒ
âââââââââââââââ âââââââââââââââ
â æžžææå¡ âââââââââ æŽ»åšæå¡ â
â Game â â Activity â
âââââââââââââââ âââââââââââââââ
没ægen项ç®ä¹å
- æå¡AèŠè°æå¡BïŒå æŸBçåŒå鮿¥å£
- æå客æ·ç«¯ä»£ç ïŒç¥ç¥·æ²¡åé
- Bæ¹äºæ¥å£ïŒAäžç¥éïŒçº¿äžåºé®é¢
- åºäºé®é¢åä¿®å€ïŒåœ±åçšæ·äœéª
æäºgen项ç®ä¹å
- æææ¥å£å®ä¹åšgen项ç®éïŒäžç®äºç¶
- 客æ·ç«¯ä»£ç èªåšçæïŒäžçšæå
- æ¥å£åæŽæ¶ïŒéæ°çæä»£ç å³å¯
- ç±»åå®å šïŒçŒè¯æå°±èœåç°é®é¢
éåæ¶çåæ
æä»¬æ¥çäžç»ç宿°æ®ïŒ
| ææ | åŒå ¥gen项ç®å | åŒå ¥gen项ç®å | æå |
|---|---|---|---|
| æ°æ¥å£åŒåæ¶éŽ | 2å°æ¶/䞪 | 15åé/䞪 | 87.5% |
| æ¥å£åæŽåæ¥æ¶éŽ | 1倩 | 5åé | 99.6% |
| æ¥å£çžå ³bugæ° | 15䞪/æ | 2䞪/æ | 86.7% |
| è·šå¢éæ²éææ¬ | 30åé/æ¥å£ | 0åé | 100% |
| 代ç éå€ç | 40% | 5% | 87.5% |
å讟å¢éæ50䞪æå¡ïŒå¹³åæ¯äžªæå¡è°çš10äžªå ¶ä»æå¡ïŒ
- èççåŒåæ¶éŽïŒ50 à 10 à 2å°æ¶ = 1000å°æ¶/幎
- èççç»Žæ€æ¶éŽïŒ200䞪æ¥å£ à 1å°æ¶/æ à 12æ = 2400å°æ¶/幎
- åå°ççº¿äžæ éïŒ13䞪 à 4å°æ¶å€ç = 52å°æ¶/幎
ç宿¡äŸïŒæžžæSDKçæ
# services/game/api/create_room.yaml
name: CreateGameRoom
description: åå»ºæžžææ¿éŽ
path: /api/v1/games/{game_id}/rooms
method: POST
auth: required
request:
path_params:
game_id:
type: string
description: æžžæID
required: true
body:
room_name:
type: string
description: æ¿éŽåç§°
required: true
max_length: 50
max_players:
type: integer
description: æå€§ç©å®¶æ°
required: true
min: 2
max: 100
game_mode:
type: enum
values: [normal, ranked, custom]
default: normal
settings:
type: GameSettings
description: æžžæè®Ÿçœ®
response:
success:
code: 0
data:
type: GameRoom
types:
GameSettings:
time_limit: integer
allow_spectators: boolean
password: string
GameRoom:
room_id: string
room_name: string
game_id: string
creator_id: string
max_players: integer
current_players: integer
status: enum [waiting, playing, finished]
created_at: timestamp
# çææžžææå¡SDK
gen generate --service game --lang all --output ./sdks/
# èŸåºïŒ
# ./sdks/go/game-client/
# ./sdks/java/game-client/
# ./sdks/typescript/game-client/
# ./sdks/python/game-client/
# ./sdks/csharp/game-client/ # Unityäžçš
gen项ç®çäœ¿çšæµçš
[é åŸå»ºè®®ïŒäžäžªæµçšåŸïŒå±ç€ºä»"å®ä¹æ¥å£"å°"çæä»£ç "å°"æå¡è°çš"ç宿޿µçš]
ç¬¬äžæ¥ïŒå®ä¹æ¥å£
åšgen项ç®äžïŒçšç»äžçæ ŒåŒå®ä¹æ¥å£ã
å æ¬è¯·æ±åæ°ãååºç»æãé误ç çã
# services/order/api/create_order.yaml
name: CreateOrder
description: å建订å
path: /api/v1/orders
method: POST
auth: required
rate_limit:
requests: 100
window: 1m
request:
body:
game_id:
type: string
required: true
items:
type: array
items: OrderItem
required: true
payment_method:
type: enum
values: [alipay, wechat, balance]
required: true
response:
success:
code: 0
data:
type: Order
errors:
- code: 2001
message: "æžžæäžååš"
- code: 2002
message: "ååå·²äžæ¶"
- code: 2003
message: "åºåäžè¶³"
ç¬¬äºæ¥ïŒçæä»£ç
è¿è¡çæåœä»€ïŒgen项ç®äŒæ ¹æ®æ¥å£å®ä¹ïŒçæåè¯èšç客æ·ç«¯ä»£ç ã
# æ¬å°çæ
gen generate --service order --lang go
# æè
éè¿CIèªåšçæ
git push origin main
# â è§ŠåGitHub Actions
# â èªåšçæææè¯èšç客æ·ç«¯
# â èªåšå建PR
ç¬¬äžæ¥ïŒåŒå ¥äŸèµ
è°ç𿹿å¡åŒå ¥çæç客æ·ç«¯äŸèµã
å°±ååŒå ¥äžäžªæ®éçSDKã
// go.mod
require (
github.com/company/gen-clients/go/user v1.2.3
github.com/company/gen-clients/go/order v1.1.0
github.com/company/gen-clients/go/payment v2.0.1
)
<dependency>
<groupId>com.company.gen-clients</groupId>
<artifactId>user-client</artifactId>
<version>1.2.3</version>
</dependency>
{
"dependencies": {
"@company/gen-clients-user": "^1.2.3",
"@company/gen-clients-order": "^1.1.0"
}
}
ç¬¬åæ¥ïŒçŽæ¥è°çš
代ç éçŽæ¥è°çšå®¢æ·ç«¯æ¹æ³ïŒå°±åè°çšæ¬å°åœæ°äžæ ·ã
åæ°ç±»åãè¿åç±»åïŒéœæIDEæç€ºã
// 订åæå¡è°çšçšæ·æå¡
func (s *OrderService) CreateOrder(ctx context.Context, req *CreateOrderReq) (*Order, error) {
// 1. éªè¯çšæ·ä¿¡æ¯
user, err := s.userClient.GetUser(ctx, &userclient.GetUserRequest{
UserID: req.UserID,
})
if err != nil {
return nil, fmt.Errorf("è·åçšæ·ä¿¡æ¯å€±èŽ¥: %w", err)
}
if user.Status == "banned" {
return nil, errors.New("çšæ·å·²è¢«çŠçš")
}
// 2. å建订å
order, err := s.orderClient.CreateOrder(ctx, &orderclient.CreateOrderRequest{
GameID: req.GameID,
UserID: req.UserID,
Items: req.Items,
PaymentMethod: req.PaymentMethod,
})
if err != nil {
return nil, fmt.Errorf("å建订å倱莥: %w", err)
}
// 3. åééç¥
s.notifyClient.SendNotification(ctx, ¬ifyclient.SendNotificationRequest{
UserID: req.UserID,
Type: "order_created",
Title: "订åå建æå",
Content: fmt.Sprintf("æšç订å %s å·²å建", order.OrderID),
})
return order, nil
}
åžžè§é®é¢è§£ç
QïŒgen项ç®äŒäžäŒå¢å ç»Žæ€ææ¬ïŒ
äžäŒã
æ¥å£å®ä¹æ¬èº«å°±æ¯éèŠçïŒgen项ç®åªæ¯æå®éäžç®¡çäºã
åèå 䞺éäžç®¡çïŒç»Žæ€ææ¬éäœäºã
| åºæ¯ | 没ægené¡¹ç® | ægené¡¹ç® |
|---|---|---|
| æ°å¢æ¥å£ | æ¯äžªè°çšæ¹åèªåææ¡£ïŒå®¹æäžäžèŽ | å®ä¹äžæ¬¡ïŒææå®¢æ·ç«¯åæ¥çæ |
| ä¿®æ¹æ¥å£ | éç¥10䞪å¢éïŒå¯èœæŒæ | æ¹å®ä¹ïŒéæ°çæïŒPRèªåšéç¥ |
| æ¥æ¥å£ææ¡£ | æŸåå¢éèŠïŒææ¡£å¯èœè¿æ¶ | gen项ç®éçŽæ¥çïŒæ°žè¿ææ° |
QïŒçæä»£ç äŒäžäŒææ§èœé®é¢ïŒ
äžäŒã
çæç代ç å°±æ¯æ®éçHTTP客æ·ç«¯ä»£ç ïŒæ²¡æé¢å€çæœè±¡å±ã
æ§èœåæåçäžæ ·ã
// çæç代ç
func (c *Client) GetUser(ctx context.Context, req *GetUserRequest) (*User, error) {
path := fmt.Sprintf("/api/v1/users/%s", req.UserID)
var resp GetUserResponse
err := c.doRequest(ctx, http.MethodGet, path, nil, &resp)
// ...
}
// æåç代ç
func GetUser(userID string) (*User, error) {
url := fmt.Sprintf("http://user-service/api/v1/users/%s", userID)
req, _ := http.NewRequest("GET", url, nil)
// ...
}
// æ¬èŽšäžæ¯äžæš¡äžæ ·çHTTPè°çšïŒé¶æ§èœæèïŒ
QïŒåŠææ¥å£å®ä¹éäºæä¹åïŒ
æ¹å®ä¹ïŒéæ°çæã
è¿æ¯æ¹10䞪æå¡çæå客æ·ç«¯ä»£ç èŠç®ååŸå€ã
# 1. ä¿®æ¹æ¥å£å®ä¹
vim services/user/api/get_user.yaml
# 2. æäº€ä»£ç
git add .
git commit -m "fix: correct user_id field name"
git push
# 3. CIèªåšè§Šå
# â çæææè¯èšå®¢æ·ç«¯
# â ååžæ°çæ¬
# â è°ç𿹿¶å°æŽæ°éç¥
# 4. è°çšæ¹å级
go get github.com/company/gen-clients/go/user@latest
# æè
npm update @company/gen-clients-user
QïŒçæç代ç èœèªå®ä¹åïŒ
å¯ä»¥ïŒ
gené¡¹ç®æäŸäºå€ç§èªå®ä¹æ¹åŒïŒ
- èªå®ä¹æš¡æ¿ïŒèŠçé»è®€æš¡æ¿ïŒçæç¬Šåå ¬åžè§èç代ç
- æä»¶æºå¶ïŒåæä»¶æ©å±çæé»èŸ
- é 眮æä»¶ïŒéè¿YAMLé 眮èªå®ä¹è¡äžº
- åå€çé©åïŒçæåèªåšæ§è¡æ ŒåŒåãlintç
# gen-config.yaml
customization:
# èªå®ä¹ä»£ç å€Žéšæ³šé
header_template: |
// Copyright {{ .Year }} GamePlatform Inc.
// Code generated by gen-project. DO NOT EDIT.
# èªå®ä¹å
å
package_name_template: "{{ .ServiceName }}client"
# çæåæ§è¡
post_hooks:
- "gofmt -w ."
- "goimports -w ."
- "go mod tidy"
QïŒåèç³»ç»æä¹éæïŒ
gené¡¹ç®æ¯æå¢éåŒå ¥ïŒ
- æ°æ¥å£çšgen项ç®ïŒæ°æå¡ãæ°æ¥å£ç»äžçšgen项ç®çæ
- èæ¥å£éæ¥è¿ç§»ïŒèæ¥å£ææ¹åšæ¶ïŒè¿ç§»å°gen项ç®
- æ··å䜿çšïŒçæç客æ·ç«¯åæåç客æ·ç«¯å¯ä»¥å ±å
// æ°æ¥å£ïŒçšçæç客æ·ç«¯
user, err := genUserClient.GetUser(ctx, &GetUserRequest{...})
// èæ¥å£ïŒç»§ç»çšæå客æ·ç«¯ïŒææ¶ïŒ
order, err := legacyOrderClient.GetOrder(orderID)
// äžå²çªïŒå¹³æ»è¿ç§»
æäœ³å®è·µæ»ç»
1. æ¥å£è®Ÿè®¡åå
- åœåè§èïŒç»äžäœ¿çšé©Œå³°åœåïŒé¿å äžå线
- çæ¬å·åšè·¯åŸäžïŒ
/api/v1/usersïŒèäžæ¯/api/users?v=1 - ç»äžååºæ ŒåŒïŒ
{code, message, data}äžä»¶å¥ - é误ç å级ïŒ1xxx çšæ·çžå ³ïŒ2xxx 订åçžå ³ïŒ3xxx æ¯ä»çžå ³
2. å¢éåäœè§è
- æ¥å£åæŽèµ°PRïŒä»»äœäººæ¹æ¥å£å®ä¹éœèŠå建PRïŒéç¥çžå ³æ¹
- breaking changeèŠè¯å®¡ïŒå€§çæ¬å级éèŠææ¯å§åäŒè¯å®¡
- ææ¡£å代ç åæ¥ïŒæ¥å£å®ä¹å°±æ¯ææ¡£ïŒäžéèŠé¢å€ç»Žæ€
3. çæ¬ååžè§è
- 客æ·ç«¯ç¬ç«çæ¬ïŒæ¯äžªè¯èšç客æ·ç«¯ç¬ç«åç
- è¯ä¹åçæ¬ïŒäž¥æ ŒéµåŸªSemVer
- changelogèªåšçæïŒä»æ¥å£å®ä¹åæŽèªåšçæchangelog
4. çæ§ååèŠ
# çæ§é
眮
monitoring:
# 客æ·ç«¯çæ¬ååž
metrics:
- client_version_distribution
# è¿æçæ¬åèŠ
alerts:
- name: deprecated_client_usage
condition: client_version < current_version - 3
action: notify_team
å°ç»
[é åŸå»ºè®®ïŒäžäžªç®æŽçæ»ç»åŸïŒå±ç€ºgen项ç®çæ žå¿ä»·åŒïŒç±»åå®å šãæ¥å£äžèŽãåŒåæç]
èŠç¹å顟
- æåHTTP客æ·ç«¯æäžå€§çç¹ïŒéå€å³åšãæ¥å£åæ¥éŸãç±»åäžå®å š
- gen项ç®éè¿ä»£ç çæè§£å³è¿äºé®é¢ïŒ
- èªåšçæåŒºç±»å客æ·ç«¯ïŒçŒè¯ææ ¡éªïŒ - æ¯æå€è¯èšãå€æš¡æ¿ã坿©å±
- 代ç çæåšè®Ÿè®¡åçïŒ
- äžéŽè¡šç€ºïŒè¯èšæ å ³çç±»åç³»ç» - æš¡æ¿åŒæïŒåºäºæš¡æ¿çæåè¯èšä»£ç
- çæ¬ç®¡çèŠååå Œå®¹ïŒ
- ç»è°çšæ¹çè¶³å级æ¶éŽ - æ°èçæ¬å¯ä»¥å ±åè¿æž¡
- æžžæè¡äžå€æå¡åäœïŒ
- äžé®çæå€è¯èšSDKïŒGo/Java/TS/Python/C#ïŒ - CI/CDèªåšåïŒæ¥å£åæŽèªåšéç¥
- äœ¿çšæµçšç®åïŒå®ä¹æ¥å£ â çæä»£ç â åŒå ¥äŸèµ â çŽæ¥è°çš
ææ¯æ¶ææ»ç»åŸ
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
â gen 项ç®å
šæ¯åŸ â
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ€
â â
â âââââââââââââââ âââââââââââââââ âââââââââââââââ â
â â æ¥å£å®ä¹ ââââââ¶â 代ç çæ ââââââ¶â 客æ·ç«¯SDK â â
â â YAML/Proto â â æš¡æ¿åŒæ â â Go/Java/TS â â
â âââââââââââââââ âââââââââââââââ âââââââââââââââ â
â â â â â
â ⌠⌠⌠â
â âââââââââââââââ âââââââââââââââ âââââââââââââââ â
â â çæ¬ç®¡ç â â æä»¶æ©å± â â CI/CDéæ â â
â â SemVer â â èªå®ä¹æš¡æ¿ â â èªåšååž â â
â âââââââââââââââ âââââââââââââââ âââââââââââââââ â
â â
â ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ â
â â
â æ žå¿ä»·åŒïŒ â
â ð ç±»åå®å
š ð æ¥å£äžèŽæ§ â¡ åŒåæç ð è·šè¯è𿝿 â
â â
âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
ååšæå
埮æå¡æ¶æäžïŒè·šæå¡è°çšæ¯ç»äžåŒçè¯é¢ã
gen项ç®äžæ¯ä»ä¹é»ç§æïŒåªæ¯äžäžªæéå€å³åšèªåšåçå·¥å ·ã
äœæ£æ¯è¿ç§"æç®åçäºæ å对"çå·¥å ·ïŒåŸåŸèœåžŠæ¥æå€§çä»·åŒã
ææ¯éå对æ¯
| æ¹æ¡ | äŒç¹ | çŒºç¹ | éçšåºæ¯ |
|---|---|---|---|
| æåHTTP客æ·ç«¯ | çµæŽ»ïŒæ åŠä¹ ææ¬ | éå€å³åšïŒå®¹æåºé | å°å¢éïŒæ¥å£å° |
| gen项ç®ïŒä»£ç çæïŒ | ç±»åå®å šïŒèªåšåæ¥ | éèŠç»Žæ€å®ä¹æä»¶ | äžå€§å¢éïŒæ¥å£å€ |
| gRPC | 髿§èœïŒåŒºç±»å | åŠä¹ ææ¬é«ïŒè°è¯éŸ | å éšæå¡è°çš |
| GraphQL | çµæŽ»æ¥è¯¢ïŒèªæè¿° | å€æåºŠé«ïŒçŒåéŸ | APIçœå ³å± |
è¿é¶ïŒé«çº§ç¹æ§äžå®å¶
èªåšææ¡£çæ
æ¥å£å®ä¹äžä» èœçæä»£ç ïŒè¿èœèªåšçæææ¡£ïŒ
# gen-config.yaml
documentation:
enabled: true
formats:
- swagger # OpenAPI 3.0 æ ŒåŒ
- markdown # å¯è¯»æ§æŽå¥œçMarkdown
- html # çŸè§çHTMLææ¡£ç«
output:
swagger: ./docs/api/openapi.yaml
markdown: ./docs/api/README.md
html: ./docs/api/html/
# çšæ·æå¡ API
## GetUser - è·åçšæ·ä¿¡æ¯
**æ¥å£è·¯åŸ**: `GET /api/v1/users/{user_id}`
**讀è¯èŠæ±**: éèŠBearer Token
### 请æ±åæ°
<table>
<thead><tr>
<th>åæ°å</th>
<th>äœçœ®</th>
<th>ç±»å</th>
<th>å¿
å¡«</th>
<th>æè¿°</th>
</tr></thead><tbody>
<tr>
<td>user_id</td>
<td>path</td>
<td>string</td>
<td>æ¯</td>
<td>çšæ·IDïŒ16äœåæ¯æ°å</td>
</tr>
<tr>
<td>fields</td>
<td>query</td>
<td>string[]</td>
<td>åŠ</td>
<td>éèŠè¿åçåæ®µå衚</td>
</tr>
</tbody></table>
### ååºç€ºäŸ
**æåååº** (200 OK):
json
{ "code": 0, "message": "success", "data": { "user_id": "abc123def456ghi7", "nickname": "æžžæèŸŸäºº", "avatar": "https://cdn.example.com/avatar/xxx.png", "level": 42, "vip_expire_time": "2026-12-31T23:59:59Z" } }
**é误ååº**:
<table>
<thead><tr>
<th>é误ç </th>
<th>诎æ</th>
</tr></thead><tbody>
<tr>
<td>1001</td>
<td>çšæ·äžååš</td>
</tr>
<tr>
<td>1002</td>
<td>çšæ·å·²è¢«çŠçš</td>
</tr>
</tbody></table>
Mockæå¡åš
åŒåé¶æ®µïŒgen项ç®å¯ä»¥èªåšå¯åšMockæå¡åšïŒ
# å¯åšMockæå¡åš
gen mock --port 8080
# èŸåºïŒ
# Mock server running at http://localhost:8080
# Loaded 45 API definitions from services/
# æ¥å£å®ä¹äžå¯ä»¥æå®mockæ°æ®
response:
mock:
user_id: "mock_user_{{uuid}}"
nickname: "{{name}}"
avatar: "https://cdn.example.com/avatar/{{uuid}}.png"
level: "{{integer 1 100}}"
å端å¢éäžéèŠçå端åŒå宿ïŒçŽæ¥çšMockæå¡åšèè°ã
æ§èœäŒåïŒä»£ç çŒå
gen项ç®äŒçŒåçæç»æïŒåªéæ°çææååçéšåïŒ
// çŒåæºå¶
type GeneratorCache struct {
// æ¥å£å®ä¹çhash â çæä»£ç çæ å°
cache map[string]*GeneratedCode
// æä»¶ä¿®æ¹æ¶éŽ
mtimes map[string]time.Time
}
func (g *Generator) Generate(service string) error {
// 1. è®¡ç®æ¥å£å®ä¹çhash
hash := g.calculateHash(service)
// 2. æ£æ¥çŒå
if cached, ok := g.cache[hash]; ok {
// 3. æ£æ¥æºæä»¶æ¯åŠåå
if !g.sourceChanged(service) {
return nil // çŽæ¥äœ¿çšçŒå
}
}
// 4. éæ°çæ
return g.generateFromScratch(service)
}
| åºæ¯ | æ çŒå | æçŒå |
|---|---|---|
| éŠæ¬¡çæïŒ50䞪æ¥å£ïŒ | 2.3ç§ | 2.3ç§ |
| ä¿®æ¹1䞪æ¥å£åéæ°çæ | 2.1ç§ | 0.05ç§ |
| æ ååéæ°çæ | 2.1ç§ | 0.01ç§ |
çæä»£ç 莚éä¿è¯
gen项ç®å 眮äºä»£ç èŽšéæ£æ¥ïŒ
# gen-config.yaml
quality:
# çæçGo代ç èªåšè¿è¡golint
lint:
go: golint
java: checkstyle
typescript: eslint
# èªåšæ ŒåŒå
format:
go: gofmt
java: google-java-format
typescript: prettier
# åå
æµè¯çæ
tests:
enabled: true
coverage_target: 80%
// Code generated by gen-project. DO NOT EDIT.
func TestGetUser(t *testing.T) {
tests := []struct {
name string
request *GetUserRequest
wantErr bool
errCode int
}{
{
name: "æ£åžžè¯·æ±",
request: &GetUserRequest{
UserID: "abc123def456ghi7",
},
wantErr: false,
},
{
name: "çšæ·ID䞺空",
request: &GetUserRequest{
UserID: "",
},
wantErr: true,
errCode: 400,
},
{
name: "çšæ·IDæ ŒåŒé误",
request: &GetUserRequest{
UserID: "invalid!",
},
wantErr: true,
errCode: 400,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// ...
})
}
}
å€ç¯å¢é 眮
gené¡¹ç®æ¯æå€ç¯å¢é 眮ïŒ
# gen-config.yaml
environments:
development:
base_url: "http://localhost:8080"
timeout: 30s
retry: 3
staging:
base_url: "https://api-staging.example.com"
timeout: 10s
retry: 2
production:
base_url: "https://api.example.com"
timeout: 5s
retry: 1
circuit_breaker:
enabled: true
threshold: 50%
timeout: 30s
// 䜿çšäžåç¯å¢ç客æ·ç«¯
devClient := userclient.New(userclient.Config{
Environment: "development",
})
prodClient := userclient.New(userclient.Config{
Environment: "production",
})
åžžè§é·é±äžé¿åæå
é·é±1ïŒè¿åºŠäŸèµçæä»£ç
// â é误ïŒåšè°çšå€åäžå¡é»èŸ
user, err := userClient.GetUser(ctx, &GetUserRequest{UserID: userID})
if user.Level < 10 {
return errors.New("ç级倪äœ")
}
if user.VIPExpireTime.Before(time.Now()) {
return errors.New("VIPå·²è¿æ")
}
// â
æ£ç¡®ïŒå°è£
æé¢åæå¡
func (s *UserService) CanAccessPremiumFeature(userID string) error {
user, err := s.userClient.GetUser(ctx, &GetUserRequest{UserID: userID})
if err != nil {
return err
}
return s.validatePremiumAccess(user)
}
é·é±2ïŒå¿œç¥ååå Œå®¹æ§
# â
æ£ç¡®çåæ³
changes:
- type: add_optional_field # æ·»å å¯éåæ®µ
field: bio
impact: minor # å°çæ¬å级
breaking: false # äžç Žåå
Œå®¹æ§
- type: change_field_type # ä¿®æ¹å段类å
field: user_id
from: string
to: int64
impact: major # å€§çæ¬å级
breaking: true # ç Žåå
Œå®¹æ§
migration_guide: docs/migration/v1-to-v2.md
é·é±3ïŒäžè®Ÿçœ®åççè¶ æ¶
# services/user/api/get_user.yaml
name: GetUser
timeout:
default: 5s # é»è®€è¶
æ¶
maximum: 10s # æå€§è¶
æ¶
retry:
count: 2 # éè¯æ¬¡æ°
backoff: exponential # ææ°éé¿
é·é±4ïŒçæä»£ç çæ¬æ··ä¹±
# çæ¬çºŠææä»¶ versions.yaml
clients:
user-client:
minimum: 1.2.0
recommended: 1.3.0
order-client:
minimum: 2.0.0
recommended: 2.1.0
# CIäžæ£æ¥çæ¬
- name: Check client versions
run: gen check-versions --enforce
æªæ¥å±æ
gen项ç®è¿åšæç»æŒè¿äžïŒè®¡åäžçç¹æ§å æ¬ïŒ
1. AIèŸ å©æ¥å£è®Ÿè®¡
æ ¹æ®èªç¶è¯èšæè¿°èªåšçææ¥å£å®ä¹ïŒ
gen ai-design "éèŠäžäžªçšæ·ç»åœæ¥å£ïŒæ¯æææºå·åé®ç®±ç»åœïŒè¿åJWT token"
2. èªåšæ§èœäŒå
åæè°çšæš¡åŒïŒèªåšçææ¹å€çæ¥å£ïŒ
# åå§ïŒå䞪è·å
GET /api/v1/users/{user_id}
# èªåšçæïŒæ¹éè·å
POST /api/v1/users/batch
{
"user_ids": ["id1", "id2", "id3"]
}
3. æºèœé误å€ç
æ ¹æ®å岿°æ®ïŒèªåšçæéè¯åé级çç¥ïŒ
// èªåšçæçæºèœå®¢æ·ç«¯
func (c *Client) GetUser(ctx context.Context, req *GetUserRequest) (*User, error) {
// èªåšéè¯ïŒæ ¹æ®å岿åçåšæè°æŽ
return c.getWithSmartRetry(ctx, req)
}
4. è·šæå¡éŸè·¯è¿œèžª
èªåšæ³šå ¥è¿œèžªä¿¡æ¯ïŒ
// çæç代ç èªåšå
å«è¿œèžª
func (c *Client) GetUser(ctx context.Context, req *GetUserRequest) (*User, error) {
// èªåšæ³šå
¥ trace ID
ctx = trace.Inject(ctx)
// ...
}
ð¬ è¯è®º (0)