745 lines
19 KiB
Markdown
745 lines
19 KiB
Markdown
# go-zero 服务实现详解
|
||
|
||
## 🔥 核心服务实现
|
||
|
||
### 1. Gateway API 服务 (HTTP 入口)
|
||
|
||
#### API 定义 (gateway.api)
|
||
|
||
```go
|
||
syntax = "v1"
|
||
|
||
info(
|
||
title: "天远API网关"
|
||
desc: "统一API入口"
|
||
version: "v1.0"
|
||
)
|
||
|
||
type (
|
||
// 登录请求
|
||
LoginReq {
|
||
Username string `json:"username"`
|
||
Password string `json:"password"`
|
||
}
|
||
|
||
LoginResp {
|
||
Token string `json:"token"`
|
||
UserInfo UserInfo `json:"userInfo"`
|
||
}
|
||
|
||
UserInfo {
|
||
UserId int64 `json:"userId"`
|
||
Username string `json:"username"`
|
||
EnterpriseId int64 `json:"enterpriseId"`
|
||
}
|
||
|
||
// 数据查询请求 (你的核心业务)
|
||
DataQueryReq {
|
||
QueryType string `json:"queryType"` // risk/credit/company/data
|
||
Parameters map[string]interface{} `json:"parameters"`
|
||
}
|
||
|
||
DataQueryResp {
|
||
Success bool `json:"success"`
|
||
Data interface{} `json:"data"`
|
||
TransactionId string `json:"transactionId"`
|
||
RemainingBalance float64 `json:"remainingBalance"`
|
||
}
|
||
)
|
||
|
||
@server(
|
||
jwt: Auth
|
||
group: auth
|
||
)
|
||
service gateway-api {
|
||
@handler LoginHandler
|
||
post /api/v1/auth/login (LoginReq) returns (LoginResp)
|
||
|
||
@handler LogoutHandler
|
||
post /api/v1/auth/logout returns ()
|
||
}
|
||
|
||
@server(
|
||
jwt: Auth
|
||
group: data
|
||
middleware: RateLimit,Audit
|
||
)
|
||
service gateway-api {
|
||
@handler RiskAssessmentHandler
|
||
post /api/v1/data/risk-assessment (DataQueryReq) returns (DataQueryResp)
|
||
|
||
@handler CreditCheckHandler
|
||
post /api/v1/data/credit-check (DataQueryReq) returns (DataQueryResp)
|
||
|
||
@handler CompanyInfoHandler
|
||
post /api/v1/data/company-info (DataQueryReq) returns (DataQueryResp)
|
||
|
||
@handler DataQueryHandler
|
||
post /api/v1/data/query (DataQueryReq) returns (DataQueryResp)
|
||
}
|
||
```
|
||
|
||
#### 核心 Logic 实现 (处理复杂调用链)
|
||
|
||
```go
|
||
// api/gateway/internal/logic/data/risklogic.go
|
||
|
||
type RiskLogic struct {
|
||
logx.Logger
|
||
ctx context.Context
|
||
svcCtx *svc.ServiceContext
|
||
}
|
||
|
||
func (l *RiskLogic) RiskAssessment(req *types.DataQueryReq) (resp *types.DataQueryResp, err error) {
|
||
// 获取用户信息 (从JWT中解析)
|
||
userId := ctxdata.GetUidFromCtx(l.ctx)
|
||
|
||
// 🔥 调用数据域RPC进行复杂业务处理
|
||
dataResp, err := l.svcCtx.DataRpc.ProcessDataRequest(l.ctx, &data.ProcessDataRequestReq{
|
||
UserId: userId,
|
||
QueryType: "risk-assessment",
|
||
Parameters: req.Parameters,
|
||
ClientIp: httpx.GetClientIP(l.ctx),
|
||
})
|
||
|
||
if err != nil {
|
||
logx.Errorf("调用数据域RPC失败: %v", err)
|
||
return nil, err
|
||
}
|
||
|
||
return &types.DataQueryResp{
|
||
Success: dataResp.Success,
|
||
Data: dataResp.Data,
|
||
TransactionId: dataResp.TransactionId,
|
||
RemainingBalance: dataResp.RemainingBalance,
|
||
}, nil
|
||
}
|
||
```
|
||
|
||
#### 服务上下文 (包含所有 RPC 客户端)
|
||
|
||
```go
|
||
// api/gateway/internal/svc/servicecontext.go
|
||
|
||
type ServiceContext struct {
|
||
Config config.Config
|
||
|
||
// 🔗 RPC客户端连接
|
||
UserRpc user.User
|
||
DataRpc data.Data
|
||
SecurityRpc security.Security
|
||
BillingRpc billing.Billing
|
||
ProductRpc product.Product
|
||
AuditRpc audit.Audit
|
||
|
||
// 中间件
|
||
RateLimit rest.Middleware
|
||
AuditMiddleware rest.Middleware
|
||
}
|
||
|
||
func NewServiceContext(c config.Config) *ServiceContext {
|
||
return &ServiceContext{
|
||
Config: c,
|
||
|
||
// 初始化RPC客户端
|
||
UserRpc: user.NewUser(zrpc.MustNewClient(c.UserRpc)),
|
||
DataRpc: data.NewData(zrpc.MustNewClient(c.DataRpc)),
|
||
SecurityRpc: security.NewSecurity(zrpc.MustNewClient(c.SecurityRpc)),
|
||
BillingRpc: billing.NewBilling(zrpc.MustNewClient(c.BillingRpc)),
|
||
ProductRpc: product.NewProduct(zrpc.MustNewClient(c.ProductRpc)),
|
||
AuditRpc: audit.NewAudit(zrpc.MustNewClient(c.AuditRpc)),
|
||
|
||
// 初始化中间件
|
||
RateLimit: ratelimit.NewRateLimit(c.RateLimit),
|
||
AuditMiddleware: auditrpc.NewAuditMiddleware(c.Audit),
|
||
}
|
||
}
|
||
```
|
||
|
||
### 2. Data RPC 服务 (核心协调者)
|
||
|
||
#### Proto 定义 (data.proto)
|
||
|
||
```protobuf
|
||
syntax = "proto3";
|
||
|
||
package data;
|
||
|
||
option go_package = "./pb";
|
||
|
||
// 数据处理请求
|
||
message ProcessDataRequestReq {
|
||
int64 user_id = 1;
|
||
string query_type = 2; // risk-assessment/credit-check/company-info/data-query
|
||
map<string, string> parameters = 3;
|
||
string client_ip = 4;
|
||
}
|
||
|
||
message ProcessDataRequestResp {
|
||
bool success = 1;
|
||
string data = 2; // JSON格式的业务数据
|
||
string transaction_id = 3;
|
||
double remaining_balance = 4;
|
||
string error_message = 5;
|
||
}
|
||
|
||
service Data {
|
||
rpc ProcessDataRequest(ProcessDataRequestReq) returns (ProcessDataRequestResp);
|
||
}
|
||
```
|
||
|
||
#### 核心协调逻辑 (你的复杂业务流程)
|
||
|
||
```go
|
||
// rpc/data/internal/logic/orchestrator/dataorchestratorlogic.go
|
||
|
||
type DataOrchestratorLogic struct {
|
||
ctx context.Context
|
||
svcCtx *svc.ServiceContext
|
||
logx.Logger
|
||
}
|
||
|
||
func (l *DataOrchestratorLogic) ProcessDataRequest(in *pb.ProcessDataRequestReq) (*pb.ProcessDataRequestResp, error) {
|
||
startTime := time.Now()
|
||
|
||
// === 第1步:安全验证 ===
|
||
|
||
// 1.1 获取用户企业信息
|
||
userResp, err := l.svcCtx.UserRpc.GetUserInfo(l.ctx, &user.GetUserInfoReq{
|
||
UserId: in.UserId,
|
||
})
|
||
if err != nil {
|
||
return nil, fmt.Errorf("获取用户信息失败: %w", err)
|
||
}
|
||
|
||
// 1.2 IP白名单验证
|
||
_, err = l.svcCtx.SecurityRpc.CheckWhitelist(l.ctx, &security.CheckWhitelistReq{
|
||
EnterpriseId: userResp.EnterpriseId,
|
||
ClientIp: in.ClientIp,
|
||
})
|
||
if err != nil {
|
||
return nil, fmt.Errorf("IP白名单验证失败: %w", err)
|
||
}
|
||
|
||
// 1.3 密钥解密
|
||
decryptResp, err := l.svcCtx.SecurityRpc.DecryptSecret(l.ctx, &security.DecryptSecretReq{
|
||
EnterpriseId: userResp.EnterpriseId,
|
||
EncryptedKey: userResp.EncryptedSecretKey,
|
||
})
|
||
if err != nil {
|
||
return nil, fmt.Errorf("密钥解密失败: %w", err)
|
||
}
|
||
|
||
// === 第2步:权限与产品验证 ===
|
||
|
||
// 2.1 产品权限检查
|
||
productResp, err := l.svcCtx.ProductRpc.CheckProductAccess(l.ctx, &product.CheckProductAccessReq{
|
||
UserId: in.UserId,
|
||
QueryType: in.QueryType,
|
||
SecretKey: decryptResp.SecretKey,
|
||
})
|
||
if err != nil {
|
||
return nil, fmt.Errorf("产品权限检查失败: %w", err)
|
||
}
|
||
|
||
// 2.2 余额检查
|
||
balanceResp, err := l.svcCtx.BillingRpc.CheckBalance(l.ctx, &billing.CheckBalanceReq{
|
||
UserId: in.UserId,
|
||
ProductCode: productResp.ProductCode,
|
||
QueryType: in.QueryType,
|
||
})
|
||
if err != nil {
|
||
return nil, fmt.Errorf("余额不足: %w", err)
|
||
}
|
||
|
||
// === 第3步:执行业务逻辑 ===
|
||
|
||
var businessResult *BusinessResult
|
||
switch in.QueryType {
|
||
case "risk-assessment":
|
||
businessResult, err = l.processRiskAssessment(in.Parameters)
|
||
case "credit-check":
|
||
businessResult, err = l.processCreditCheck(in.Parameters)
|
||
case "company-info":
|
||
businessResult, err = l.processCompanyInfo(in.Parameters)
|
||
case "data-query":
|
||
businessResult, err = l.processDataQuery(in.Parameters)
|
||
default:
|
||
return nil, errors.New("不支持的查询类型")
|
||
}
|
||
|
||
if err != nil {
|
||
return nil, fmt.Errorf("业务处理失败: %w", err)
|
||
}
|
||
|
||
// === 第4步:计费和审计 ===
|
||
|
||
// 4.1 执行扣费
|
||
chargeResp, err := l.svcCtx.BillingRpc.Charge(l.ctx, &billing.ChargeReq{
|
||
UserId: in.UserId,
|
||
ProductCode: productResp.ProductCode,
|
||
Amount: balanceResp.RequiredAmount,
|
||
TransactionType: in.QueryType,
|
||
RequestId: generateRequestId(),
|
||
})
|
||
if err != nil {
|
||
return nil, fmt.Errorf("扣费失败: %w", err)
|
||
}
|
||
|
||
// 4.2 异步记录审计日志
|
||
go func() {
|
||
l.svcCtx.AuditRpc.RecordAPICall(context.Background(), &audit.RecordAPICallReq{
|
||
UserId: in.UserId,
|
||
EnterpriseId: userResp.EnterpriseId,
|
||
QueryType: in.QueryType,
|
||
ClientIp: in.ClientIp,
|
||
TransactionId: chargeResp.TransactionId,
|
||
ResponseTime: time.Since(startTime).Milliseconds(),
|
||
Status: "success",
|
||
})
|
||
}()
|
||
|
||
return &pb.ProcessDataRequestResp{
|
||
Success: true,
|
||
Data: businessResult.ToJSON(),
|
||
TransactionId: chargeResp.TransactionId,
|
||
RemainingBalance: chargeResp.RemainingBalance,
|
||
}, nil
|
||
}
|
||
|
||
// 🔥 原FLXG逻辑 - 风险评估
|
||
func (l *DataOrchestratorLogic) processRiskAssessment(params map[string]string) (*BusinessResult, error) {
|
||
// 调用西部数据源
|
||
westData, err := l.svcCtx.WestDexClient.QueryRiskData(params)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// 调用百度风控API
|
||
baiduData, err := l.svcCtx.BaiduClient.RiskAssessment(params)
|
||
if err != nil {
|
||
logx.Errorf("百度API调用失败: %v", err)
|
||
// 降级处理,只使用西部数据
|
||
}
|
||
|
||
// 数据融合处理
|
||
result := &BusinessResult{
|
||
Code: "FLXG001",
|
||
Data: mergeRiskData(westData, baiduData),
|
||
Source: "west+baidu",
|
||
}
|
||
|
||
return result, nil
|
||
}
|
||
|
||
// 🔥 原JRZQ逻辑 - 征信查询
|
||
func (l *DataOrchestratorLogic) processCreditCheck(params map[string]string) (*BusinessResult, error) {
|
||
// 调用征信API
|
||
creditData, err := l.svcCtx.CreditClient.QueryCredit(params)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
return &BusinessResult{
|
||
Code: "JRZQ001",
|
||
Data: creditData,
|
||
Source: "credit_bureau",
|
||
}, nil
|
||
}
|
||
```
|
||
|
||
### 3. 数据库操作 (go-zero Model)
|
||
|
||
#### 用户模型
|
||
|
||
```go
|
||
// rpc/user/internal/model/usermodel.go
|
||
|
||
type User struct {
|
||
Id int64 `db:"id"`
|
||
Username string `db:"username"`
|
||
Password string `db:"password"`
|
||
Email string `db:"email"`
|
||
EnterpriseId int64 `db:"enterprise_id"`
|
||
Status int64 `db:"status"`
|
||
CreatedAt time.Time `db:"created_at"`
|
||
UpdatedAt time.Time `db:"updated_at"`
|
||
}
|
||
|
||
type UserModel interface {
|
||
Insert(ctx context.Context, data *User) (sql.Result, error)
|
||
FindOne(ctx context.Context, id int64) (*User, error)
|
||
FindOneByUsername(ctx context.Context, username string) (*User, error)
|
||
Update(ctx context.Context, data *User) error
|
||
Delete(ctx context.Context, id int64) error
|
||
}
|
||
|
||
type defaultUserModel struct {
|
||
conn sqlx.SqlConn
|
||
table string
|
||
}
|
||
|
||
func NewUserModel(conn sqlx.SqlConn) UserModel {
|
||
return &defaultUserModel{
|
||
conn: conn,
|
||
table: "`users`",
|
||
}
|
||
}
|
||
|
||
func (m *defaultUserModel) FindOneByUsername(ctx context.Context, username string) (*User, error) {
|
||
query := fmt.Sprintf("select %s from %s where `username` = ? limit 1", userRows, m.table)
|
||
var resp User
|
||
err := m.conn.QueryRowCtx(ctx, &resp, query, username)
|
||
switch err {
|
||
case nil:
|
||
return &resp, nil
|
||
case sqlc.ErrNotFound:
|
||
return nil, ErrNotFound
|
||
default:
|
||
return nil, err
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 缓存处理
|
||
|
||
```go
|
||
// rpc/user/internal/logic/user/getuserinfologic.go
|
||
|
||
func (l *GetUserInfoLogic) GetUserInfo(in *pb.GetUserInfoReq) (*pb.GetUserInfoResp, error) {
|
||
// 1. 先查缓存
|
||
cacheKey := fmt.Sprintf("user:info:%d", in.UserId)
|
||
cached, err := l.svcCtx.RedisClient.Get(cacheKey)
|
||
if err == nil && cached != "" {
|
||
var userInfo pb.GetUserInfoResp
|
||
if json.Unmarshal([]byte(cached), &userInfo) == nil {
|
||
return &userInfo, nil
|
||
}
|
||
}
|
||
|
||
// 2. 查数据库
|
||
user, err := l.svcCtx.UserModel.FindOne(l.ctx, in.UserId)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
enterprise, err := l.svcCtx.EnterpriseModel.FindOne(l.ctx, user.EnterpriseId)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
resp := &pb.GetUserInfoResp{
|
||
UserId: user.Id,
|
||
Username: user.Username,
|
||
Email: user.Email,
|
||
EnterpriseId: user.EnterpriseId,
|
||
EnterpriseName: enterprise.Name,
|
||
EncryptedSecretKey: enterprise.EncryptedSecretKey,
|
||
}
|
||
|
||
// 3. 写入缓存 (5分钟过期)
|
||
respJson, _ := json.Marshal(resp)
|
||
l.svcCtx.RedisClient.Setex(cacheKey, string(respJson), 300)
|
||
|
||
return resp, nil
|
||
}
|
||
```
|
||
|
||
## 🚀 部署配置
|
||
|
||
### Docker 部署
|
||
|
||
#### 1. 服务 Dockerfile
|
||
|
||
```dockerfile
|
||
# rpc/user/Dockerfile
|
||
FROM golang:1.19-alpine AS builder
|
||
|
||
WORKDIR /build
|
||
COPY . .
|
||
RUN go mod download
|
||
RUN go build -ldflags="-s -w" -o user rpc/user/user.go
|
||
|
||
FROM alpine:latest
|
||
RUN apk --no-cache add ca-certificates
|
||
WORKDIR /app
|
||
COPY --from=builder /build/user .
|
||
COPY --from=builder /build/rpc/user/etc/user.yaml ./etc/
|
||
|
||
EXPOSE 8001
|
||
CMD ["./user", "-f", "etc/user.yaml"]
|
||
```
|
||
|
||
#### 2. Docker Compose (开发环境)
|
||
|
||
```yaml
|
||
# deploy/docker/docker-compose.dev.yml
|
||
version: "3.8"
|
||
|
||
services:
|
||
# 基础设施
|
||
mysql:
|
||
image: mysql:8.0
|
||
environment:
|
||
MYSQL_ROOT_PASSWORD: root123
|
||
MYSQL_DATABASE: tianyuan
|
||
ports:
|
||
- "3306:3306"
|
||
volumes:
|
||
- mysql_data:/var/lib/mysql
|
||
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
|
||
|
||
redis:
|
||
image: redis:7-alpine
|
||
ports:
|
||
- "6379:6379"
|
||
|
||
etcd:
|
||
image: quay.io/coreos/etcd:v3.5.0
|
||
environment:
|
||
- ETCD_ADVERTISE_CLIENT_URLS=http://etcd:2379
|
||
- ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379
|
||
ports:
|
||
- "2379:2379"
|
||
|
||
kafka:
|
||
image: confluentinc/cp-kafka:latest
|
||
environment:
|
||
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
|
||
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092
|
||
ports:
|
||
- "9092:9092"
|
||
depends_on:
|
||
- zookeeper
|
||
|
||
zookeeper:
|
||
image: confluentinc/cp-zookeeper:latest
|
||
environment:
|
||
ZOOKEEPER_CLIENT_PORT: 2181
|
||
|
||
# 微服务
|
||
user-rpc:
|
||
build:
|
||
context: ../../
|
||
dockerfile: rpc/user/Dockerfile
|
||
ports:
|
||
- "8001:8001"
|
||
environment:
|
||
- DB_HOST=mysql
|
||
- REDIS_HOST=redis
|
||
- ETCD_HOSTS=etcd:2379
|
||
depends_on:
|
||
- mysql
|
||
- redis
|
||
- etcd
|
||
|
||
data-rpc:
|
||
build:
|
||
context: ../../
|
||
dockerfile: rpc/data/Dockerfile
|
||
ports:
|
||
- "8002:8002"
|
||
environment:
|
||
- DB_HOST=mysql
|
||
- REDIS_HOST=redis
|
||
- ETCD_HOSTS=etcd:2379
|
||
- USER_RPC=user-rpc:8001
|
||
depends_on:
|
||
- user-rpc
|
||
|
||
gateway-api:
|
||
build:
|
||
context: ../../
|
||
dockerfile: api/gateway/Dockerfile
|
||
ports:
|
||
- "8000:8000"
|
||
environment:
|
||
- USER_RPC=user-rpc:8001
|
||
- DATA_RPC=data-rpc:8002
|
||
- SECURITY_RPC=security-rpc:8003
|
||
- BILLING_RPC=billing-rpc:8004
|
||
depends_on:
|
||
- user-rpc
|
||
- data-rpc
|
||
|
||
volumes:
|
||
mysql_data:
|
||
```
|
||
|
||
#### 3. Kubernetes 部署
|
||
|
||
```yaml
|
||
# deploy/k8s/user-rpc.yaml
|
||
apiVersion: apps/v1
|
||
kind: Deployment
|
||
metadata:
|
||
name: user-rpc
|
||
spec:
|
||
replicas: 3
|
||
selector:
|
||
matchLabels:
|
||
app: user-rpc
|
||
template:
|
||
metadata:
|
||
labels:
|
||
app: user-rpc
|
||
spec:
|
||
containers:
|
||
- name: user-rpc
|
||
image: tianyuan/user-rpc:latest
|
||
ports:
|
||
- containerPort: 8001
|
||
env:
|
||
- name: DB_HOST
|
||
value: "mysql-svc"
|
||
- name: REDIS_HOST
|
||
value: "redis-svc"
|
||
- name: ETCD_HOSTS
|
||
value: "etcd-svc:2379"
|
||
resources:
|
||
requests:
|
||
memory: "128Mi"
|
||
cpu: "100m"
|
||
limits:
|
||
memory: "512Mi"
|
||
cpu: "500m"
|
||
livenessProbe:
|
||
httpGet:
|
||
path: /health
|
||
port: 8001
|
||
initialDelaySeconds: 30
|
||
periodSeconds: 10
|
||
readinessProbe:
|
||
httpGet:
|
||
path: /ready
|
||
port: 8001
|
||
initialDelaySeconds: 5
|
||
periodSeconds: 5
|
||
|
||
---
|
||
apiVersion: v1
|
||
kind: Service
|
||
metadata:
|
||
name: user-rpc-svc
|
||
spec:
|
||
selector:
|
||
app: user-rpc
|
||
ports:
|
||
- port: 8001
|
||
targetPort: 8001
|
||
type: ClusterIP
|
||
```
|
||
|
||
### 4. Makefile (统一构建部署)
|
||
|
||
```makefile
|
||
# Makefile
|
||
.PHONY: build-all start-dev stop-dev deploy-k8s
|
||
|
||
# 构建所有服务
|
||
build-all:
|
||
@echo "构建所有微服务..."
|
||
cd api/gateway && go build -o ../../bin/gateway gateway.go
|
||
cd rpc/user && go build -o ../../bin/user-rpc user.go
|
||
cd rpc/data && go build -o ../../bin/data-rpc data.go
|
||
cd rpc/security && go build -o ../../bin/security-rpc security.go
|
||
cd rpc/billing && go build -o ../../bin/billing-rpc billing.go
|
||
|
||
# 生成代码
|
||
gen-api:
|
||
cd api/gateway && goctl api go -api gateway.api -dir .
|
||
|
||
gen-rpc:
|
||
cd rpc/user && goctl rpc protoc user.proto --go_out=. --go-grpc_out=. --zrpc_out=.
|
||
|
||
# 开发环境
|
||
start-dev:
|
||
docker-compose -f deploy/docker/docker-compose.dev.yml up -d
|
||
|
||
stop-dev:
|
||
docker-compose -f deploy/docker/docker-compose.dev.yml down
|
||
|
||
# 数据库迁移
|
||
migrate-up:
|
||
cd tools/migrate && go run migrate.go up
|
||
|
||
migrate-down:
|
||
cd tools/migrate && go run migrate.go down
|
||
|
||
# K8s部署
|
||
deploy-k8s:
|
||
kubectl apply -f deploy/k8s/namespace.yaml
|
||
kubectl apply -f deploy/k8s/configmap.yaml
|
||
kubectl apply -f deploy/k8s/mysql.yaml
|
||
kubectl apply -f deploy/k8s/redis.yaml
|
||
kubectl apply -f deploy/k8s/user-rpc.yaml
|
||
kubectl apply -f deploy/k8s/data-rpc.yaml
|
||
kubectl apply -f deploy/k8s/gateway-api.yaml
|
||
|
||
# 测试
|
||
test-all:
|
||
go test ./api/gateway/...
|
||
go test ./rpc/user/...
|
||
go test ./rpc/data/...
|
||
|
||
# 代码检查
|
||
lint:
|
||
golangci-lint run ./...
|
||
|
||
# 清理
|
||
clean:
|
||
rm -rf bin/
|
||
docker system prune -f
|
||
```
|
||
|
||
## 🔄 CI/CD 配置
|
||
|
||
```yaml
|
||
# .github/workflows/deploy.yml
|
||
name: Deploy Microservices
|
||
|
||
on:
|
||
push:
|
||
branches: [main]
|
||
|
||
jobs:
|
||
build-and-deploy:
|
||
runs-on: ubuntu-latest
|
||
|
||
steps:
|
||
- uses: actions/checkout@v3
|
||
|
||
- name: Set up Go
|
||
uses: actions/setup-go@v3
|
||
with:
|
||
go-version: 1.19
|
||
|
||
- name: Build services
|
||
run: make build-all
|
||
|
||
- name: Run tests
|
||
run: make test-all
|
||
|
||
- name: Build Docker images
|
||
run: |
|
||
docker build -t tianyuan/gateway:${{ github.sha }} api/gateway/
|
||
docker build -t tianyuan/user-rpc:${{ github.sha }} rpc/user/
|
||
docker build -t tianyuan/data-rpc:${{ github.sha }} rpc/data/
|
||
|
||
- name: Push to registry
|
||
run: |
|
||
echo ${{ secrets.DOCKER_PASSWORD }} | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin
|
||
docker push tianyuan/gateway:${{ github.sha }}
|
||
docker push tianyuan/user-rpc:${{ github.sha }}
|
||
docker push tianyuan/data-rpc:${{ github.sha }}
|
||
|
||
- name: Deploy to K8s
|
||
run: |
|
||
echo "${{ secrets.KUBECONFIG }}" | base64 -d > kubeconfig
|
||
export KUBECONFIG=kubeconfig
|
||
sed -i 's|latest|${{ github.sha }}|g' deploy/k8s/*.yaml
|
||
kubectl apply -f deploy/k8s/
|
||
```
|
||
|
||
这个架构设计完全基于 go-zero 框架,保持了你原有业务逻辑的同时,提供了清晰的服务边界和强大的扩展能力。每个服务都可以独立开发、测试、部署和扩容。
|