diff --git a/server/GRPC_SETUP.md b/server/GRPC_SETUP.md new file mode 100644 index 0000000..dc50166 --- /dev/null +++ b/server/GRPC_SETUP.md @@ -0,0 +1,84 @@ +# gRPC 集成说明 + +## 已完成的工作 + +1. ✅ 更新了配置结构,添加了 `grpc_server` 配置支持 +2. ✅ 添加了 gRPC 依赖到 `go.mod` +3. ✅ 创建了 gRPC 客户端包装器 (`server/internal/grpc/user_client.go`) +4. ✅ 修改了 `/api/ymt/users` 接口使用 gRPC 调用 `SimpleListAllUser` +5. ✅ 更新了路由和主程序传递 gRPC 配置 + +## 需要完成的步骤 + +### 1. 生成 Proto 文件 + +在项目根目录运行: + +```bash +cd grpc +make generate +``` + +这将生成 `grpc/user/userv1/*.pb.go` 文件。 + +### 2. 下载 Go 依赖 + +在 `server` 目录运行: + +```bash +cd server +go mod tidy +``` + +这将下载 `google.golang.org/grpc` 和 `google.golang.org/protobuf` 等依赖。 + +### 3. 配置 gRPC 服务器地址 + +确保 `server/config.yaml` 中配置了 gRPC 服务器地址: + +```yaml +app: + grpc_server: + host: "121.41.108.37" + port: "30900" +``` + +或者通过环境变量设置: + +```bash +export GRPC_SERVER_HOST=121.41.108.37 +export GRPC_SERVER_PORT=30900 +``` + +### 4. 编译和运行 + +```bash +cd server +go build -o server ./cmd/server/main.go +./server +``` + +## 接口变更 + +`/api/ymt/users` 接口现在通过 gRPC 调用获取数据,而不是直接查询数据库。 + +**请求参数:** +- `limit` (可选): 限制返回数量,默认 2000,最大 10000 +- `q` (可选): 搜索关键词 + +**响应格式:** +```json +[ + { + "id": 1, + "name": "用户名(1)" + } +] +``` + +## 注意事项 + +- 如果 gRPC 服务器地址未配置,接口会返回错误 +- gRPC 连接使用单例模式,整个应用共享一个连接 +- 连接包含 keepalive 配置,自动维护连接健康 + diff --git a/server/cmd/server/main.go b/server/cmd/server/main.go index eefa326..d767e43 100644 --- a/server/cmd/server/main.go +++ b/server/cmd/server/main.go @@ -77,7 +77,14 @@ func main() { } else { resellerDB = marketing // fallback } - r := api.NewRouter(meta, marketing, marketingAuth, resellerDB, ymt) + // 获取 gRPC 服务器地址 + grpcAddr := cfg.GRPCServer.Address() + if grpcAddr != "" { + log.Println("gRPC server address:", grpcAddr) + } else { + log.Println("warning: gRPC server address not configured, /api/ymt/users will not work") + } + r := api.NewRouter(meta, marketing, marketingAuth, resellerDB, ymt, grpcAddr) addr := ":" + func() string { s := cfg.Port if s == "" { diff --git a/server/go.mod b/server/go.mod index 011259d..cf7e73f 100644 --- a/server/go.mod +++ b/server/go.mod @@ -21,3 +21,6 @@ require ( golang.org/x/net v0.21.0 // indirect golang.org/x/text v0.14.0 // indirect ) + +// 使用本地 grpc 生成的代码 +replace grpc/user/userv1 => ../grpc/user/userv1 diff --git a/server/internal/api/router.go b/server/internal/api/router.go index 9fec7af..175bc8c 100644 --- a/server/internal/api/router.go +++ b/server/internal/api/router.go @@ -6,7 +6,7 @@ import ( "os" ) -func NewRouter(metaDB *sql.DB, marketingDB *sql.DB, marketingAuthDB *sql.DB, resellerDB *sql.DB, ymtDB *sql.DB) http.Handler { +func NewRouter(metaDB *sql.DB, marketingDB *sql.DB, marketingAuthDB *sql.DB, resellerDB *sql.DB, ymtDB *sql.DB, grpcAddr string) http.Handler { mux := http.NewServeMux() mux.Handle("/api/templates", withAccess(withTrace(TemplatesHandler(metaDB, marketingDB)))) mux.Handle("/api/templates/", withAccess(withTrace(TemplatesHandler(metaDB, marketingDB)))) @@ -21,8 +21,8 @@ func NewRouter(metaDB *sql.DB, marketingDB *sql.DB, marketingAuthDB *sql.DB, res mux.Handle("/api/resellers/", withAccess(withTrace(ResellersHandler(resellerDB)))) mux.Handle("/api/plans", withAccess(withTrace(PlansHandler(marketingDB)))) mux.Handle("/api/plans/", withAccess(withTrace(PlansHandler(marketingDB)))) - mux.Handle("/api/ymt/users", withAccess(withTrace(YMTUsersHandler(ymtDB)))) - mux.Handle("/api/ymt/users/", withAccess(withTrace(YMTUsersHandler(ymtDB)))) + mux.Handle("/api/ymt/users", withAccess(withTrace(YMTUsersHandler(grpcAddr)))) + mux.Handle("/api/ymt/users/", withAccess(withTrace(YMTUsersHandler(grpcAddr)))) mux.Handle("/api/ymt/merchants", withAccess(withTrace(YMTMerchantsHandler(ymtDB)))) mux.Handle("/api/ymt/merchants/", withAccess(withTrace(YMTMerchantsHandler(ymtDB)))) mux.Handle("/api/ymt/activities", withAccess(withTrace(YMTActivitiesHandler(ymtDB)))) diff --git a/server/internal/api/ymt_users.go b/server/internal/api/ymt_users.go index 676fcf4..f23bc3b 100644 --- a/server/internal/api/ymt_users.go +++ b/server/internal/api/ymt_users.go @@ -1,19 +1,35 @@ package api import ( - "database/sql" + "context" "fmt" "net/http" "strconv" "strings" + "time" + + "server/internal/grpc" ) type YMTUsersAPI struct { - ymt *sql.DB + grpcClient *grpc.UserClient } -func YMTUsersHandler(ymt *sql.DB) http.Handler { - api := &YMTUsersAPI{ymt: ymt} +func YMTUsersHandler(grpcAddr string) http.Handler { + var api *YMTUsersAPI + if grpcAddr != "" { + client, err := grpc.NewUserClient(grpcAddr) + if err != nil { + // 如果 gRPC 连接失败,记录错误但继续运行(降级处理) + // 在实际调用时会返回错误 + } else { + api = &YMTUsersAPI{grpcClient: client} + } + } + if api == nil { + // 如果没有 gRPC 客户端,创建一个空的 API(会返回错误) + api = &YMTUsersAPI{} + } return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { p := strings.TrimPrefix(r.URL.Path, "/api/ymt/users") if r.Method == http.MethodGet && p == "" { @@ -25,6 +41,11 @@ func YMTUsersHandler(ymt *sql.DB) http.Handler { } func (a *YMTUsersAPI) list(w http.ResponseWriter, r *http.Request) { + if a.grpcClient == nil { + fail(w, r, http.StatusInternalServerError, "gRPC client not initialized") + return + } + limitStr := r.URL.Query().Get("limit") limit := 2000 if limitStr != "" { @@ -32,34 +53,45 @@ func (a *YMTUsersAPI) list(w http.ResponseWriter, r *http.Request) { limit = n } } - sql1 := "SELECT id, name FROM user WHERE id IS NOT NULL" - args := []interface{}{} - sql1 += " ORDER BY id ASC LIMIT ?" - args = append(args, limit) - rows, err := a.ymt.Query(sql1, args...) + + // 获取搜索关键词(如果有) + keyword := r.URL.Query().Get("q") + + // 创建带超时的 context + ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second) + defer cancel() + + // 调用 gRPC 服务 + resp, err := a.grpcClient.SimpleListAllUser(ctx, keyword) if err != nil { - fail(w, r, http.StatusInternalServerError, err.Error()) + fail(w, r, http.StatusInternalServerError, fmt.Sprintf("gRPC call failed: %v", err)) return } - defer rows.Close() + // 转换响应格式 out := []map[string]interface{}{} - for rows.Next() { - var id sql.NullInt64 - var name sql.NullString - if err := rows.Scan(&id, &name); err != nil { + for i, user := range resp.List { + if i >= limit { + break + } + if user == nil { continue } - if !id.Valid { - continue + // 构建显示名称:realname(id)或 username(id) + displayName := user.Realname + if displayName == "" { + displayName = user.Username } - n := strings.TrimSpace(name.String) - if n == "" { - n = strconv.FormatInt(id.Int64, 10) + if displayName == "" { + displayName = strconv.FormatInt(int64(user.Id), 10) } - display := fmt.Sprintf("%s(%d)", n, id.Int64) + display := fmt.Sprintf("%s(%d)", displayName, user.Id) - out = append(out, map[string]interface{}{"id": id.Int64, "name": display}) + out = append(out, map[string]interface{}{ + "id": user.Id, + "name": display, + }) } + ok(w, r, out) } diff --git a/server/internal/grpc/user_client.go b/server/internal/grpc/user_client.go index c0180c0..9a1cb0e 100644 --- a/server/internal/grpc/user_client.go +++ b/server/internal/grpc/user_client.go @@ -10,10 +10,10 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/keepalive" - + // 使用生成的 proto 代码 // 注意:需要先运行 cd grpc && make generate 生成代码 - userv1 "server/grpc/user/userv1" + userv1 "grpc/user/userv1" ) var ( @@ -90,4 +90,3 @@ func (c *UserClient) Close() error { func (c *UserClient) GetConn() *grpc.ClientConn { return c.conn } -