feat(api): 支持 gRPC 用户服务集成
- 更新路由以支持 gRPC 服务器地址配置 - 修改 YMTUsersHandler 以使用 gRPC 客户端进行用户数据查询 - 添加 gRPC 连接失败的降级处理逻辑 - 引入 gRPC 生成的代码以支持用户服务调用
This commit is contained in:
parent
c02f051cb8
commit
fde87fde96
|
|
@ -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 配置,自动维护连接健康
|
||||||
|
|
||||||
|
|
@ -77,7 +77,14 @@ func main() {
|
||||||
} else {
|
} else {
|
||||||
resellerDB = marketing // fallback
|
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 {
|
addr := ":" + func() string {
|
||||||
s := cfg.Port
|
s := cfg.Port
|
||||||
if s == "" {
|
if s == "" {
|
||||||
|
|
|
||||||
|
|
@ -21,3 +21,6 @@ require (
|
||||||
golang.org/x/net v0.21.0 // indirect
|
golang.org/x/net v0.21.0 // indirect
|
||||||
golang.org/x/text v0.14.0 // indirect
|
golang.org/x/text v0.14.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 使用本地 grpc 生成的代码
|
||||||
|
replace grpc/user/userv1 => ../grpc/user/userv1
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import (
|
||||||
"os"
|
"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 := http.NewServeMux()
|
||||||
mux.Handle("/api/templates", withAccess(withTrace(TemplatesHandler(metaDB, marketingDB))))
|
mux.Handle("/api/templates", withAccess(withTrace(TemplatesHandler(metaDB, marketingDB))))
|
||||||
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/resellers/", withAccess(withTrace(ResellersHandler(resellerDB))))
|
||||||
mux.Handle("/api/plans", withAccess(withTrace(PlansHandler(marketingDB))))
|
mux.Handle("/api/plans", withAccess(withTrace(PlansHandler(marketingDB))))
|
||||||
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(grpcAddr))))
|
||||||
mux.Handle("/api/ymt/users/", withAccess(withTrace(YMTUsersHandler(ymtDB))))
|
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/merchants/", withAccess(withTrace(YMTMerchantsHandler(ymtDB))))
|
mux.Handle("/api/ymt/merchants/", withAccess(withTrace(YMTMerchantsHandler(ymtDB))))
|
||||||
mux.Handle("/api/ymt/activities", withAccess(withTrace(YMTActivitiesHandler(ymtDB))))
|
mux.Handle("/api/ymt/activities", withAccess(withTrace(YMTActivitiesHandler(ymtDB))))
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,35 @@
|
||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"server/internal/grpc"
|
||||||
)
|
)
|
||||||
|
|
||||||
type YMTUsersAPI struct {
|
type YMTUsersAPI struct {
|
||||||
ymt *sql.DB
|
grpcClient *grpc.UserClient
|
||||||
}
|
}
|
||||||
|
|
||||||
func YMTUsersHandler(ymt *sql.DB) http.Handler {
|
func YMTUsersHandler(grpcAddr string) http.Handler {
|
||||||
api := &YMTUsersAPI{ymt: ymt}
|
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) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
p := strings.TrimPrefix(r.URL.Path, "/api/ymt/users")
|
p := strings.TrimPrefix(r.URL.Path, "/api/ymt/users")
|
||||||
if r.Method == http.MethodGet && p == "" {
|
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) {
|
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")
|
limitStr := r.URL.Query().Get("limit")
|
||||||
limit := 2000
|
limit := 2000
|
||||||
if limitStr != "" {
|
if limitStr != "" {
|
||||||
|
|
@ -32,34 +53,45 @@ func (a *YMTUsersAPI) list(w http.ResponseWriter, r *http.Request) {
|
||||||
limit = n
|
limit = n
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sql1 := "SELECT id, name FROM user WHERE id IS NOT NULL"
|
|
||||||
args := []interface{}{}
|
// 获取搜索关键词(如果有)
|
||||||
sql1 += " ORDER BY id ASC LIMIT ?"
|
keyword := r.URL.Query().Get("q")
|
||||||
args = append(args, limit)
|
|
||||||
rows, err := a.ymt.Query(sql1, args...)
|
// 创建带超时的 context
|
||||||
|
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
// 调用 gRPC 服务
|
||||||
|
resp, err := a.grpcClient.SimpleListAllUser(ctx, keyword)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fail(w, r, http.StatusInternalServerError, err.Error())
|
fail(w, r, http.StatusInternalServerError, fmt.Sprintf("gRPC call failed: %v", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer rows.Close()
|
|
||||||
|
|
||||||
|
// 转换响应格式
|
||||||
out := []map[string]interface{}{}
|
out := []map[string]interface{}{}
|
||||||
for rows.Next() {
|
for i, user := range resp.List {
|
||||||
var id sql.NullInt64
|
if i >= limit {
|
||||||
var name sql.NullString
|
break
|
||||||
if err := rows.Scan(&id, &name); err != nil {
|
}
|
||||||
|
if user == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if !id.Valid {
|
// 构建显示名称:realname(id)或 username(id)
|
||||||
continue
|
displayName := user.Realname
|
||||||
|
if displayName == "" {
|
||||||
|
displayName = user.Username
|
||||||
}
|
}
|
||||||
n := strings.TrimSpace(name.String)
|
if displayName == "" {
|
||||||
if n == "" {
|
displayName = strconv.FormatInt(int64(user.Id), 10)
|
||||||
n = strconv.FormatInt(id.Int64, 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)
|
ok(w, r, out)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ import (
|
||||||
|
|
||||||
// 使用生成的 proto 代码
|
// 使用生成的 proto 代码
|
||||||
// 注意:需要先运行 cd grpc && make generate 生成代码
|
// 注意:需要先运行 cd grpc && make generate 生成代码
|
||||||
userv1 "server/grpc/user/userv1"
|
userv1 "grpc/user/userv1"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -90,4 +90,3 @@ func (c *UserClient) Close() error {
|
||||||
func (c *UserClient) GetConn() *grpc.ClientConn {
|
func (c *UserClient) GetConn() *grpc.ClientConn {
|
||||||
return c.conn
|
return c.conn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue