Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
59 changes: 59 additions & 0 deletions PR_DESCRIPTION_ISSUE_86.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
## Title
fix(handlergen): prevent panic when generating controller methods with incomplete interface metadata

## Related Issue
- Closes #86

## Background
When running the controller/handler generator (`cmd/handlergen`), the process could panic with:

`panic: runtime error: invalid memory address or nil pointer dereference`

This happened when the target handler interface in `internal/api/<name>/handler.go` did not satisfy generator assumptions (for example: missing method list metadata or missing required method annotations/comments).

## Root Cause
`cmd/handlergen/main.go` made unsafe assumptions:

1. `interfaceType.Methods` is always non-nil.
2. Every method always contains at least 3 leading comments:
- summary line
- `@Tags`
- `@Router`
3. The first comment always contains the method name for split-based parsing.

These assumptions caused unsafe dereference/index access in edge cases.

## What Changed
Updated `cmd/handlergen/main.go` to make generation defensive and non-crashing:

1. Added a nil guard for `interfaceType.Methods`.
- If missing, skip that interface and log a clear message.
2. Added annotation length guard for each method.
- Require at least 3 leading comments.
- If not enough, skip method and log a precise warning with method name and actual count.
3. Added safe fallback for summary/description text generation.
- If method name split cannot extract suffix, fallback to method name instead of indexing blindly.

## Behavior After Fix
- Generator no longer panics on incomplete handler definitions.
- Invalid/incomplete methods are skipped with readable logs.
- Valid methods continue generating `func_*.go` as before.

## Verification
Executed locally:

1. `go test ./cmd/handlergen/...`
- Result: pass (compile check, no test files)
2. `go test ./...`
- Existing unrelated failure remains in `pkg/mail`:
- `TestSend` failed due to external SMTP auth (`535 Error: authentication failed`)
- No new failures introduced by this change in `cmd/handlergen`.

## Risk Assessment
- Low risk.
- Change is isolated to generator command path.
- Runtime API/business logic is not modified.

## Rollback Plan
If any unexpected generation behavior appears, revert this commit to restore previous generator behavior.

