Merge branch 'develop' of git.ailuowan.com:zzw/lone-services into develop
This commit is contained in:
@@ -73,7 +73,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Docker build
|
- name: Docker build
|
||||||
run: |
|
run: |
|
||||||
docker build \
|
docker buildx build --load \
|
||||||
-f bff/Dockerfile \
|
-f bff/Dockerfile \
|
||||||
-t ${{ vars.REGISTRY }}/$NAME:latest \
|
-t ${{ vars.REGISTRY }}/$NAME:latest \
|
||||||
.
|
.
|
||||||
@@ -98,7 +98,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Docker build
|
- name: Docker build
|
||||||
run: |
|
run: |
|
||||||
docker build \
|
docker buildx build --load \
|
||||||
-f product/Dockerfile \
|
-f product/Dockerfile \
|
||||||
-t ${{ vars.REGISTRY }}/$NAME:latest \
|
-t ${{ vars.REGISTRY }}/$NAME:latest \
|
||||||
.
|
.
|
||||||
@@ -123,7 +123,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Docker build
|
- name: Docker build
|
||||||
run: |
|
run: |
|
||||||
docker build \
|
docker buildx build --load \
|
||||||
-f admin/Dockerfile \
|
-f admin/Dockerfile \
|
||||||
-t ${{ vars.REGISTRY }}/$NAME:latest \
|
-t ${{ vars.REGISTRY }}/$NAME:latest \
|
||||||
.
|
.
|
||||||
|
|||||||
+7
-3
@@ -1,3 +1,5 @@
|
|||||||
|
# syntax=docker/dockerfile:1
|
||||||
|
|
||||||
FROM golang:1.26.5-alpine AS builder
|
FROM golang:1.26.5-alpine AS builder
|
||||||
|
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
@@ -11,10 +13,13 @@ COPY pkg ./pkg
|
|||||||
COPY admin/go.mod admin/go.sum ./admin/
|
COPY admin/go.mod admin/go.sum ./admin/
|
||||||
|
|
||||||
WORKDIR /src/admin
|
WORKDIR /src/admin
|
||||||
RUN go mod download
|
RUN --mount=type=cache,target=/go/pkg/mod \
|
||||||
|
go mod download
|
||||||
|
|
||||||
COPY admin/ ./
|
COPY admin/ ./
|
||||||
RUN go build -ldflags="-s -w" -o /out/admin .
|
RUN --mount=type=cache,target=/go/pkg/mod \
|
||||||
|
--mount=type=cache,target=/root/.cache/go-build \
|
||||||
|
go build -ldflags="-s -w" -o /out/admin .
|
||||||
|
|
||||||
FROM alpine:3.22
|
FROM alpine:3.22
|
||||||
|
|
||||||
@@ -27,7 +32,6 @@ RUN apk add --no-cache tzdata \
|
|||||||
&& echo "Asia/Shanghai" > /etc/timezone
|
&& echo "Asia/Shanghai" > /etc/timezone
|
||||||
|
|
||||||
COPY --from=builder /out/admin .
|
COPY --from=builder /out/admin .
|
||||||
COPY admin/etc/admin.yaml ./etc/admin.yaml
|
|
||||||
|
|
||||||
EXPOSE 10200
|
EXPOSE 10200
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
etc/bff.yaml
|
||||||
+8
-3
@@ -1,3 +1,5 @@
|
|||||||
|
# syntax=docker/dockerfile:1
|
||||||
|
|
||||||
FROM golang:1.26.5-alpine AS builder
|
FROM golang:1.26.5-alpine AS builder
|
||||||
|
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
@@ -11,10 +13,13 @@ COPY pkg ./pkg
|
|||||||
COPY bff/go.mod bff/go.sum ./bff/
|
COPY bff/go.mod bff/go.sum ./bff/
|
||||||
|
|
||||||
WORKDIR /src/bff
|
WORKDIR /src/bff
|
||||||
RUN go mod download
|
RUN --mount=type=cache,target=/go/pkg/mod \
|
||||||
|
go mod download
|
||||||
|
|
||||||
COPY bff/ ./
|
COPY bff/ ./
|
||||||
RUN go build -ldflags="-s -w" -o /out/bff .
|
RUN --mount=type=cache,target=/go/pkg/mod \
|
||||||
|
--mount=type=cache,target=/root/.cache/go-build \
|
||||||
|
go build -ldflags="-s -w" -o /out/bff .
|
||||||
|
|
||||||
FROM alpine:3.22
|
FROM alpine:3.22
|
||||||
|
|
||||||
@@ -27,7 +32,7 @@ RUN apk add --no-cache tzdata \
|
|||||||
&& echo "Asia/Shanghai" > /etc/timezone
|
&& echo "Asia/Shanghai" > /etc/timezone
|
||||||
|
|
||||||
COPY --from=builder /out/bff .
|
COPY --from=builder /out/bff .
|
||||||
COPY bff/etc/ ./etc/
|
COPY bff/etc/*.pb ./etc/
|
||||||
|
|
||||||
EXPOSE 10000
|
EXPOSE 10000
|
||||||
|
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
Name: bff-service
|
|
||||||
Host: 0.0.0.0
|
|
||||||
Port: 10000
|
|
||||||
|
|
||||||
Nacos:
|
|
||||||
Hosts:
|
|
||||||
- rnacos:8848
|
|
||||||
NamespaceId: test
|
|
||||||
Group: LONE_SERVICES
|
|
||||||
RegisterIP: bff
|
|
||||||
|
|
||||||
Response:
|
|
||||||
IsDebug: true # true 不加密
|
|
||||||
EncryptKey: "M17fcaPeHDr05H17fcaPH1deDH17l3Bd"
|
|
||||||
|
|
||||||
Upstreams:
|
|
||||||
- Name: product
|
|
||||||
Grpc:
|
|
||||||
Target: product-service
|
|
||||||
Timeout: 5000
|
|
||||||
ProtoSets:
|
|
||||||
- etc/product.pb
|
|
||||||
|
|
||||||
- Name: admin
|
|
||||||
Grpc:
|
|
||||||
Target: admin-service
|
|
||||||
Timeout: 5000
|
|
||||||
ProtoSets:
|
|
||||||
- etc/admin.pb
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
services:
|
|
||||||
rnacos:
|
|
||||||
image: qingpan/rnacos:stable
|
|
||||||
restart: unless-stopped
|
|
||||||
ports:
|
|
||||||
- "8848:8848"
|
|
||||||
volumes:
|
|
||||||
- ./rnacos/data:/app/data
|
|
||||||
environment:
|
|
||||||
RNACOS_SERVER_PORT: 8848
|
|
||||||
RNACOS_ENABLE_NO_AUTH_CONSOLE: "true"
|
|
||||||
|
|
||||||
apisix:
|
|
||||||
image: apache/apisix:3.17.0-debian
|
|
||||||
restart: unless-stopped
|
|
||||||
volumes:
|
|
||||||
- ./apisix/config.yaml:/usr/local/apisix/conf/config.yaml:ro
|
|
||||||
- ./apisix/apisix.yaml:/usr/local/apisix/conf/apisix.yaml:ro
|
|
||||||
# 单文件挂载到 /usr/local/apisix/auth.lua
|
|
||||||
- ./apisix/lua/auth.lua:/usr/local/apisix/auth.lua:ro
|
|
||||||
ports:
|
|
||||||
- "9080:9080"
|
|
||||||
- "9443:9443"
|
|
||||||
networks:
|
|
||||||
- default
|
|
||||||
|
|
||||||
networks:
|
|
||||||
default:
|
|
||||||
external: true
|
|
||||||
name: services-network
|
|
||||||
+363
-363
@@ -1,363 +1,363 @@
|
|||||||
package discovery
|
package discovery
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Hosts []string
|
Hosts []string
|
||||||
NamespaceId string
|
NamespaceId string
|
||||||
Group string
|
Group string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Instance struct {
|
type Instance struct {
|
||||||
ServiceName string
|
ServiceName string
|
||||||
IP string
|
IP string
|
||||||
Port uint64
|
Port uint64
|
||||||
Group string
|
Group string
|
||||||
}
|
}
|
||||||
|
|
||||||
type ServiceInstance struct {
|
type ServiceInstance struct {
|
||||||
IP string
|
IP string
|
||||||
Port uint64
|
Port uint64
|
||||||
ServiceName string
|
ServiceName string
|
||||||
Healthy bool
|
Healthy bool
|
||||||
Weight float64
|
Weight float64
|
||||||
Enabled bool
|
Enabled bool
|
||||||
Ephemeral bool
|
Ephemeral bool
|
||||||
ClusterName string
|
ClusterName string
|
||||||
Metadata map[string]string
|
Metadata map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
baseURL string
|
baseURL string
|
||||||
namespace string
|
namespace string
|
||||||
group string
|
group string
|
||||||
httpClient = &http.Client{Timeout: 5 * time.Second}
|
httpClient = &http.Client{Timeout: 5 * time.Second}
|
||||||
current Instance
|
current Instance
|
||||||
inited bool
|
inited bool
|
||||||
registered bool
|
registered bool
|
||||||
stopBeat chan struct{}
|
stopBeat chan struct{}
|
||||||
|
|
||||||
pickMu sync.Mutex
|
pickMu sync.Mutex
|
||||||
pickRR = map[string]uint64{}
|
pickRR = map[string]uint64{}
|
||||||
)
|
)
|
||||||
|
|
||||||
func Init(cfg Config) error {
|
func Init(cfg Config) error {
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
defer mu.Unlock()
|
defer mu.Unlock()
|
||||||
|
|
||||||
if inited {
|
if inited {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if len(cfg.Hosts) == 0 {
|
if len(cfg.Hosts) == 0 {
|
||||||
return fmt.Errorf("discovery: Hosts is empty")
|
return fmt.Errorf("discovery: Hosts is empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
host, port, err := parseHostPort(cfg.Hosts[0])
|
host, port, err := parseHostPort(cfg.Hosts[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
group = cfg.Group
|
group = cfg.Group
|
||||||
if group == "" {
|
if group == "" {
|
||||||
group = "default"
|
group = "default"
|
||||||
}
|
}
|
||||||
namespace = cfg.NamespaceId
|
namespace = cfg.NamespaceId
|
||||||
baseURL = fmt.Sprintf("http://%s:%d", host, port)
|
baseURL = fmt.Sprintf("http://%s:%d", host, port)
|
||||||
inited = true
|
inited = true
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func Register(inst Instance) error {
|
func Register(inst Instance) error {
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
defer mu.Unlock()
|
defer mu.Unlock()
|
||||||
|
|
||||||
if !inited {
|
if !inited {
|
||||||
return fmt.Errorf("discovery: call Init first")
|
return fmt.Errorf("discovery: call Init first")
|
||||||
}
|
}
|
||||||
|
|
||||||
g := inst.Group
|
g := inst.Group
|
||||||
if g == "" {
|
if g == "" {
|
||||||
g = group
|
g = group
|
||||||
}
|
}
|
||||||
|
|
||||||
form := url.Values{}
|
form := url.Values{}
|
||||||
form.Set("ip", inst.IP)
|
form.Set("ip", inst.IP)
|
||||||
form.Set("port", strconv.FormatUint(inst.Port, 10))
|
form.Set("port", strconv.FormatUint(inst.Port, 10))
|
||||||
form.Set("serviceName", joinGroupService(g, inst.ServiceName))
|
form.Set("serviceName", joinGroupService(g, inst.ServiceName))
|
||||||
form.Set("groupName", g)
|
form.Set("groupName", g)
|
||||||
form.Set("weight", "10")
|
form.Set("weight", "10")
|
||||||
form.Set("enable", "true")
|
form.Set("enable", "true")
|
||||||
form.Set("healthy", "true")
|
form.Set("healthy", "true")
|
||||||
form.Set("ephemeral", "true")
|
form.Set("ephemeral", "true")
|
||||||
form.Set("clusterName", "DEFAULT")
|
form.Set("clusterName", "DEFAULT")
|
||||||
if namespace != "" {
|
if namespace != "" {
|
||||||
form.Set("namespaceId", namespace)
|
form.Set("namespaceId", namespace)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := doForm(http.MethodPost, "/nacos/v1/ns/instance", form); err != nil {
|
if err := doForm(http.MethodPost, "/nacos/v1/ns/instance", form); err != nil {
|
||||||
return fmt.Errorf("discovery: register: %w", err)
|
return fmt.Errorf("discovery: register: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
current = inst
|
current = inst
|
||||||
current.Group = g
|
current.Group = g
|
||||||
registered = true
|
registered = true
|
||||||
|
|
||||||
if stopBeat != nil {
|
if stopBeat != nil {
|
||||||
close(stopBeat)
|
close(stopBeat)
|
||||||
}
|
}
|
||||||
stopBeat = make(chan struct{})
|
stopBeat = make(chan struct{})
|
||||||
go heartbeatLoop(stopBeat, current)
|
go heartbeatLoop(stopBeat, current)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func Deregister() error {
|
func Deregister() error {
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
defer mu.Unlock()
|
defer mu.Unlock()
|
||||||
|
|
||||||
if !inited || !registered {
|
if !inited || !registered {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if stopBeat != nil {
|
if stopBeat != nil {
|
||||||
close(stopBeat)
|
close(stopBeat)
|
||||||
stopBeat = nil
|
stopBeat = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
form := url.Values{}
|
form := url.Values{}
|
||||||
form.Set("ip", current.IP)
|
form.Set("ip", current.IP)
|
||||||
form.Set("port", strconv.FormatUint(current.Port, 10))
|
form.Set("port", strconv.FormatUint(current.Port, 10))
|
||||||
form.Set("serviceName", joinGroupService(current.Group, current.ServiceName))
|
form.Set("serviceName", joinGroupService(current.Group, current.ServiceName))
|
||||||
form.Set("groupName", current.Group)
|
form.Set("groupName", current.Group)
|
||||||
form.Set("clusterName", "DEFAULT")
|
form.Set("clusterName", "DEFAULT")
|
||||||
form.Set("ephemeral", "true")
|
form.Set("ephemeral", "true")
|
||||||
if namespace != "" {
|
if namespace != "" {
|
||||||
form.Set("namespaceId", namespace)
|
form.Set("namespaceId", namespace)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := doForm(http.MethodDelete, "/nacos/v1/ns/instance", form); err != nil {
|
if err := doForm(http.MethodDelete, "/nacos/v1/ns/instance", form); err != nil {
|
||||||
return fmt.Errorf("discovery: deregister: %w", err)
|
return fmt.Errorf("discovery: deregister: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
registered = false
|
registered = false
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetInstances(serviceName string, groupName ...string) ([]ServiceInstance, error) {
|
func GetInstances(serviceName string, groupName ...string) ([]ServiceInstance, error) {
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
defer mu.Unlock()
|
defer mu.Unlock()
|
||||||
|
|
||||||
if !inited {
|
if !inited {
|
||||||
return nil, fmt.Errorf("discovery: call Init first")
|
return nil, fmt.Errorf("discovery: call Init first")
|
||||||
}
|
}
|
||||||
|
|
||||||
g := group
|
g := group
|
||||||
if len(groupName) > 0 && groupName[0] != "" {
|
if len(groupName) > 0 && groupName[0] != "" {
|
||||||
g = groupName[0]
|
g = groupName[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
q := url.Values{}
|
q := url.Values{}
|
||||||
q.Set("serviceName", joinGroupService(g, serviceName))
|
q.Set("serviceName", joinGroupService(g, serviceName))
|
||||||
q.Set("groupName", g)
|
q.Set("groupName", g)
|
||||||
q.Set("healthyOnly", "true")
|
q.Set("healthyOnly", "true")
|
||||||
if namespace != "" {
|
if namespace != "" {
|
||||||
q.Set("namespaceId", namespace)
|
q.Set("namespaceId", namespace)
|
||||||
}
|
}
|
||||||
|
|
||||||
body, err := doGet("/nacos/v1/ns/instance/list", q)
|
body, err := doGet("/nacos/v1/ns/instance/list", q)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("discovery: select instances: %w", err)
|
return nil, fmt.Errorf("discovery: select instances: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var resp listResponse
|
var resp listResponse
|
||||||
if err := json.Unmarshal(body, &resp); err != nil {
|
if err := json.Unmarshal(body, &resp); err != nil {
|
||||||
return nil, fmt.Errorf("discovery: decode instances: %w", err)
|
return nil, fmt.Errorf("discovery: decode instances: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
out := make([]ServiceInstance, 0, len(resp.Hosts))
|
out := make([]ServiceInstance, 0, len(resp.Hosts))
|
||||||
for _, h := range resp.Hosts {
|
for _, h := range resp.Hosts {
|
||||||
out = append(out, ServiceInstance{
|
out = append(out, ServiceInstance{
|
||||||
IP: h.IP,
|
IP: h.IP,
|
||||||
Port: uint64(h.Port),
|
Port: uint64(h.Port),
|
||||||
ServiceName: h.ServiceName,
|
ServiceName: h.ServiceName,
|
||||||
Healthy: h.Healthy,
|
Healthy: h.Healthy,
|
||||||
Weight: h.Weight,
|
Weight: h.Weight,
|
||||||
Enabled: h.Enabled,
|
Enabled: h.Enabled,
|
||||||
Ephemeral: h.Ephemeral,
|
Ephemeral: h.Ephemeral,
|
||||||
ClusterName: h.ClusterName,
|
ClusterName: h.ClusterName,
|
||||||
Metadata: h.Metadata,
|
Metadata: h.Metadata,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pick returns one healthy instance for serviceName using round-robin.
|
// Pick returns one healthy instance for serviceName using round-robin.
|
||||||
func Pick(serviceName string, groupName ...string) (*ServiceInstance, error) {
|
func Pick(serviceName string, groupName ...string) (*ServiceInstance, error) {
|
||||||
list, err := GetInstances(serviceName, groupName...)
|
list, err := GetInstances(serviceName, groupName...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if len(list) == 0 {
|
if len(list) == 0 {
|
||||||
return nil, fmt.Errorf("discovery: no healthy instance for %q", serviceName)
|
return nil, fmt.Errorf("discovery: no healthy instance for %q", serviceName)
|
||||||
}
|
}
|
||||||
|
|
||||||
pickMu.Lock()
|
pickMu.Lock()
|
||||||
idx := pickRR[serviceName] % uint64(len(list))
|
idx := pickRR[serviceName] % uint64(len(list))
|
||||||
pickRR[serviceName]++
|
pickRR[serviceName]++
|
||||||
pickMu.Unlock()
|
pickMu.Unlock()
|
||||||
|
|
||||||
inst := list[idx]
|
inst := list[idx]
|
||||||
return &inst, nil
|
return &inst, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type listResponse struct {
|
type listResponse struct {
|
||||||
Hosts []hostInfo `json:"hosts"`
|
Hosts []hostInfo `json:"hosts"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type hostInfo struct {
|
type hostInfo struct {
|
||||||
IP string `json:"ip"`
|
IP string `json:"ip"`
|
||||||
Port float64 `json:"port"`
|
Port float64 `json:"port"`
|
||||||
ServiceName string `json:"serviceName"`
|
ServiceName string `json:"serviceName"`
|
||||||
Healthy bool `json:"healthy"`
|
Healthy bool `json:"healthy"`
|
||||||
Weight float64 `json:"weight"`
|
Weight float64 `json:"weight"`
|
||||||
Enabled bool `json:"enabled"`
|
Enabled bool `json:"enabled"`
|
||||||
Ephemeral bool `json:"ephemeral"`
|
Ephemeral bool `json:"ephemeral"`
|
||||||
ClusterName string `json:"clusterName"`
|
ClusterName string `json:"clusterName"`
|
||||||
Metadata map[string]string `json:"metadata"`
|
Metadata map[string]string `json:"metadata"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type beatResponse struct {
|
type beatResponse struct {
|
||||||
ClientBeatInterval int64 `json:"clientBeatInterval"`
|
ClientBeatInterval int64 `json:"clientBeatInterval"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func heartbeatLoop(stop <-chan struct{}, inst Instance) {
|
func heartbeatLoop(stop <-chan struct{}, inst Instance) {
|
||||||
interval := 5 * time.Second
|
interval := 5 * time.Second
|
||||||
ticker := time.NewTicker(interval)
|
ticker := time.NewTicker(interval)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-stop:
|
case <-stop:
|
||||||
return
|
return
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
if d := sendBeat(inst); d > 0 {
|
if d := sendBeat(inst); d > 0 {
|
||||||
ticker.Reset(d)
|
ticker.Reset(d)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendBeat(inst Instance) time.Duration {
|
func sendBeat(inst Instance) time.Duration {
|
||||||
groupedName := joinGroupService(inst.Group, inst.ServiceName)
|
groupedName := joinGroupService(inst.Group, inst.ServiceName)
|
||||||
beat, _ := json.Marshal(map[string]any{
|
beat, _ := json.Marshal(map[string]any{
|
||||||
"ip": inst.IP,
|
"ip": inst.IP,
|
||||||
"port": inst.Port,
|
"port": inst.Port,
|
||||||
"serviceName": groupedName,
|
"serviceName": groupedName,
|
||||||
"weight": 10,
|
"weight": 10,
|
||||||
"ephemeral": true,
|
"ephemeral": true,
|
||||||
"cluster": "DEFAULT",
|
"cluster": "DEFAULT",
|
||||||
})
|
})
|
||||||
|
|
||||||
q := url.Values{}
|
q := url.Values{}
|
||||||
q.Set("serviceName", groupedName)
|
q.Set("serviceName", groupedName)
|
||||||
q.Set("groupName", inst.Group)
|
q.Set("groupName", inst.Group)
|
||||||
q.Set("ip", inst.IP)
|
q.Set("ip", inst.IP)
|
||||||
q.Set("port", strconv.FormatUint(inst.Port, 10))
|
q.Set("port", strconv.FormatUint(inst.Port, 10))
|
||||||
q.Set("beat", string(beat))
|
q.Set("beat", string(beat))
|
||||||
if namespace != "" {
|
if namespace != "" {
|
||||||
q.Set("namespaceId", namespace)
|
q.Set("namespaceId", namespace)
|
||||||
}
|
}
|
||||||
req, err := http.NewRequest(http.MethodPut, baseURL+"/nacos/v1/ns/instance/beat?"+q.Encode(), nil)
|
req, err := http.NewRequest(http.MethodPut, baseURL+"/nacos/v1/ns/instance/beat?"+q.Encode(), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
resp, err := httpClient.Do(req)
|
resp, err := httpClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
body, _ := io.ReadAll(resp.Body)
|
body, _ := io.ReadAll(resp.Body)
|
||||||
if resp.StatusCode >= 300 {
|
if resp.StatusCode >= 300 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
var br beatResponse
|
var br beatResponse
|
||||||
if json.Unmarshal(body, &br) == nil && br.ClientBeatInterval > 0 {
|
if json.Unmarshal(body, &br) == nil && br.ClientBeatInterval > 0 {
|
||||||
return time.Duration(br.ClientBeatInterval) * time.Millisecond
|
return time.Duration(br.ClientBeatInterval) * time.Millisecond
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func doForm(method, path string, form url.Values) error {
|
func doForm(method, path string, form url.Values) error {
|
||||||
req, err := http.NewRequest(method, baseURL+path, strings.NewReader(form.Encode()))
|
req, err := http.NewRequest(method, baseURL+path, strings.NewReader(form.Encode()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
resp, err := httpClient.Do(req)
|
resp, err := httpClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
body, _ := io.ReadAll(resp.Body)
|
body, _ := io.ReadAll(resp.Body)
|
||||||
if resp.StatusCode >= 300 {
|
if resp.StatusCode >= 300 {
|
||||||
return fmt.Errorf("http %d: %s", resp.StatusCode, strings.TrimSpace(string(body)))
|
return fmt.Errorf("http %d: %s", resp.StatusCode, strings.TrimSpace(string(body)))
|
||||||
}
|
}
|
||||||
if s := strings.TrimSpace(string(body)); s != "" && s != "ok" && s != "true" {
|
if s := strings.TrimSpace(string(body)); s != "" && s != "ok" && s != "true" {
|
||||||
// rnacos / nacos 成功时通常返回 "ok"
|
// rnacos / nacos 成功时通常返回 "ok"
|
||||||
var ok bool
|
var ok bool
|
||||||
if err := json.Unmarshal(body, &ok); err == nil && ok {
|
if err := json.Unmarshal(body, &ok); err == nil && ok {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if s != "ok" {
|
if s != "ok" {
|
||||||
return fmt.Errorf("unexpected response: %s", s)
|
return fmt.Errorf("unexpected response: %s", s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func doGet(path string, q url.Values) ([]byte, error) {
|
func doGet(path string, q url.Values) ([]byte, error) {
|
||||||
resp, err := httpClient.Get(baseURL + path + "?" + q.Encode())
|
resp, err := httpClient.Get(baseURL + path + "?" + q.Encode())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
body, err := io.ReadAll(resp.Body)
|
body, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if resp.StatusCode >= 300 {
|
if resp.StatusCode >= 300 {
|
||||||
return nil, fmt.Errorf("http %d: %s", resp.StatusCode, strings.TrimSpace(string(body)))
|
return nil, fmt.Errorf("http %d: %s", resp.StatusCode, strings.TrimSpace(string(body)))
|
||||||
}
|
}
|
||||||
return body, nil
|
return body, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseHostPort(addr string) (string, uint64, error) {
|
func parseHostPort(addr string) (string, uint64, error) {
|
||||||
host, portStr, ok := strings.Cut(addr, ":")
|
host, portStr, ok := strings.Cut(addr, ":")
|
||||||
if !ok || host == "" || portStr == "" {
|
if !ok || host == "" || portStr == "" {
|
||||||
return "", 0, fmt.Errorf("discovery: invalid host %q, want host:port", addr)
|
return "", 0, fmt.Errorf("discovery: invalid host %q, want host:port", addr)
|
||||||
}
|
}
|
||||||
port, err := strconv.ParseUint(portStr, 10, 64)
|
port, err := strconv.ParseUint(portStr, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", 0, fmt.Errorf("discovery: invalid port in %q: %w", addr, err)
|
return "", 0, fmt.Errorf("discovery: invalid port in %q: %w", addr, err)
|
||||||
}
|
}
|
||||||
return host, port, nil
|
return host, port, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func joinGroupService(groupName, serviceName string) string {
|
func joinGroupService(groupName, serviceName string) string {
|
||||||
if groupName == "" || strings.Contains(serviceName, "@@") {
|
if groupName == "" || strings.Contains(serviceName, "@@") {
|
||||||
return serviceName
|
return serviceName
|
||||||
}
|
}
|
||||||
return groupName + "@@" + serviceName
|
return groupName + "@@" + serviceName
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
etc/product.yaml
|
||||||
+7
-3
@@ -1,3 +1,5 @@
|
|||||||
|
# syntax=docker/dockerfile:1
|
||||||
|
|
||||||
FROM golang:1.26.5-alpine AS builder
|
FROM golang:1.26.5-alpine AS builder
|
||||||
|
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
@@ -11,10 +13,13 @@ COPY pkg ./pkg
|
|||||||
COPY product/go.mod product/go.sum ./product/
|
COPY product/go.mod product/go.sum ./product/
|
||||||
|
|
||||||
WORKDIR /src/product
|
WORKDIR /src/product
|
||||||
RUN go mod download
|
RUN --mount=type=cache,target=/go/pkg/mod \
|
||||||
|
go mod download
|
||||||
|
|
||||||
COPY product/ ./
|
COPY product/ ./
|
||||||
RUN go build -ldflags="-s -w" -o /out/product .
|
RUN --mount=type=cache,target=/go/pkg/mod \
|
||||||
|
--mount=type=cache,target=/root/.cache/go-build \
|
||||||
|
go build -ldflags="-s -w" -o /out/product .
|
||||||
|
|
||||||
FROM alpine:3.22
|
FROM alpine:3.22
|
||||||
|
|
||||||
@@ -27,7 +32,6 @@ RUN apk add --no-cache tzdata \
|
|||||||
&& echo "Asia/Shanghai" > /etc/timezone
|
&& echo "Asia/Shanghai" > /etc/timezone
|
||||||
|
|
||||||
COPY --from=builder /out/product .
|
COPY --from=builder /out/product .
|
||||||
COPY product/etc/product.yaml ./etc/product.yaml
|
|
||||||
|
|
||||||
EXPOSE 10100
|
EXPOSE 10100
|
||||||
|
|
||||||
|
|||||||
@@ -1,60 +0,0 @@
|
|||||||
Name: product-service
|
|
||||||
ListenOn: 0.0.0.0:10100
|
|
||||||
Mode: dev
|
|
||||||
|
|
||||||
Log:
|
|
||||||
ServiceName: product-service
|
|
||||||
Mode: file # console | file | volume
|
|
||||||
Encoding: plain
|
|
||||||
Level: info
|
|
||||||
Path: logs
|
|
||||||
KeepDays: 7
|
|
||||||
Compress: false
|
|
||||||
# Rotation: daily
|
|
||||||
# MaxSize: 100
|
|
||||||
# MaxBackups: 10
|
|
||||||
|
|
||||||
Nacos:
|
|
||||||
Hosts:
|
|
||||||
- rnacos:8848
|
|
||||||
NamespaceId: test
|
|
||||||
Group: LONE_SERVICES
|
|
||||||
RegisterIP: product
|
|
||||||
|
|
||||||
Mysql:
|
|
||||||
Host: mysql
|
|
||||||
Port: 3306
|
|
||||||
User: root
|
|
||||||
Password: "123123"
|
|
||||||
Database: dms-product
|
|
||||||
Charset: utf8mb4
|
|
||||||
Prefix:
|
|
||||||
|
|
||||||
# Host: "39.106.171.204"
|
|
||||||
# Port: 33066
|
|
||||||
# User: root
|
|
||||||
# Password: "MOLXRZNOU4Y4"
|
|
||||||
# Database: dms-product
|
|
||||||
# Charset: utf8mb4
|
|
||||||
# Prefix:
|
|
||||||
|
|
||||||
# ReadHost: mysql-slave
|
|
||||||
# ReadPort: 3306
|
|
||||||
# ReadUser: root
|
|
||||||
# ReadPassword: "123123"
|
|
||||||
# ReadDatabase: dms-product
|
|
||||||
|
|
||||||
BizRedis:
|
|
||||||
Host: redis
|
|
||||||
Port: 6379
|
|
||||||
Password: ""
|
|
||||||
DB: 0
|
|
||||||
|
|
||||||
AppLog:
|
|
||||||
Path: logs
|
|
||||||
InfoFile: info.log
|
|
||||||
ErrorFile: error.log
|
|
||||||
FatalFile: fatal.log
|
|
||||||
MaxSize: 100
|
|
||||||
MaxBackups: 10
|
|
||||||
MaxAge: 30
|
|
||||||
Reference in New Issue
Block a user