4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@
1. 支持 生成数据表 CURD、控制器方法 等代码生成器
1. 支持 [cron](https://github.com/jakecoffman/cron) 定时任务,在后台可界面配置
1. 支持 [websocket](https://github.com/gorilla/websocket) 实时通讯,在后台有界面演示
1. 支持 [gRPC](https://google.golang.org/grpc) 服务,包含健康检查和示例服务
1. 支持 web 界面,使用的 [Light Year Admin 模板](https://gitee.com/yinqi/Light-Year-Admin-Using-Iframe)
说明:
- 当前仓库主干功能以 HTTP/REST、GraphQL、WebSocket 为主,暂未内置 gRPC 服务端或 `.proto` 定义。
- 如需在项目中接入 gRPC,请按业务需要自行扩展(例如新增 `cmd/grpc` 服务入口与对应协议定义)。


## 文档索引(可加入交流群)
Expand Down
39 changes: 39 additions & 0 deletions api/proto/service.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
syntax = "proto3";

package api;

option go_package = "github.com/xinliangnote/go-gin-api/internal/grpc/pb";

// HealthService 健康检查服务
service HealthService {
// Check 健康检查
rpc Check(HealthCheckRequest) returns (HealthCheckResponse);
}

message HealthCheckRequest {
string service = 1;
}

message HealthCheckResponse {
enum ServingStatus {
UNKNOWN = 0;
SERVING = 1;
NOT_SERVING = 2;
}
ServingStatus status = 1;
}

// HelloService 示例 gRPC 服务
service HelloService {
// SayHello 简单的 unary RPC
rpc SayHello(HelloRequest) returns (HelloResponse);
}

message HelloRequest {
string name = 1;
}

message HelloResponse {
string message = 1;
string trace_id = 2;
}
16 changes: 14 additions & 2 deletions cmd/handlergen/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,20 @@ func main() {
if interfaceType, ok = typeSpec.Type.(*dst.InterfaceType); !ok {
continue
}
if interfaceType.Methods == nil {
log.Printf("skip interface %s: no method definitions", typeSpec.Name.Name)
continue
}

for _, v := range interfaceType.Methods.List {
if len(v.Names) > 0 {
if v.Names[0].String() == "i" {
continue
}
if len(v.Decorations().Start.All()) < 3 {
log.Printf("skip method %s: need 3 comments (summary/tags/router), got %d", v.Names[0].String(), len(v.Decorations().Start.All()))
continue
}

filepath := "./internal/api/" + handlerName
filename := fmt.Sprintf("%s/func_%s.go", filepath, strings.ToLower(v.Names[0].String()))
Expand Down Expand Up @@ -85,8 +93,12 @@ func main() {
funcContent += fmt.Sprintf("%s\n", v.Decorations().Start.All()[0])

nameArr := strings.Split(v.Decorations().Start.All()[0], v.Names[0].String())
funcContent += fmt.Sprintf("// @Summary%s \n", nameArr[1])
funcContent += fmt.Sprintf("// @Description%s \n", nameArr[1])
summaryDesc := " " + v.Names[0].String()
if len(nameArr) > 1 {
summaryDesc = nameArr[1]
}
funcContent += fmt.Sprintf("// @Summary%s \n", summaryDesc)
funcContent += fmt.Sprintf("// @Description%s \n", summaryDesc)
// Tags
funcContent += fmt.Sprintf("%s \n", v.Decorations().Start.All()[1])
funcContent += fmt.Sprintf("// @Accept application/x-www-form-urlencoded \n")
Expand Down
3 changes: 3 additions & 0 deletions configs/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ const (
// ProjectPort 项目端口
ProjectPort = ":9999"

// ProjectGRPCPort gRPC 服务端口
ProjectGRPCPort = ":9998"

// ProjectAccessLogFile 项目访问日志存放文件
ProjectAccessLogFile = "./logs/" + ProjectName + "-access.log"

Expand Down
4 changes: 4 additions & 0 deletions en.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ Features:
1. Standard RESTful API return value
1. CURD code generator , controller generator, etc.
1. Web interface, supported by [Light Year Admin template](https://gitee.com/yinqi/Light-Year-Admin-Using-Iframe)
Notes:
- The current repository focuses on HTTP/REST, GraphQL, and WebSocket capabilities.
- Built-in gRPC server support and `.proto` definitions are not included at this time.
- If you need gRPC, add it as an extension module for your own service boundaries.



Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ require (
go.uber.org/zap v1.19.1
golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11
golang.org/x/tools v0.1.7
google.golang.org/grpc v1.46.0
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
gopkg.in/natefinch/lumberjack.v2 v2.0.0
Expand Down
8 changes: 8 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,11 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
Expand All @@ -119,6 +123,7 @@ github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5y
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y=
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
Expand Down Expand Up @@ -863,6 +868,7 @@ google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKr
google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w=
google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71 h1:z+ErRPu0+KS02Td3fOAgdX+lnPDh/VyaABEJPD4JRQs=
google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
Expand All @@ -889,6 +895,8 @@ google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQ
google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/grpc v1.46.0 h1:oCjezcn6g6A75TGoKYBPgKmVBLexhYLM6MebdrPApP8=
google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
Expand Down
56 changes: 56 additions & 0 deletions internal/grpc/interceptor/interceptor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package interceptor

import (
"context"
"fmt"
"runtime/debug"
"time"

"go.uber.org/zap"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)

// UnaryLogInterceptor 日志拦截器,记录每次 unary RPC 调用
func UnaryLogInterceptor(logger *zap.Logger) grpc.UnaryServerInterceptor {
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
ts := time.Now()

resp, err := handler(ctx, req)

cost := time.Since(ts)
fields := []zap.Field{
zap.String("method", info.FullMethod),
zap.Duration("cost", cost),
}

if err != nil {
fields = append(fields, zap.Error(err))
logger.Warn("grpc-request", fields...)
} else {
logger.Info("grpc-request", fields...)
}

return resp, err
}
}

// UnaryRecoveryInterceptor panic 恢复拦截器
func UnaryRecoveryInterceptor(logger *zap.Logger) grpc.UnaryServerInterceptor {
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
defer func() {
if r := recover(); r != nil {
stackInfo := string(debug.Stack())
logger.Error("grpc panic recovered",
zap.String("method", info.FullMethod),
zap.String("panic", fmt.Sprintf("%+v", r)),
zap.String("stack", stackInfo),
)
err = status.Errorf(codes.Internal, "internal server error")
}
}()

return handler(ctx, req)
}
}
37 changes: 37 additions & 0 deletions internal/grpc/pb/service.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading