Compare commits
No commits in common. "028bb6cc0e7a9255e42d0c6b2fa4203f0f9d31f5" and "9c39c8cbd7d4668971bd52c4cdf9985d12b6ff9e" have entirely different histories.
028bb6cc0e
...
9c39c8cbd7
10
.env.example
10
.env.example
@ -1,20 +1,18 @@
|
|||||||
# iloom 微服务环境变量配置
|
# iloom 微服务环境变量配置
|
||||||
# Copy this file to .env and fill in real values. NEVER commit .env to git.
|
|
||||||
|
|
||||||
# MySQL (共享 muyu_wms 数据库)
|
# MySQL (共享 muyu_wms 数据库)
|
||||||
MYSQL_ROOT_PASSWORD=your_mysql_password_here
|
DB_DSN=root:muyu2026@tcp(mysql:3306)/muyu_wms?parseTime=true&charset=utf8mb4
|
||||||
DB_DSN=root:your_password@tcp(mysql:3306)/muyu_wms?parseTime=true&charset=utf8mb4
|
|
||||||
|
|
||||||
# JWT (复用 muyu 认证)
|
# JWT (复用 muyu 认证)
|
||||||
JWT_SECRET=your_jwt_secret_here
|
JWT_SECRET=muyu-wms-jwt-secret-key-2026
|
||||||
JWT_REFRESH_SECRET=your_jwt_refresh_secret_here
|
JWT_REFRESH_SECRET=muyu-wms-jwt-refresh-2026
|
||||||
|
|
||||||
# muyu gRPC 地址
|
# muyu gRPC 地址
|
||||||
MUYU_SYSTEM_RPC_ADDR=system-rpc:9001
|
MUYU_SYSTEM_RPC_ADDR=system-rpc:9001
|
||||||
MUYU_INVENTORY_RPC_ADDR=inventory-rpc:9002
|
MUYU_INVENTORY_RPC_ADDR=inventory-rpc:9002
|
||||||
|
|
||||||
# 服务间调用密钥
|
# 服务间调用密钥
|
||||||
INTERNAL_SERVICE_KEY=your_internal_service_key_here
|
INTERNAL_SERVICE_KEY=internal-service-shared-secret
|
||||||
|
|
||||||
# 服务端口
|
# 服务端口
|
||||||
GATEWAY_PORT=8080
|
GATEWAY_PORT=8080
|
||||||
|
|||||||
@ -1,73 +0,0 @@
|
|||||||
stages:
|
|
||||||
- build-1
|
|
||||||
- build-2
|
|
||||||
- build-3
|
|
||||||
|
|
||||||
variables:
|
|
||||||
HARBOR_REGISTRY: harbor-in-k3s.cheverjohn.me
|
|
||||||
HARBOR_PROJECT: iloom
|
|
||||||
DOCKER_TLS_CERTDIR: "/certs"
|
|
||||||
DOCKER_HOST: tcp://localhost:2376
|
|
||||||
DOCKER_TLS_VERIFY: "1"
|
|
||||||
DOCKER_CERT_PATH: /certs/client
|
|
||||||
|
|
||||||
.build-template: &build-template
|
|
||||||
image: docker:27
|
|
||||||
services:
|
|
||||||
- name: docker:27-dind
|
|
||||||
command: ["--dns", "10.43.0.10", "--dns", "223.5.5.5"]
|
|
||||||
retry: 2
|
|
||||||
before_script:
|
|
||||||
- until docker info >/dev/null 2>&1; do echo "Waiting for Docker daemon..."; sleep 2; done
|
|
||||||
- echo "${HARBOR_PASS}" | docker login ${HARBOR_REGISTRY} -u "${HARBOR_USER}" --password-stdin
|
|
||||||
script:
|
|
||||||
- |
|
|
||||||
if [ -n "$CI_COMMIT_TAG" ]; then
|
|
||||||
TAG="$CI_COMMIT_TAG"
|
|
||||||
elif [ "$CI_COMMIT_BRANCH" = "main" ]; then
|
|
||||||
TAG="main-${CI_COMMIT_SHORT_SHA}"
|
|
||||||
else
|
|
||||||
TAG="feat-${CI_COMMIT_SHORT_SHA}"
|
|
||||||
fi
|
|
||||||
- docker build --network host -t ${HARBOR_REGISTRY}/${HARBOR_PROJECT}/${IMAGE_NAME}:${TAG} -f ${DOCKERFILE_PATH} .
|
|
||||||
- docker push ${HARBOR_REGISTRY}/${HARBOR_PROJECT}/${IMAGE_NAME}:${TAG}
|
|
||||||
rules:
|
|
||||||
- if: $CI_COMMIT_BRANCH == "main"
|
|
||||||
- if: $CI_COMMIT_TAG =~ /^v/
|
|
||||||
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
|
||||||
- if: $CI_COMMIT_BRANCH =~ /^feat\//
|
|
||||||
|
|
||||||
build-gateway:
|
|
||||||
<<: *build-template
|
|
||||||
stage: build-1
|
|
||||||
variables:
|
|
||||||
IMAGE_NAME: iloom-gateway
|
|
||||||
DOCKERFILE_PATH: gateway/Dockerfile
|
|
||||||
|
|
||||||
build-auth-service:
|
|
||||||
<<: *build-template
|
|
||||||
stage: build-1
|
|
||||||
variables:
|
|
||||||
IMAGE_NAME: iloom-auth-service
|
|
||||||
DOCKERFILE_PATH: auth-service/Dockerfile
|
|
||||||
|
|
||||||
build-purchaser-service:
|
|
||||||
<<: *build-template
|
|
||||||
stage: build-2
|
|
||||||
variables:
|
|
||||||
IMAGE_NAME: iloom-purchaser-service
|
|
||||||
DOCKERFILE_PATH: purchaser-service/Dockerfile
|
|
||||||
|
|
||||||
build-textile-service:
|
|
||||||
<<: *build-template
|
|
||||||
stage: build-2
|
|
||||||
variables:
|
|
||||||
IMAGE_NAME: iloom-textile-service
|
|
||||||
DOCKERFILE_PATH: textile-service/Dockerfile
|
|
||||||
|
|
||||||
build-washing-service:
|
|
||||||
<<: *build-template
|
|
||||||
stage: build-3
|
|
||||||
variables:
|
|
||||||
IMAGE_NAME: iloom-washing-service
|
|
||||||
DOCKERFILE_PATH: washing-service/Dockerfile
|
|
||||||
@ -1,16 +1,11 @@
|
|||||||
ARG BASE_REGISTRY=harbor-in-k3s.cheverjohn.me/library/
|
FROM golang:1.24-alpine AS builder
|
||||||
FROM ${BASE_REGISTRY}golang:1.24-alpine AS builder
|
|
||||||
ARG GOPROXY=https://goproxy.cn,direct
|
|
||||||
ENV GOPROXY=${GOPROXY}
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY shared/ ./shared/
|
COPY shared/ ./shared/
|
||||||
COPY auth-service/ ./auth-service/
|
COPY auth-service/ ./auth-service/
|
||||||
WORKDIR /app/auth-service
|
WORKDIR /app/auth-service
|
||||||
RUN go build -o /auth-service ./cmd/
|
RUN go build -o /auth-service ./cmd/
|
||||||
|
|
||||||
FROM ${BASE_REGISTRY}alpine:3.20
|
FROM alpine:3.20
|
||||||
ARG APK_MIRROR=mirrors.aliyun.com
|
|
||||||
RUN if [ -n "$APK_MIRROR" ]; then sed -i "s|dl-cdn.alpinelinux.org|$APK_MIRROR|g" /etc/apk/repositories; fi
|
|
||||||
RUN apk add --no-cache wget
|
RUN apk add --no-cache wget
|
||||||
COPY --from=builder /auth-service /auth-service
|
COPY --from=builder /auth-service /auth-service
|
||||||
EXPOSE 8081
|
EXPOSE 8081
|
||||||
|
|||||||
@ -9,7 +9,6 @@ import (
|
|||||||
"github.com/muyuqingfeng/iloom/auth-service/internal/handler"
|
"github.com/muyuqingfeng/iloom/auth-service/internal/handler"
|
||||||
"github.com/muyuqingfeng/iloom/auth-service/internal/repository"
|
"github.com/muyuqingfeng/iloom/auth-service/internal/repository"
|
||||||
"github.com/muyuqingfeng/iloom/auth-service/internal/service"
|
"github.com/muyuqingfeng/iloom/auth-service/internal/service"
|
||||||
"github.com/muyuqingfeng/iloom/auth-service/internal/storage"
|
|
||||||
"github.com/muyuqingfeng/iloom/shared/pkg/auth"
|
"github.com/muyuqingfeng/iloom/shared/pkg/auth"
|
||||||
"github.com/muyuqingfeng/iloom/shared/pkg/database"
|
"github.com/muyuqingfeng/iloom/shared/pkg/database"
|
||||||
sgrpc "github.com/muyuqingfeng/iloom/shared/pkg/grpc"
|
sgrpc "github.com/muyuqingfeng/iloom/shared/pkg/grpc"
|
||||||
@ -33,17 +32,6 @@ func main() {
|
|||||||
|
|
||||||
jwtMgr := auth.NewJWTManager(cfg.JWTSecret, cfg.JWTRefreshSecret)
|
jwtMgr := auth.NewJWTManager(cfg.JWTSecret, cfg.JWTRefreshSecret)
|
||||||
|
|
||||||
minioStorage, err := storage.NewMinIOStorage(storage.MinIOConfig{
|
|
||||||
Endpoint: cfg.MinIOEndpoint,
|
|
||||||
AccessKey: cfg.MinIOAccessKey,
|
|
||||||
SecretKey: cfg.MinIOSecretKey,
|
|
||||||
Bucket: cfg.MinIOBucket,
|
|
||||||
UseSSL: cfg.MinIOUseSSL,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("warning: minio init failed: %v (file upload disabled)", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
profileRepo := repository.NewProfileRepo(db)
|
profileRepo := repository.NewProfileRepo(db)
|
||||||
companyRepo := repository.NewCompanyRepo(db)
|
companyRepo := repository.NewCompanyRepo(db)
|
||||||
memberRepo := repository.NewMemberRepo(db)
|
memberRepo := repository.NewMemberRepo(db)
|
||||||
@ -60,7 +48,7 @@ func main() {
|
|||||||
companyH := handler.NewCompanyHandler(companySvc)
|
companyH := handler.NewCompanyHandler(companySvc)
|
||||||
memberH := handler.NewMemberHandler(memberSvc)
|
memberH := handler.NewMemberHandler(memberSvc)
|
||||||
notifH := handler.NewNotificationHandler(notifSvc)
|
notifH := handler.NewNotificationHandler(notifSvc)
|
||||||
commonH := handler.NewCommonHandler(shareLinkRepo, auditLogRepo, minioStorage)
|
commonH := handler.NewCommonHandler(shareLinkRepo, auditLogRepo)
|
||||||
|
|
||||||
r := gin.New()
|
r := gin.New()
|
||||||
r.Use(
|
r.Use(
|
||||||
@ -95,7 +83,6 @@ func main() {
|
|||||||
commonGroup.GET("/share-links/:code", commonH.GetShareLink)
|
commonGroup.GET("/share-links/:code", commonH.GetShareLink)
|
||||||
commonGroup.POST("/feedback", commonH.Feedback)
|
commonGroup.POST("/feedback", commonH.Feedback)
|
||||||
commonGroup.POST("/crash-reports", commonH.CrashReport)
|
commonGroup.POST("/crash-reports", commonH.CrashReport)
|
||||||
commonGroup.POST("/upload", commonH.Upload)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
addr := ":" + cfg.Port
|
addr := ":" + cfg.Port
|
||||||
|
|||||||
@ -11,7 +11,6 @@ replace github.com/muyuqingfeng/iloom/shared => ../shared
|
|||||||
require (
|
require (
|
||||||
github.com/gin-gonic/gin v1.10.0
|
github.com/gin-gonic/gin v1.10.0
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/minio/minio-go/v7 v7.0.82
|
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@ -20,26 +19,21 @@ require (
|
|||||||
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
||||||
github.com/cloudwego/base64x v0.1.4 // indirect
|
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
|
||||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||||
github.com/go-ini/ini v1.67.0 // indirect
|
|
||||||
github.com/go-playground/locales v0.14.1 // indirect
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
github.com/go-playground/validator/v10 v10.20.0 // indirect
|
github.com/go-playground/validator/v10 v10.20.0 // indirect
|
||||||
github.com/go-sql-driver/mysql v1.8.1 // indirect
|
github.com/go-sql-driver/mysql v1.8.1 // indirect
|
||||||
github.com/goccy/go-json v0.10.3 // indirect
|
github.com/goccy/go-json v0.10.2 // indirect
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
|
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/klauspost/compress v1.17.11 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.8 // indirect
|
|
||||||
github.com/leodido/go-urn v1.4.0 // indirect
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/minio/md5-simd v1.1.2 // indirect
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||||
github.com/rs/xid v1.6.0 // indirect
|
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||||
golang.org/x/arch v0.8.0 // indirect
|
golang.org/x/arch v0.8.0 // indirect
|
||||||
|
|||||||
@ -11,16 +11,12 @@ github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQ
|
|||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
|
||||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
|
||||||
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
||||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||||
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
|
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
|
||||||
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||||
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
|
|
||||||
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
|
||||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
@ -35,8 +31,8 @@ github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBEx
|
|||||||
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||||
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||||
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
|
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||||
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
|
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||||
@ -48,21 +44,14 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
|||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
|
|
||||||
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
|
||||||
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
|
||||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
|
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
||||||
github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
|
|
||||||
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
|
|
||||||
github.com/minio/minio-go/v7 v7.0.82 h1:tWfICLhmp2aFPXL8Tli0XDTHj2VB/fNf0PC1f/i1gRo=
|
|
||||||
github.com/minio/minio-go/v7 v7.0.82/go.mod h1:84gmIilaX4zcvAWWzJ5Z1WI5axN+hAbM5w25xf8xvC0=
|
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
@ -72,8 +61,6 @@ github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6
|
|||||||
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
|
|
||||||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
|||||||
@ -8,25 +8,15 @@ type Config struct {
|
|||||||
JWTSecret string
|
JWTSecret string
|
||||||
JWTRefreshSecret string
|
JWTRefreshSecret string
|
||||||
SystemRPCAddr string
|
SystemRPCAddr string
|
||||||
MinIOEndpoint string
|
|
||||||
MinIOAccessKey string
|
|
||||||
MinIOSecretKey string
|
|
||||||
MinIOBucket string
|
|
||||||
MinIOUseSSL bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Load() *Config {
|
func Load() *Config {
|
||||||
return &Config{
|
return &Config{
|
||||||
Port: getEnv("PORT", "8081"),
|
Port: getEnv("PORT", "8081"),
|
||||||
DBDSN: getEnv("DB_DSN", ""),
|
DBDSN: getEnv("DB_DSN", "root:muyu2026@tcp(127.0.0.1:3306)/muyu_wms?parseTime=true&charset=utf8mb4"),
|
||||||
JWTSecret: getEnv("JWT_SECRET", ""),
|
JWTSecret: getEnv("JWT_SECRET", "muyu-wms-jwt-secret-key-2026"),
|
||||||
JWTRefreshSecret: getEnv("JWT_REFRESH_SECRET", ""),
|
JWTRefreshSecret: getEnv("JWT_REFRESH_SECRET", "muyu-wms-jwt-refresh-2026"),
|
||||||
SystemRPCAddr: getEnv("MUYU_SYSTEM_RPC_ADDR", "127.0.0.1:9001"),
|
SystemRPCAddr: getEnv("MUYU_SYSTEM_RPC_ADDR", "127.0.0.1:9001"),
|
||||||
MinIOEndpoint: getEnv("MINIO_ENDPOINT", "minio:9000"),
|
|
||||||
MinIOAccessKey: getEnv("MINIO_ACCESS_KEY", "minioadmin"),
|
|
||||||
MinIOSecretKey: getEnv("MINIO_SECRET_KEY", "minioadmin123"),
|
|
||||||
MinIOBucket: getEnv("MINIO_BUCKET", "iloom-uploads"),
|
|
||||||
MinIOUseSSL: getEnv("MINIO_USE_SSL", "false") == "true",
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,24 +1,20 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/muyuqingfeng/iloom/auth-service/internal/repository"
|
"github.com/muyuqingfeng/iloom/auth-service/internal/repository"
|
||||||
"github.com/muyuqingfeng/iloom/auth-service/internal/storage"
|
|
||||||
"github.com/muyuqingfeng/iloom/shared/pkg/response"
|
"github.com/muyuqingfeng/iloom/shared/pkg/response"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CommonHandler struct {
|
type CommonHandler struct {
|
||||||
shareLinkRepo *repository.ShareLinkRepo
|
shareLinkRepo *repository.ShareLinkRepo
|
||||||
auditLogRepo *repository.AuditLogRepo
|
auditLogRepo *repository.AuditLogRepo
|
||||||
storage *storage.MinIOStorage
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCommonHandler(slRepo *repository.ShareLinkRepo, alRepo *repository.AuditLogRepo, st *storage.MinIOStorage) *CommonHandler {
|
func NewCommonHandler(slRepo *repository.ShareLinkRepo, alRepo *repository.AuditLogRepo) *CommonHandler {
|
||||||
return &CommonHandler{shareLinkRepo: slRepo, auditLogRepo: alRepo, storage: st}
|
return &CommonHandler{shareLinkRepo: slRepo, auditLogRepo: alRepo}
|
||||||
}
|
}
|
||||||
|
|
||||||
type createShareLinkReq struct {
|
type createShareLinkReq struct {
|
||||||
@ -136,48 +132,3 @@ func (h *CommonHandler) CrashReport(c *gin.Context) {
|
|||||||
|
|
||||||
response.OK(c, gin.H{"message": "crash report received"})
|
response.OK(c, gin.H{"message": "crash report received"})
|
||||||
}
|
}
|
||||||
|
|
||||||
const maxUploadSize = 10 << 20 // 10 MB
|
|
||||||
|
|
||||||
func (h *CommonHandler) Upload(c *gin.Context) {
|
|
||||||
if h.storage == nil {
|
|
||||||
response.InternalError(c, "file storage not configured")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := c.Request.ParseMultipartForm(maxUploadSize); err != nil {
|
|
||||||
response.BadRequest(c, fmt.Sprintf("file too large (max %dMB)", maxUploadSize>>20))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
file, header, err := c.Request.FormFile("file")
|
|
||||||
if err != nil {
|
|
||||||
response.BadRequest(c, "missing file field")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
// Validate content type
|
|
||||||
contentType := header.Header.Get("Content-Type")
|
|
||||||
if !strings.HasPrefix(contentType, "image/") {
|
|
||||||
response.BadRequest(c, "only image files are allowed")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate size
|
|
||||||
if header.Size > maxUploadSize {
|
|
||||||
response.BadRequest(c, fmt.Sprintf("file too large (max %dMB)", maxUploadSize>>20))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
pathPrefix := c.DefaultPostForm("path_prefix", "products")
|
|
||||||
|
|
||||||
objectPath, err := h.storage.Upload(c.Request.Context(), pathPrefix, header.Filename, file, header.Size, contentType)
|
|
||||||
if err != nil {
|
|
||||||
response.InternalError(c, "upload failed: "+err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
url := "/uploads/" + objectPath
|
|
||||||
response.OK(c, gin.H{"url": url})
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,101 +0,0 @@
|
|||||||
package storage
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"github.com/minio/minio-go/v7"
|
|
||||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
|
||||||
)
|
|
||||||
|
|
||||||
type MinIOStorage struct {
|
|
||||||
client *minio.Client
|
|
||||||
bucket string
|
|
||||||
}
|
|
||||||
|
|
||||||
type MinIOConfig struct {
|
|
||||||
Endpoint string
|
|
||||||
AccessKey string
|
|
||||||
SecretKey string
|
|
||||||
Bucket string
|
|
||||||
UseSSL bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewMinIOStorage(cfg MinIOConfig) (*MinIOStorage, error) {
|
|
||||||
client, err := minio.New(cfg.Endpoint, &minio.Options{
|
|
||||||
Creds: credentials.NewStaticV4(cfg.AccessKey, cfg.SecretKey, ""),
|
|
||||||
Secure: cfg.UseSSL,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("create minio client: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
exists, err := client.BucketExists(ctx, cfg.Bucket)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("check bucket: %w", err)
|
|
||||||
}
|
|
||||||
if !exists {
|
|
||||||
if err := client.MakeBucket(ctx, cfg.Bucket, minio.MakeBucketOptions{}); err != nil {
|
|
||||||
return nil, fmt.Errorf("create bucket: %w", err)
|
|
||||||
}
|
|
||||||
log.Printf("created minio bucket: %s", cfg.Bucket)
|
|
||||||
|
|
||||||
// Set bucket policy to allow public read
|
|
||||||
policy := fmt.Sprintf(`{
|
|
||||||
"Version": "2012-10-17",
|
|
||||||
"Statement": [{
|
|
||||||
"Effect": "Allow",
|
|
||||||
"Principal": {"AWS": ["*"]},
|
|
||||||
"Action": ["s3:GetObject"],
|
|
||||||
"Resource": ["arn:aws:s3:::%s/*"]
|
|
||||||
}]
|
|
||||||
}`, cfg.Bucket)
|
|
||||||
if err := client.SetBucketPolicy(ctx, cfg.Bucket, policy); err != nil {
|
|
||||||
log.Printf("warning: set bucket policy: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &MinIOStorage{client: client, bucket: cfg.Bucket}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Upload stores a file in MinIO and returns the object path (without bucket prefix).
|
|
||||||
// The returned path can be used to construct the public URL: /uploads/{path}
|
|
||||||
func (s *MinIOStorage) Upload(ctx context.Context, pathPrefix string, filename string, reader io.Reader, size int64, contentType string) (string, error) {
|
|
||||||
ext := filepath.Ext(filename)
|
|
||||||
if ext == "" {
|
|
||||||
ext = guessExt(contentType)
|
|
||||||
}
|
|
||||||
objectName := fmt.Sprintf("%s/%s%s", pathPrefix, uuid.New().String(), ext)
|
|
||||||
|
|
||||||
_, err := s.client.PutObject(ctx, s.bucket, objectName, reader, size, minio.PutObjectOptions{
|
|
||||||
ContentType: contentType,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("put object: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return objectName, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func guessExt(contentType string) string {
|
|
||||||
switch {
|
|
||||||
case strings.HasPrefix(contentType, "image/jpeg"):
|
|
||||||
return ".jpg"
|
|
||||||
case strings.HasPrefix(contentType, "image/png"):
|
|
||||||
return ".png"
|
|
||||||
case strings.HasPrefix(contentType, "image/gif"):
|
|
||||||
return ".gif"
|
|
||||||
case strings.HasPrefix(contentType, "image/webp"):
|
|
||||||
return ".webp"
|
|
||||||
case strings.HasPrefix(contentType, "image/svg"):
|
|
||||||
return ".svg"
|
|
||||||
default:
|
|
||||||
return ".bin"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -6,15 +6,11 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: gateway/Dockerfile
|
dockerfile: gateway/Dockerfile
|
||||||
args:
|
|
||||||
BASE_REGISTRY: ${BASE_REGISTRY-harbor-in-k3s.cheverjohn.me/library/}
|
|
||||||
GOPROXY: ${GOPROXY-https://goproxy.cn,direct}
|
|
||||||
APK_MIRROR: ${APK_MIRROR-mirrors.aliyun.com}
|
|
||||||
ports:
|
ports:
|
||||||
- "${GATEWAY_PORT:-8080}:8080"
|
- "${GATEWAY_PORT:-8080}:8080"
|
||||||
environment:
|
environment:
|
||||||
- PORT=8080
|
- PORT=8080
|
||||||
- JWT_SECRET=${JWT_SECRET:?Set JWT_SECRET in .env}
|
- JWT_SECRET=${JWT_SECRET:-muyu-wms-jwt-secret-key-2026}
|
||||||
- AUTH_SERVICE_URL=http://auth-service:8081
|
- AUTH_SERVICE_URL=http://auth-service:8081
|
||||||
- PURCHASER_SERVICE_URL=http://purchaser-service:8082
|
- PURCHASER_SERVICE_URL=http://purchaser-service:8082
|
||||||
- TEXTILE_SERVICE_URL=http://textile-service:8083
|
- TEXTILE_SERVICE_URL=http://textile-service:8083
|
||||||
@ -37,17 +33,13 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: auth-service/Dockerfile
|
dockerfile: auth-service/Dockerfile
|
||||||
args:
|
|
||||||
BASE_REGISTRY: ${BASE_REGISTRY-harbor-in-k3s.cheverjohn.me/library/}
|
|
||||||
GOPROXY: ${GOPROXY-https://goproxy.cn,direct}
|
|
||||||
APK_MIRROR: ${APK_MIRROR-mirrors.aliyun.com}
|
|
||||||
ports:
|
ports:
|
||||||
- "${AUTH_SERVICE_PORT:-8081}:8081"
|
- "${AUTH_SERVICE_PORT:-8081}:8081"
|
||||||
environment:
|
environment:
|
||||||
- PORT=8081
|
- PORT=8081
|
||||||
- DB_DSN=${DB_DSN:?Set DB_DSN in .env}
|
- DB_DSN=root:muyu2026@tcp(muyu-mysql:3306)/muyu_wms?parseTime=true&charset=utf8mb4
|
||||||
- JWT_SECRET=${JWT_SECRET:?Set JWT_SECRET in .env}
|
- JWT_SECRET=${JWT_SECRET:-muyu-wms-jwt-secret-key-2026}
|
||||||
- JWT_REFRESH_SECRET=${JWT_REFRESH_SECRET:?Set JWT_REFRESH_SECRET in .env}
|
- JWT_REFRESH_SECRET=${JWT_REFRESH_SECRET:-muyu-wms-jwt-refresh-2026}
|
||||||
- MUYU_SYSTEM_RPC_ADDR=muyu-system-rpc:9001
|
- MUYU_SYSTEM_RPC_ADDR=muyu-system-rpc:9001
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "wget", "-qO-", "http://localhost:8081/health"]
|
test: ["CMD", "wget", "-qO-", "http://localhost:8081/health"]
|
||||||
@ -62,16 +54,12 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: purchaser-service/Dockerfile
|
dockerfile: purchaser-service/Dockerfile
|
||||||
args:
|
|
||||||
BASE_REGISTRY: ${BASE_REGISTRY-harbor-in-k3s.cheverjohn.me/library/}
|
|
||||||
GOPROXY: ${GOPROXY-https://goproxy.cn,direct}
|
|
||||||
APK_MIRROR: ${APK_MIRROR-mirrors.aliyun.com}
|
|
||||||
ports:
|
ports:
|
||||||
- "${PURCHASER_SERVICE_PORT:-8082}:8082"
|
- "${PURCHASER_SERVICE_PORT:-8082}:8082"
|
||||||
environment:
|
environment:
|
||||||
- PORT=8082
|
- PORT=8082
|
||||||
- DB_DSN=${DB_DSN:?Set DB_DSN in .env}
|
- DB_DSN=root:muyu2026@tcp(muyu-mysql:3306)/muyu_wms?parseTime=true&charset=utf8mb4
|
||||||
- INTERNAL_SERVICE_KEY=${INTERNAL_SERVICE_KEY:?Set INTERNAL_SERVICE_KEY in .env}
|
- INTERNAL_SERVICE_KEY=${INTERNAL_SERVICE_KEY:-internal-service-shared-secret}
|
||||||
- MUYU_INVENTORY_RPC_ADDR=muyu-inventory-rpc:9002
|
- MUYU_INVENTORY_RPC_ADDR=muyu-inventory-rpc:9002
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "wget", "-qO-", "http://localhost:8082/health"]
|
test: ["CMD", "wget", "-qO-", "http://localhost:8082/health"]
|
||||||
@ -86,16 +74,12 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: textile-service/Dockerfile
|
dockerfile: textile-service/Dockerfile
|
||||||
args:
|
|
||||||
BASE_REGISTRY: ${BASE_REGISTRY-harbor-in-k3s.cheverjohn.me/library/}
|
|
||||||
GOPROXY: ${GOPROXY-https://goproxy.cn,direct}
|
|
||||||
APK_MIRROR: ${APK_MIRROR-mirrors.aliyun.com}
|
|
||||||
ports:
|
ports:
|
||||||
- "${TEXTILE_SERVICE_PORT:-8083}:8083"
|
- "${TEXTILE_SERVICE_PORT:-8083}:8083"
|
||||||
environment:
|
environment:
|
||||||
- PORT=8083
|
- PORT=8083
|
||||||
- DB_DSN=${DB_DSN:?Set DB_DSN in .env}
|
- DB_DSN=root:muyu2026@tcp(muyu-mysql:3306)/muyu_wms?parseTime=true&charset=utf8mb4
|
||||||
- INTERNAL_SERVICE_KEY=${INTERNAL_SERVICE_KEY:?Set INTERNAL_SERVICE_KEY in .env}
|
- INTERNAL_SERVICE_KEY=${INTERNAL_SERVICE_KEY:-internal-service-shared-secret}
|
||||||
- PURCHASER_SERVICE_URL=http://purchaser-service:8082
|
- PURCHASER_SERVICE_URL=http://purchaser-service:8082
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "wget", "-qO-", "http://localhost:8083/health"]
|
test: ["CMD", "wget", "-qO-", "http://localhost:8083/health"]
|
||||||
@ -110,16 +94,12 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: washing-service/Dockerfile
|
dockerfile: washing-service/Dockerfile
|
||||||
args:
|
|
||||||
BASE_REGISTRY: ${BASE_REGISTRY-harbor-in-k3s.cheverjohn.me/library/}
|
|
||||||
GOPROXY: ${GOPROXY-https://goproxy.cn,direct}
|
|
||||||
APK_MIRROR: ${APK_MIRROR-mirrors.aliyun.com}
|
|
||||||
ports:
|
ports:
|
||||||
- "${WASHING_SERVICE_PORT:-8084}:8084"
|
- "${WASHING_SERVICE_PORT:-8084}:8084"
|
||||||
environment:
|
environment:
|
||||||
- PORT=8084
|
- PORT=8084
|
||||||
- DB_DSN=${DB_DSN:?Set DB_DSN in .env}
|
- DB_DSN=root:muyu2026@tcp(muyu-mysql:3306)/muyu_wms?parseTime=true&charset=utf8mb4
|
||||||
- INTERNAL_SERVICE_KEY=${INTERNAL_SERVICE_KEY:?Set INTERNAL_SERVICE_KEY in .env}
|
- INTERNAL_SERVICE_KEY=${INTERNAL_SERVICE_KEY:-internal-service-shared-secret}
|
||||||
- PURCHASER_SERVICE_URL=http://purchaser-service:8082
|
- PURCHASER_SERVICE_URL=http://purchaser-service:8082
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "wget", "-qO-", "http://localhost:8084/health"]
|
test: ["CMD", "wget", "-qO-", "http://localhost:8084/health"]
|
||||||
@ -132,10 +112,8 @@ services:
|
|||||||
|
|
||||||
frontend:
|
frontend:
|
||||||
build:
|
build:
|
||||||
context: ./iloom-flatten
|
context: .
|
||||||
dockerfile: Dockerfile
|
dockerfile: iloom-flatten/Dockerfile
|
||||||
args:
|
|
||||||
BASE_REGISTRY: ${BASE_REGISTRY-harbor-in-k3s.cheverjohn.me/library/}
|
|
||||||
ports:
|
ports:
|
||||||
- "${FRONTEND_PORT:-3015}:3015"
|
- "${FRONTEND_PORT:-3015}:3015"
|
||||||
depends_on:
|
depends_on:
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
# 集成模式(与 muyu-apiserver 联合):
|
# 集成模式(与 muyu-apiserver 联合):
|
||||||
# 1. cd ../muyu-apiserver/deploy && docker compose up
|
# 1. cd ../muyu-apiserver/deploy && docker compose up
|
||||||
# 2. 设置环境变量指向 muyu 的服务:
|
# 2. 设置环境变量指向 muyu 的服务:
|
||||||
# export DB_DSN="root:yourpassword@tcp(muyu-mysql:3306)/muyu_wms?parseTime=true&charset=utf8mb4"
|
# export DB_DSN="root:muyu2026@tcp(muyu-mysql:3306)/muyu_wms?parseTime=true&charset=utf8mb4"
|
||||||
# export MUYU_SYSTEM_RPC_ADDR=muyu-system-rpc:9001
|
# export MUYU_SYSTEM_RPC_ADDR=muyu-system-rpc:9001
|
||||||
# export MUYU_INVENTORY_RPC_ADDR=muyu-inventory-rpc:9002
|
# export MUYU_INVENTORY_RPC_ADDR=muyu-inventory-rpc:9002
|
||||||
# 3. docker compose --profile integrated up
|
# 3. docker compose --profile integrated up
|
||||||
@ -15,7 +15,7 @@ services:
|
|||||||
mysql:
|
mysql:
|
||||||
image: mysql:8.0
|
image: mysql:8.0
|
||||||
environment:
|
environment:
|
||||||
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:?Set MYSQL_ROOT_PASSWORD in .env}
|
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-muyu2026}
|
||||||
MYSQL_DATABASE: ${MYSQL_DATABASE:-muyu_wms}
|
MYSQL_DATABASE: ${MYSQL_DATABASE:-muyu_wms}
|
||||||
TZ: Asia/Shanghai
|
TZ: Asia/Shanghai
|
||||||
ports:
|
ports:
|
||||||
@ -26,7 +26,7 @@ services:
|
|||||||
- ./scripts/init-iloom.sql:/docker-entrypoint-initdb.d/02-iloom.sql
|
- ./scripts/init-iloom.sql:/docker-entrypoint-initdb.d/02-iloom.sql
|
||||||
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
|
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-uroot", "-p${MYSQL_ROOT_PASSWORD}"]
|
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-uroot", "-p${MYSQL_ROOT_PASSWORD:-muyu2026}"]
|
||||||
interval: 5s
|
interval: 5s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 10
|
retries: 10
|
||||||
@ -35,15 +35,11 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: gateway/Dockerfile
|
dockerfile: gateway/Dockerfile
|
||||||
args:
|
|
||||||
BASE_REGISTRY: ${BASE_REGISTRY-harbor-in-k3s.cheverjohn.me/library/}
|
|
||||||
GOPROXY: ${GOPROXY-https://goproxy.cn,direct}
|
|
||||||
APK_MIRROR: ${APK_MIRROR-mirrors.aliyun.com}
|
|
||||||
ports:
|
ports:
|
||||||
- "${GATEWAY_PORT:-8080}:8080"
|
- "${GATEWAY_PORT:-8080}:8080"
|
||||||
environment:
|
environment:
|
||||||
- PORT=8080
|
- PORT=8080
|
||||||
- JWT_SECRET=${JWT_SECRET:?Set JWT_SECRET in .env}
|
- JWT_SECRET=${JWT_SECRET:-muyu-wms-jwt-secret-key-2026}
|
||||||
- AUTH_SERVICE_URL=http://auth-service:8081
|
- AUTH_SERVICE_URL=http://auth-service:8081
|
||||||
- PURCHASER_SERVICE_URL=http://purchaser-service:8082
|
- PURCHASER_SERVICE_URL=http://purchaser-service:8082
|
||||||
- TEXTILE_SERVICE_URL=http://textile-service:8083
|
- TEXTILE_SERVICE_URL=http://textile-service:8083
|
||||||
@ -63,17 +59,13 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: auth-service/Dockerfile
|
dockerfile: auth-service/Dockerfile
|
||||||
args:
|
|
||||||
BASE_REGISTRY: ${BASE_REGISTRY-harbor-in-k3s.cheverjohn.me/library/}
|
|
||||||
GOPROXY: ${GOPROXY-https://goproxy.cn,direct}
|
|
||||||
APK_MIRROR: ${APK_MIRROR-mirrors.aliyun.com}
|
|
||||||
ports:
|
ports:
|
||||||
- "${AUTH_SERVICE_PORT:-8081}:8081"
|
- "${AUTH_SERVICE_PORT:-8081}:8081"
|
||||||
environment:
|
environment:
|
||||||
- PORT=8081
|
- PORT=8081
|
||||||
- DB_DSN=${DB_DSN:?Set DB_DSN in .env}
|
- DB_DSN=${DB_DSN:-root:muyu2026@tcp(mysql:3306)/muyu_wms?parseTime=true&charset=utf8mb4}
|
||||||
- JWT_SECRET=${JWT_SECRET:?Set JWT_SECRET in .env}
|
- JWT_SECRET=${JWT_SECRET:-muyu-wms-jwt-secret-key-2026}
|
||||||
- JWT_REFRESH_SECRET=${JWT_REFRESH_SECRET:?Set JWT_REFRESH_SECRET in .env}
|
- JWT_REFRESH_SECRET=${JWT_REFRESH_SECRET:-muyu-wms-jwt-refresh-2026}
|
||||||
- MUYU_SYSTEM_RPC_ADDR=${MUYU_SYSTEM_RPC_ADDR:-system-rpc:9001}
|
- MUYU_SYSTEM_RPC_ADDR=${MUYU_SYSTEM_RPC_ADDR:-system-rpc:9001}
|
||||||
depends_on:
|
depends_on:
|
||||||
mysql:
|
mysql:
|
||||||
@ -88,16 +80,12 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: purchaser-service/Dockerfile
|
dockerfile: purchaser-service/Dockerfile
|
||||||
args:
|
|
||||||
BASE_REGISTRY: ${BASE_REGISTRY-harbor-in-k3s.cheverjohn.me/library/}
|
|
||||||
GOPROXY: ${GOPROXY-https://goproxy.cn,direct}
|
|
||||||
APK_MIRROR: ${APK_MIRROR-mirrors.aliyun.com}
|
|
||||||
ports:
|
ports:
|
||||||
- "${PURCHASER_SERVICE_PORT:-8082}:8082"
|
- "${PURCHASER_SERVICE_PORT:-8082}:8082"
|
||||||
environment:
|
environment:
|
||||||
- PORT=8082
|
- PORT=8082
|
||||||
- DB_DSN=${DB_DSN:?Set DB_DSN in .env}
|
- DB_DSN=${DB_DSN:-root:muyu2026@tcp(mysql:3306)/muyu_wms?parseTime=true&charset=utf8mb4}
|
||||||
- INTERNAL_SERVICE_KEY=${INTERNAL_SERVICE_KEY:?Set INTERNAL_SERVICE_KEY in .env}
|
- INTERNAL_SERVICE_KEY=${INTERNAL_SERVICE_KEY:-internal-service-shared-secret}
|
||||||
- MUYU_INVENTORY_RPC_ADDR=${MUYU_INVENTORY_RPC_ADDR:-inventory-rpc:9002}
|
- MUYU_INVENTORY_RPC_ADDR=${MUYU_INVENTORY_RPC_ADDR:-inventory-rpc:9002}
|
||||||
depends_on:
|
depends_on:
|
||||||
mysql:
|
mysql:
|
||||||
@ -112,16 +100,12 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: textile-service/Dockerfile
|
dockerfile: textile-service/Dockerfile
|
||||||
args:
|
|
||||||
BASE_REGISTRY: ${BASE_REGISTRY-harbor-in-k3s.cheverjohn.me/library/}
|
|
||||||
GOPROXY: ${GOPROXY-https://goproxy.cn,direct}
|
|
||||||
APK_MIRROR: ${APK_MIRROR-mirrors.aliyun.com}
|
|
||||||
ports:
|
ports:
|
||||||
- "${TEXTILE_SERVICE_PORT:-8083}:8083"
|
- "${TEXTILE_SERVICE_PORT:-8083}:8083"
|
||||||
environment:
|
environment:
|
||||||
- PORT=8083
|
- PORT=8083
|
||||||
- DB_DSN=${DB_DSN:?Set DB_DSN in .env}
|
- DB_DSN=${DB_DSN:-root:muyu2026@tcp(mysql:3306)/muyu_wms?parseTime=true&charset=utf8mb4}
|
||||||
- INTERNAL_SERVICE_KEY=${INTERNAL_SERVICE_KEY:?Set INTERNAL_SERVICE_KEY in .env}
|
- INTERNAL_SERVICE_KEY=${INTERNAL_SERVICE_KEY:-internal-service-shared-secret}
|
||||||
- PURCHASER_SERVICE_URL=http://purchaser-service:8082
|
- PURCHASER_SERVICE_URL=http://purchaser-service:8082
|
||||||
depends_on:
|
depends_on:
|
||||||
mysql:
|
mysql:
|
||||||
@ -136,16 +120,12 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: washing-service/Dockerfile
|
dockerfile: washing-service/Dockerfile
|
||||||
args:
|
|
||||||
BASE_REGISTRY: ${BASE_REGISTRY-harbor-in-k3s.cheverjohn.me/library/}
|
|
||||||
GOPROXY: ${GOPROXY-https://goproxy.cn,direct}
|
|
||||||
APK_MIRROR: ${APK_MIRROR-mirrors.aliyun.com}
|
|
||||||
ports:
|
ports:
|
||||||
- "${WASHING_SERVICE_PORT:-8084}:8084"
|
- "${WASHING_SERVICE_PORT:-8084}:8084"
|
||||||
environment:
|
environment:
|
||||||
- PORT=8084
|
- PORT=8084
|
||||||
- DB_DSN=${DB_DSN:?Set DB_DSN in .env}
|
- DB_DSN=${DB_DSN:-root:muyu2026@tcp(mysql:3306)/muyu_wms?parseTime=true&charset=utf8mb4}
|
||||||
- INTERNAL_SERVICE_KEY=${INTERNAL_SERVICE_KEY:?Set INTERNAL_SERVICE_KEY in .env}
|
- INTERNAL_SERVICE_KEY=${INTERNAL_SERVICE_KEY:-internal-service-shared-secret}
|
||||||
- PURCHASER_SERVICE_URL=http://purchaser-service:8082
|
- PURCHASER_SERVICE_URL=http://purchaser-service:8082
|
||||||
depends_on:
|
depends_on:
|
||||||
mysql:
|
mysql:
|
||||||
@ -158,10 +138,8 @@ services:
|
|||||||
|
|
||||||
frontend:
|
frontend:
|
||||||
build:
|
build:
|
||||||
context: ./iloom-flatten
|
context: .
|
||||||
dockerfile: Dockerfile
|
dockerfile: iloom-flatten/Dockerfile
|
||||||
args:
|
|
||||||
BASE_REGISTRY: ${BASE_REGISTRY-harbor-in-k3s.cheverjohn.me/library/}
|
|
||||||
ports:
|
ports:
|
||||||
- "${FRONTEND_PORT:-3015}:3015"
|
- "${FRONTEND_PORT:-3015}:3015"
|
||||||
depends_on:
|
depends_on:
|
||||||
|
|||||||
@ -1,16 +1,11 @@
|
|||||||
ARG BASE_REGISTRY=harbor-in-k3s.cheverjohn.me/library/
|
FROM golang:1.24-alpine AS builder
|
||||||
FROM ${BASE_REGISTRY}golang:1.24-alpine AS builder
|
|
||||||
ARG GOPROXY=https://goproxy.cn,direct
|
|
||||||
ENV GOPROXY=${GOPROXY}
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY shared/ ./shared/
|
COPY shared/ ./shared/
|
||||||
COPY gateway/ ./gateway/
|
COPY gateway/ ./gateway/
|
||||||
WORKDIR /app/gateway
|
WORKDIR /app/gateway
|
||||||
RUN go build -o /gateway ./cmd/
|
RUN go build -o /gateway ./cmd/
|
||||||
|
|
||||||
FROM ${BASE_REGISTRY}alpine:3.20
|
FROM alpine:3.20
|
||||||
ARG APK_MIRROR=mirrors.aliyun.com
|
|
||||||
RUN if [ -n "$APK_MIRROR" ]; then sed -i "s|dl-cdn.alpinelinux.org|$APK_MIRROR|g" /etc/apk/repositories; fi
|
|
||||||
RUN apk add --no-cache wget
|
RUN apk add --no-cache wget
|
||||||
COPY --from=builder /gateway /gateway
|
COPY --from=builder /gateway /gateway
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
|
|||||||
@ -29,17 +29,8 @@ func main() {
|
|||||||
|
|
||||||
v1 := r.Group("/api/v1")
|
v1 := r.Group("/api/v1")
|
||||||
{
|
{
|
||||||
// /api/v1/auth/* -> AUTH_SERVICE_URL
|
// /api/v1/auth/* -> AUTH_SERVICE_URL (no auth required)
|
||||||
// Public auth endpoints (no JWT required)
|
v1.Any("/auth/*path", proxy.NewReverseProxy(cfg.AuthServiceURL))
|
||||||
authPublic := v1.Group("/auth")
|
|
||||||
authPublic.POST("/register", proxy.NewReverseProxy(cfg.AuthServiceURL))
|
|
||||||
authPublic.POST("/login", proxy.NewReverseProxy(cfg.AuthServiceURL))
|
|
||||||
authPublic.POST("/logout", proxy.NewReverseProxy(cfg.AuthServiceURL))
|
|
||||||
authPublic.POST("/refresh", proxy.NewReverseProxy(cfg.AuthServiceURL))
|
|
||||||
|
|
||||||
// Protected auth endpoints (JWT required to set X-User-ID)
|
|
||||||
authProtected := v1.Group("/auth", middleware.AuthRequired(cfg.JWTSecret))
|
|
||||||
authProtected.GET("/me", proxy.NewReverseProxy(cfg.AuthServiceURL))
|
|
||||||
|
|
||||||
// /api/v1/common/* -> AUTH_SERVICE_URL (JWT required)
|
// /api/v1/common/* -> AUTH_SERVICE_URL (JWT required)
|
||||||
common := v1.Group("/common", middleware.AuthRequired(cfg.JWTSecret))
|
common := v1.Group("/common", middleware.AuthRequired(cfg.JWTSecret))
|
||||||
@ -65,31 +56,12 @@ func main() {
|
|||||||
middleware.RoleRequired("washing"),
|
middleware.RoleRequired("washing"),
|
||||||
)
|
)
|
||||||
washing.Any("/*path", proxy.NewReverseProxy(cfg.WashingServiceURL))
|
washing.Any("/*path", proxy.NewReverseProxy(cfg.WashingServiceURL))
|
||||||
|
|
||||||
// /api/v1/muyu/* -> MUYU_GATEWAY_URL (muyu warehouse subsystem)
|
|
||||||
// muyu-gateway handles its own authentication internally
|
|
||||||
muyu := v1.Group("/muyu",
|
|
||||||
middleware.AuthRequired(cfg.JWTSecret),
|
|
||||||
)
|
|
||||||
muyu.Any("/*path", proxy.NewReverseProxy(cfg.MuyuGatewayURL))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// WebSocket proxy — route by role (JWT + role check applied)
|
// WebSocket proxy — route by role
|
||||||
r.GET("/ws/v1/purchaser",
|
r.GET("/ws/v1/purchaser", proxy.NewWSProxy(cfg.PurchaserServiceURL))
|
||||||
middleware.AuthRequired(cfg.JWTSecret),
|
r.GET("/ws/v1/textile", proxy.NewWSProxy(cfg.TextileServiceURL))
|
||||||
middleware.RoleRequired("purchaser"),
|
r.GET("/ws/v1/washing", proxy.NewWSProxy(cfg.WashingServiceURL))
|
||||||
proxy.NewWSProxy(cfg.PurchaserServiceURL, cfg.AllowOrigins),
|
|
||||||
)
|
|
||||||
r.GET("/ws/v1/textile",
|
|
||||||
middleware.AuthRequired(cfg.JWTSecret),
|
|
||||||
middleware.RoleRequired("textile"),
|
|
||||||
proxy.NewWSProxy(cfg.TextileServiceURL, cfg.AllowOrigins),
|
|
||||||
)
|
|
||||||
r.GET("/ws/v1/washing",
|
|
||||||
middleware.AuthRequired(cfg.JWTSecret),
|
|
||||||
middleware.RoleRequired("washing"),
|
|
||||||
proxy.NewWSProxy(cfg.WashingServiceURL, cfg.AllowOrigins),
|
|
||||||
)
|
|
||||||
|
|
||||||
addr := ":" + cfg.Port
|
addr := ":" + cfg.Port
|
||||||
log.Printf("gateway listening on %s", addr)
|
log.Printf("gateway listening on %s", addr)
|
||||||
|
|||||||
@ -12,19 +12,17 @@ type Config struct {
|
|||||||
PurchaserServiceURL string
|
PurchaserServiceURL string
|
||||||
TextileServiceURL string
|
TextileServiceURL string
|
||||||
WashingServiceURL string
|
WashingServiceURL string
|
||||||
MuyuGatewayURL string
|
|
||||||
AllowOrigins []string
|
AllowOrigins []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func Load() *Config {
|
func Load() *Config {
|
||||||
return &Config{
|
return &Config{
|
||||||
Port: getEnv("PORT", "8080"),
|
Port: getEnv("PORT", "8080"),
|
||||||
JWTSecret: getEnv("JWT_SECRET", ""),
|
JWTSecret: getEnv("JWT_SECRET", "muyu-wms-jwt-secret-key-2026"),
|
||||||
AuthServiceURL: getEnv("AUTH_SERVICE_URL", "http://localhost:8081"),
|
AuthServiceURL: getEnv("AUTH_SERVICE_URL", "http://localhost:8081"),
|
||||||
PurchaserServiceURL: getEnv("PURCHASER_SERVICE_URL", "http://localhost:8082"),
|
PurchaserServiceURL: getEnv("PURCHASER_SERVICE_URL", "http://localhost:8082"),
|
||||||
TextileServiceURL: getEnv("TEXTILE_SERVICE_URL", "http://localhost:8083"),
|
TextileServiceURL: getEnv("TEXTILE_SERVICE_URL", "http://localhost:8083"),
|
||||||
WashingServiceURL: getEnv("WASHING_SERVICE_URL", "http://localhost:8084"),
|
WashingServiceURL: getEnv("WASHING_SERVICE_URL", "http://localhost:8084"),
|
||||||
MuyuGatewayURL: getEnv("MUYU_GATEWAY_URL", "http://localhost:8888"),
|
|
||||||
AllowOrigins: strings.Split(getEnv("ALLOW_ORIGINS", "*"), ","),
|
AllowOrigins: strings.Split(getEnv("ALLOW_ORIGINS", "*"), ","),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,29 +11,11 @@ import (
|
|||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newUpgrader(allowedOrigins []string) websocket.Upgrader {
|
var upgrader = websocket.Upgrader{
|
||||||
originSet := make(map[string]bool, len(allowedOrigins))
|
CheckOrigin: func(r *http.Request) bool { return true },
|
||||||
for _, o := range allowedOrigins {
|
|
||||||
originSet[strings.TrimSpace(o)] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
return websocket.Upgrader{
|
|
||||||
CheckOrigin: func(r *http.Request) bool {
|
|
||||||
if originSet["*"] {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
origin := r.Header.Get("Origin")
|
|
||||||
if origin == "" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return originSet[origin]
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewWSProxy(targetURL string, allowedOrigins []string) gin.HandlerFunc {
|
func NewWSProxy(targetURL string) gin.HandlerFunc {
|
||||||
upgrader := newUpgrader(allowedOrigins)
|
|
||||||
|
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
clientConn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
|
clientConn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -68,7 +50,6 @@ func NewWSProxy(targetURL string, allowedOrigins []string) gin.HandlerFunc {
|
|||||||
go pumpMessages(backendConn, clientConn, errCh)
|
go pumpMessages(backendConn, clientConn, errCh)
|
||||||
|
|
||||||
<-errCh
|
<-errCh
|
||||||
<-errCh
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
Subproject commit d085054437c10ef8bf09c983d115e97b0946881d
|
Subproject commit 3093ae18c25f6d654e1c0c3a6cd546c04a5438d1
|
||||||
@ -21,17 +21,6 @@ data:
|
|||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
client_max_body_size 10m;
|
|
||||||
}
|
|
||||||
|
|
||||||
location ^~ /uploads/ {
|
|
||||||
resolver 10.43.0.10 valid=30s;
|
|
||||||
set $minio_host "minio.iloom.svc.cluster.local";
|
|
||||||
rewrite ^/uploads/(.*)$ /iloom-uploads/$1 break;
|
|
||||||
proxy_pass http://$minio_host:9000;
|
|
||||||
proxy_set_header Host $minio_host:9000;
|
|
||||||
expires 7d;
|
|
||||||
add_header Cache-Control "public";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
location /ws/ {
|
location /ws/ {
|
||||||
@ -44,23 +33,12 @@ data:
|
|||||||
proxy_read_timeout 86400;
|
proxy_read_timeout 86400;
|
||||||
}
|
}
|
||||||
|
|
||||||
# SPA fallback — index.html must never be cached
|
|
||||||
location / {
|
location / {
|
||||||
try_files $uri $uri/ /index.html;
|
try_files $uri $uri/ /index.html;
|
||||||
add_header Cache-Control "no-cache, no-store, must-revalidate";
|
|
||||||
add_header Pragma "no-cache";
|
|
||||||
add_header Expires "0";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# Hashed static assets (e.g. main.decafe71.js) — safe to cache long-term
|
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
|
||||||
location ~* \.[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]\.(js|css)$ {
|
|
||||||
expires 30d;
|
expires 30d;
|
||||||
add_header Cache-Control "public, immutable";
|
add_header Cache-Control "public, immutable";
|
||||||
}
|
}
|
||||||
|
|
||||||
# Other static assets (images, fonts) — moderate cache
|
|
||||||
location ~* \.(png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
|
|
||||||
expires 7d;
|
|
||||||
add_header Cache-Control "public";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,3 @@
|
|||||||
## DEPRECATED: These raw K8s manifests are superseded by iloom-deploy/charts/.
|
|
||||||
## DataSource values must be injected via Kubernetes Secrets at deploy time.
|
|
||||||
## See iloom-deploy/charts/infra/templates/secrets.yaml for the Helm-based approach.
|
|
||||||
##
|
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: ConfigMap
|
kind: ConfigMap
|
||||||
metadata:
|
metadata:
|
||||||
@ -17,7 +13,7 @@ data:
|
|||||||
- etcd:2379
|
- etcd:2379
|
||||||
Key: system.rpc
|
Key: system.rpc
|
||||||
|
|
||||||
DataSource: REPLACE_AT_DEPLOY_TIME
|
DataSource: root:muyu2026@tcp(mysql:3306)/muyu_wms?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai
|
||||||
|
|
||||||
Cache:
|
Cache:
|
||||||
- Host: redis:6379
|
- Host: redis:6379
|
||||||
@ -42,7 +38,7 @@ data:
|
|||||||
- etcd:2379
|
- etcd:2379
|
||||||
Key: inventory.rpc
|
Key: inventory.rpc
|
||||||
|
|
||||||
DataSource: REPLACE_AT_DEPLOY_TIME
|
DataSource: root:muyu2026@tcp(mysql:3306)/muyu_wms?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai
|
||||||
|
|
||||||
Cache:
|
Cache:
|
||||||
- Host: redis:6379
|
- Host: redis:6379
|
||||||
@ -64,7 +60,7 @@ data:
|
|||||||
Port: 8888
|
Port: 8888
|
||||||
|
|
||||||
Auth:
|
Auth:
|
||||||
AccessSecret: REPLACE_AT_DEPLOY_TIME
|
AccessSecret: muyu-wms-jwt-secret-key-2026
|
||||||
AccessExpire: 7200
|
AccessExpire: 7200
|
||||||
|
|
||||||
SystemRpc:
|
SystemRpc:
|
||||||
|
|||||||
@ -1,97 +0,0 @@
|
|||||||
apiVersion: v1
|
|
||||||
kind: Secret
|
|
||||||
metadata:
|
|
||||||
name: minio-secret
|
|
||||||
namespace: iloom
|
|
||||||
type: Opaque
|
|
||||||
stringData:
|
|
||||||
MINIO_ROOT_USER: "minioadmin"
|
|
||||||
MINIO_ROOT_PASSWORD: "minioadmin123"
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: minio
|
|
||||||
namespace: iloom
|
|
||||||
spec:
|
|
||||||
clusterIP: None
|
|
||||||
ports:
|
|
||||||
- name: api
|
|
||||||
port: 9000
|
|
||||||
targetPort: 9000
|
|
||||||
- name: console
|
|
||||||
port: 9001
|
|
||||||
targetPort: 9001
|
|
||||||
selector:
|
|
||||||
app: minio
|
|
||||||
---
|
|
||||||
apiVersion: apps/v1
|
|
||||||
kind: StatefulSet
|
|
||||||
metadata:
|
|
||||||
name: minio
|
|
||||||
namespace: iloom
|
|
||||||
spec:
|
|
||||||
serviceName: minio
|
|
||||||
replicas: 1
|
|
||||||
selector:
|
|
||||||
matchLabels:
|
|
||||||
app: minio
|
|
||||||
template:
|
|
||||||
metadata:
|
|
||||||
labels:
|
|
||||||
app: minio
|
|
||||||
spec:
|
|
||||||
imagePullSecrets:
|
|
||||||
- name: harbor-pull-secret
|
|
||||||
containers:
|
|
||||||
- name: minio
|
|
||||||
image: harbor-in-k3s.cheverjohn.me/iloom/minio:latest
|
|
||||||
args:
|
|
||||||
- server
|
|
||||||
- /data
|
|
||||||
- --console-address
|
|
||||||
- ":9001"
|
|
||||||
ports:
|
|
||||||
- containerPort: 9000
|
|
||||||
- containerPort: 9001
|
|
||||||
env:
|
|
||||||
- name: MINIO_ROOT_USER
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: minio-secret
|
|
||||||
key: MINIO_ROOT_USER
|
|
||||||
- name: MINIO_ROOT_PASSWORD
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: minio-secret
|
|
||||||
key: MINIO_ROOT_PASSWORD
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
cpu: 50m
|
|
||||||
memory: 128Mi
|
|
||||||
limits:
|
|
||||||
cpu: 500m
|
|
||||||
memory: 512Mi
|
|
||||||
readinessProbe:
|
|
||||||
httpGet:
|
|
||||||
path: /minio/health/ready
|
|
||||||
port: 9000
|
|
||||||
initialDelaySeconds: 10
|
|
||||||
periodSeconds: 10
|
|
||||||
livenessProbe:
|
|
||||||
httpGet:
|
|
||||||
path: /minio/health/live
|
|
||||||
port: 9000
|
|
||||||
initialDelaySeconds: 20
|
|
||||||
periodSeconds: 30
|
|
||||||
volumeMounts:
|
|
||||||
- name: data
|
|
||||||
mountPath: /data
|
|
||||||
volumeClaimTemplates:
|
|
||||||
- metadata:
|
|
||||||
name: data
|
|
||||||
spec:
|
|
||||||
accessModes: ["ReadWriteOnce"]
|
|
||||||
resources:
|
|
||||||
requests:
|
|
||||||
storage: 2Gi
|
|
||||||
@ -60,20 +60,6 @@ spec:
|
|||||||
key: JWT_REFRESH_SECRET
|
key: JWT_REFRESH_SECRET
|
||||||
- name: MUYU_SYSTEM_RPC_ADDR
|
- name: MUYU_SYSTEM_RPC_ADDR
|
||||||
value: "muyu-system-rpc:9001"
|
value: "muyu-system-rpc:9001"
|
||||||
- name: MINIO_ENDPOINT
|
|
||||||
value: "minio:9000"
|
|
||||||
- name: MINIO_ACCESS_KEY
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: minio-secret
|
|
||||||
key: MINIO_ROOT_USER
|
|
||||||
- name: MINIO_SECRET_KEY
|
|
||||||
valueFrom:
|
|
||||||
secretKeyRef:
|
|
||||||
name: minio-secret
|
|
||||||
key: MINIO_ROOT_PASSWORD
|
|
||||||
- name: MINIO_BUCKET
|
|
||||||
value: "iloom-uploads"
|
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: 50m
|
cpu: 50m
|
||||||
|
|||||||
@ -1,16 +1,11 @@
|
|||||||
ARG BASE_REGISTRY=harbor-in-k3s.cheverjohn.me/library/
|
FROM golang:1.24-alpine AS builder
|
||||||
FROM ${BASE_REGISTRY}golang:1.24-alpine AS builder
|
|
||||||
ARG GOPROXY=https://goproxy.cn,direct
|
|
||||||
ENV GOPROXY=${GOPROXY}
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY shared/ ./shared/
|
COPY shared/ ./shared/
|
||||||
COPY purchaser-service/ ./purchaser-service/
|
COPY purchaser-service/ ./purchaser-service/
|
||||||
WORKDIR /app/purchaser-service
|
WORKDIR /app/purchaser-service
|
||||||
RUN go build -o /purchaser-service ./cmd/
|
RUN go build -o /purchaser-service ./cmd/
|
||||||
|
|
||||||
FROM ${BASE_REGISTRY}alpine:3.20
|
FROM alpine:3.20
|
||||||
ARG APK_MIRROR=mirrors.aliyun.com
|
|
||||||
RUN if [ -n "$APK_MIRROR" ]; then sed -i "s|dl-cdn.alpinelinux.org|$APK_MIRROR|g" /etc/apk/repositories; fi
|
|
||||||
RUN apk add --no-cache wget
|
RUN apk add --no-cache wget
|
||||||
COPY --from=builder /purchaser-service /purchaser-service
|
COPY --from=builder /purchaser-service /purchaser-service
|
||||||
EXPOSE 8082
|
EXPOSE 8082
|
||||||
|
|||||||
@ -12,7 +12,7 @@ type Config struct {
|
|||||||
func Load() *Config {
|
func Load() *Config {
|
||||||
return &Config{
|
return &Config{
|
||||||
Port: getEnv("PORT", "8082"),
|
Port: getEnv("PORT", "8082"),
|
||||||
DBDSN: getEnv("DB_DSN", ""),
|
DBDSN: getEnv("DB_DSN", "root:muyu2026@tcp(127.0.0.1:3306)/muyu_wms?parseTime=true&charset=utf8mb4"),
|
||||||
InternalServiceKey: getEnv("INTERNAL_SERVICE_KEY", "dev-internal-key"),
|
InternalServiceKey: getEnv("INTERNAL_SERVICE_KEY", "dev-internal-key"),
|
||||||
InventoryRPCAddr: getEnv("MUYU_INVENTORY_RPC_ADDR", "127.0.0.1:9002"),
|
InventoryRPCAddr: getEnv("MUYU_INVENTORY_RPC_ADDR", "127.0.0.1:9002"),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,26 +20,15 @@ func NewProductHandler(svc *service.ProductService, wsHub *websocket.Hub) *Produ
|
|||||||
}
|
}
|
||||||
|
|
||||||
type CreateProductReq struct {
|
type CreateProductReq struct {
|
||||||
ProductName string `json:"product_name"`
|
ProductName string `json:"product_name" binding:"required"`
|
||||||
FabricCode string `json:"fabric_code" binding:"required"`
|
FabricCode string `json:"fabric_code" binding:"required"`
|
||||||
Color string `json:"color"`
|
Color string `json:"color" binding:"required"`
|
||||||
ColorCode string `json:"color_code"`
|
ColorCode string `json:"color_code"`
|
||||||
Weight float64 `json:"weight"`
|
Weight float64 `json:"weight"`
|
||||||
WarpWeight *float64 `json:"warp_weight"`
|
|
||||||
WeftWeight *float64 `json:"weft_weight"`
|
|
||||||
TotalWeight *float64 `json:"total_weight"`
|
|
||||||
YarnUsagePerMeter float64 `json:"yarn_usage_per_meter"`
|
YarnUsagePerMeter float64 `json:"yarn_usage_per_meter"`
|
||||||
ProductionPrice *float64 `json:"production_price"`
|
ProductionPrice *float64 `json:"production_price"`
|
||||||
ImageURL *string `json:"image_url"`
|
|
||||||
ProductType string `json:"product_type"`
|
|
||||||
YarnTypes []string `json:"yarn_types"`
|
YarnTypes []string `json:"yarn_types"`
|
||||||
YarnRatios []YarnRatioReq `json:"yarn_ratios"`
|
YarnRatios []float64 `json:"yarn_ratios"`
|
||||||
}
|
|
||||||
|
|
||||||
type YarnRatioReq struct {
|
|
||||||
YarnName string `json:"yarn_name"`
|
|
||||||
YarnType string `json:"yarn_type"`
|
|
||||||
Ratio float64 `json:"ratio"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpdateProductReq struct {
|
type UpdateProductReq struct {
|
||||||
@ -48,15 +37,10 @@ type UpdateProductReq struct {
|
|||||||
Color string `json:"color"`
|
Color string `json:"color"`
|
||||||
ColorCode string `json:"color_code"`
|
ColorCode string `json:"color_code"`
|
||||||
Weight float64 `json:"weight"`
|
Weight float64 `json:"weight"`
|
||||||
WarpWeight *float64 `json:"warp_weight"`
|
|
||||||
WeftWeight *float64 `json:"weft_weight"`
|
|
||||||
TotalWeight *float64 `json:"total_weight"`
|
|
||||||
YarnUsagePerMeter float64 `json:"yarn_usage_per_meter"`
|
YarnUsagePerMeter float64 `json:"yarn_usage_per_meter"`
|
||||||
ProductionPrice *float64 `json:"production_price"`
|
ProductionPrice *float64 `json:"production_price"`
|
||||||
ImageURL *string `json:"image_url"`
|
|
||||||
ProductType string `json:"product_type"`
|
|
||||||
YarnTypes []string `json:"yarn_types"`
|
YarnTypes []string `json:"yarn_types"`
|
||||||
YarnRatios []YarnRatioReq `json:"yarn_ratios"`
|
YarnRatios []float64 `json:"yarn_ratios"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type InboundReq struct {
|
type InboundReq struct {
|
||||||
@ -113,30 +97,16 @@ func (h *ProductHandler) Create(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
yarnRatios := make([]service.YarnRatio, len(req.YarnRatios))
|
|
||||||
for i, yr := range req.YarnRatios {
|
|
||||||
yarnRatios[i] = service.YarnRatio{
|
|
||||||
YarnName: yr.YarnName,
|
|
||||||
YarnType: yr.YarnType,
|
|
||||||
Ratio: yr.Ratio,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
svcReq := service.CreateProductRequest{
|
svcReq := service.CreateProductRequest{
|
||||||
ProductName: req.ProductName,
|
ProductName: req.ProductName,
|
||||||
FabricCode: req.FabricCode,
|
FabricCode: req.FabricCode,
|
||||||
Color: req.Color,
|
Color: req.Color,
|
||||||
ColorCode: req.ColorCode,
|
ColorCode: req.ColorCode,
|
||||||
Weight: req.Weight,
|
Weight: req.Weight,
|
||||||
WarpWeight: req.WarpWeight,
|
|
||||||
WeftWeight: req.WeftWeight,
|
|
||||||
TotalWeight: req.TotalWeight,
|
|
||||||
YarnUsagePerMeter: req.YarnUsagePerMeter,
|
YarnUsagePerMeter: req.YarnUsagePerMeter,
|
||||||
ProductionPrice: req.ProductionPrice,
|
ProductionPrice: req.ProductionPrice,
|
||||||
ImageURL: req.ImageURL,
|
|
||||||
ProductType: req.ProductType,
|
|
||||||
YarnTypes: req.YarnTypes,
|
YarnTypes: req.YarnTypes,
|
||||||
YarnRatios: yarnRatios,
|
YarnRatios: req.YarnRatios,
|
||||||
}
|
}
|
||||||
|
|
||||||
product, err := h.svc.Create(c.Request.Context(), companyID, svcReq)
|
product, err := h.svc.Create(c.Request.Context(), companyID, svcReq)
|
||||||
@ -173,30 +143,16 @@ func (h *ProductHandler) Update(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
yarnRatios := make([]service.YarnRatio, len(req.YarnRatios))
|
|
||||||
for i, yr := range req.YarnRatios {
|
|
||||||
yarnRatios[i] = service.YarnRatio{
|
|
||||||
YarnName: yr.YarnName,
|
|
||||||
YarnType: yr.YarnType,
|
|
||||||
Ratio: yr.Ratio,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
svcReq := service.UpdateProductRequest{
|
svcReq := service.UpdateProductRequest{
|
||||||
ProductName: req.ProductName,
|
ProductName: req.ProductName,
|
||||||
FabricCode: req.FabricCode,
|
FabricCode: req.FabricCode,
|
||||||
Color: req.Color,
|
Color: req.Color,
|
||||||
ColorCode: req.ColorCode,
|
ColorCode: req.ColorCode,
|
||||||
Weight: req.Weight,
|
Weight: req.Weight,
|
||||||
WarpWeight: req.WarpWeight,
|
|
||||||
WeftWeight: req.WeftWeight,
|
|
||||||
TotalWeight: req.TotalWeight,
|
|
||||||
YarnUsagePerMeter: req.YarnUsagePerMeter,
|
YarnUsagePerMeter: req.YarnUsagePerMeter,
|
||||||
ProductionPrice: req.ProductionPrice,
|
ProductionPrice: req.ProductionPrice,
|
||||||
ImageURL: req.ImageURL,
|
|
||||||
ProductType: req.ProductType,
|
|
||||||
YarnTypes: req.YarnTypes,
|
YarnTypes: req.YarnTypes,
|
||||||
YarnRatios: yarnRatios,
|
YarnRatios: req.YarnRatios,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := h.svc.Update(c.Request.Context(), id, companyID, svcReq); err != nil {
|
if err := h.svc.Update(c.Request.Context(), id, companyID, svcReq); err != nil {
|
||||||
|
|||||||
@ -1,71 +1,6 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import "time"
|
||||||
"database/sql/driver"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ProductYarnRatio struct {
|
|
||||||
YarnName string `json:"yarn_name"`
|
|
||||||
YarnType string `json:"yarn_type"`
|
|
||||||
Ratio float64 `json:"ratio"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type JSONStringSlice []string
|
|
||||||
|
|
||||||
func (s *JSONStringSlice) Scan(src interface{}) error {
|
|
||||||
if src == nil {
|
|
||||||
*s = nil
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
var b []byte
|
|
||||||
switch v := src.(type) {
|
|
||||||
case []byte:
|
|
||||||
b = v
|
|
||||||
case string:
|
|
||||||
b = []byte(v)
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("unsupported type: %T", src)
|
|
||||||
}
|
|
||||||
return json.Unmarshal(b, s)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s JSONStringSlice) Value() (driver.Value, error) {
|
|
||||||
if s == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
b, err := json.Marshal(s)
|
|
||||||
return string(b), err
|
|
||||||
}
|
|
||||||
|
|
||||||
type JSONYarnRatios []ProductYarnRatio
|
|
||||||
|
|
||||||
func (r *JSONYarnRatios) Scan(src interface{}) error {
|
|
||||||
if src == nil {
|
|
||||||
*r = nil
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
var b []byte
|
|
||||||
switch v := src.(type) {
|
|
||||||
case []byte:
|
|
||||||
b = v
|
|
||||||
case string:
|
|
||||||
b = []byte(v)
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("unsupported type: %T", src)
|
|
||||||
}
|
|
||||||
return json.Unmarshal(b, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r JSONYarnRatios) Value() (driver.Value, error) {
|
|
||||||
if r == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
b, err := json.Marshal(r)
|
|
||||||
return string(b), err
|
|
||||||
}
|
|
||||||
|
|
||||||
type Product struct {
|
type Product struct {
|
||||||
ID string `json:"id" db:"id"`
|
ID string `json:"id" db:"id"`
|
||||||
@ -84,9 +19,8 @@ type Product struct {
|
|||||||
RawFabricMeters float64 `json:"raw_fabric_meters" db:"raw_fabric_meters"`
|
RawFabricMeters float64 `json:"raw_fabric_meters" db:"raw_fabric_meters"`
|
||||||
RawFabricRolls int `json:"raw_fabric_rolls" db:"raw_fabric_rolls"`
|
RawFabricRolls int `json:"raw_fabric_rolls" db:"raw_fabric_rolls"`
|
||||||
ImageURL *string `json:"image_url,omitempty" db:"image_url"`
|
ImageURL *string `json:"image_url,omitempty" db:"image_url"`
|
||||||
ProductType string `json:"product_type" db:"product_type"`
|
YarnTypes []string `json:"yarn_types,omitempty" db:"yarn_types"`
|
||||||
YarnTypes JSONStringSlice `json:"yarn_types,omitempty" db:"yarn_types"`
|
YarnRatios []float64 `json:"yarn_ratios,omitempty" db:"yarn_ratios"`
|
||||||
YarnRatios JSONYarnRatios `json:"yarn_ratios,omitempty" db:"yarn_ratios"`
|
|
||||||
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|||||||
@ -282,8 +282,8 @@ func (r *PlanRepo) GetInventory(ctx context.Context, planID string) ([]model.Inv
|
|||||||
func (r *PlanRepo) CountByStatus(ctx context.Context, companyID string) (total, producing, completed int, err error) {
|
func (r *PlanRepo) CountByStatus(ctx context.Context, companyID string) (total, producing, completed int, err error) {
|
||||||
query := `SELECT
|
query := `SELECT
|
||||||
COUNT(*),
|
COUNT(*),
|
||||||
COALESCE(SUM(CASE WHEN status = 'producing' THEN 1 ELSE 0 END), 0),
|
SUM(CASE WHEN status = 'producing' THEN 1 ELSE 0 END),
|
||||||
COALESCE(SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END), 0)
|
SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END)
|
||||||
FROM ilm_production_plan WHERE purchaser_id = ?`
|
FROM ilm_production_plan WHERE purchaser_id = ?`
|
||||||
err = r.db.QueryRowContext(ctx, query, companyID).Scan(&total, &producing, &completed)
|
err = r.db.QueryRowContext(ctx, query, companyID).Scan(&total, &producing, &completed)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@ -22,7 +22,7 @@ func (r *ProductRepo) List(ctx context.Context, companyID string) ([]model.Produ
|
|||||||
query := `SELECT id, company_id, product_name, fabric_code, color, color_code,
|
query := `SELECT id, company_id, product_name, fabric_code, color, color_code,
|
||||||
weight, warp_weight, weft_weight, total_weight, yarn_usage_per_meter,
|
weight, warp_weight, weft_weight, total_weight, yarn_usage_per_meter,
|
||||||
production_price, total_stock, raw_fabric_meters, raw_fabric_rolls,
|
production_price, total_stock, raw_fabric_meters, raw_fabric_rolls,
|
||||||
image_url, product_type, yarn_types, yarn_ratios, created_at, updated_at
|
image_url, yarn_types, yarn_ratios, created_at, updated_at
|
||||||
FROM products WHERE company_id = ? ORDER BY created_at DESC`
|
FROM products WHERE company_id = ? ORDER BY created_at DESC`
|
||||||
|
|
||||||
rows, err := r.db.QueryContext(ctx, query, companyID)
|
rows, err := r.db.QueryContext(ctx, query, companyID)
|
||||||
@ -39,7 +39,7 @@ func (r *ProductRepo) List(ctx context.Context, companyID string) ([]model.Produ
|
|||||||
&p.Weight, &p.WarpWeight, &p.WeftWeight, &p.TotalWeight,
|
&p.Weight, &p.WarpWeight, &p.WeftWeight, &p.TotalWeight,
|
||||||
&p.YarnUsagePerMeter, &p.ProductionPrice,
|
&p.YarnUsagePerMeter, &p.ProductionPrice,
|
||||||
&p.TotalStock, &p.RawFabricMeters, &p.RawFabricRolls,
|
&p.TotalStock, &p.RawFabricMeters, &p.RawFabricRolls,
|
||||||
&p.ImageURL, &p.ProductType, &p.YarnTypes, &p.YarnRatios,
|
&p.ImageURL, &p.YarnTypes, &p.YarnRatios,
|
||||||
&p.CreatedAt, &p.UpdatedAt,
|
&p.CreatedAt, &p.UpdatedAt,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, fmt.Errorf("scan product: %w", err)
|
return nil, fmt.Errorf("scan product: %w", err)
|
||||||
@ -54,14 +54,14 @@ func (r *ProductRepo) Create(ctx context.Context, p *model.Product) error {
|
|||||||
(id, company_id, product_name, fabric_code, color, color_code,
|
(id, company_id, product_name, fabric_code, color, color_code,
|
||||||
weight, warp_weight, weft_weight, total_weight, yarn_usage_per_meter,
|
weight, warp_weight, weft_weight, total_weight, yarn_usage_per_meter,
|
||||||
production_price, total_stock, raw_fabric_meters, raw_fabric_rolls,
|
production_price, total_stock, raw_fabric_meters, raw_fabric_rolls,
|
||||||
image_url, product_type, yarn_types, yarn_ratios, created_at, updated_at)
|
image_url, yarn_types, yarn_ratios, created_at, updated_at)
|
||||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`
|
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`
|
||||||
_, err := r.db.ExecContext(ctx, query,
|
_, err := r.db.ExecContext(ctx, query,
|
||||||
p.ID, p.CompanyID, p.ProductName, p.FabricCode, p.Color, p.ColorCode,
|
p.ID, p.CompanyID, p.ProductName, p.FabricCode, p.Color, p.ColorCode,
|
||||||
p.Weight, p.WarpWeight, p.WeftWeight, p.TotalWeight,
|
p.Weight, p.WarpWeight, p.WeftWeight, p.TotalWeight,
|
||||||
p.YarnUsagePerMeter, p.ProductionPrice,
|
p.YarnUsagePerMeter, p.ProductionPrice,
|
||||||
p.TotalStock, p.RawFabricMeters, p.RawFabricRolls,
|
p.TotalStock, p.RawFabricMeters, p.RawFabricRolls,
|
||||||
p.ImageURL, p.ProductType, p.YarnTypes, p.YarnRatios,
|
p.ImageURL, p.YarnTypes, p.YarnRatios,
|
||||||
p.CreatedAt, p.UpdatedAt,
|
p.CreatedAt, p.UpdatedAt,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -75,13 +75,13 @@ func (r *ProductRepo) Update(ctx context.Context, p *model.Product) error {
|
|||||||
product_name = ?, fabric_code = ?, color = ?, color_code = ?,
|
product_name = ?, fabric_code = ?, color = ?, color_code = ?,
|
||||||
weight = ?, warp_weight = ?, weft_weight = ?, total_weight = ?,
|
weight = ?, warp_weight = ?, weft_weight = ?, total_weight = ?,
|
||||||
yarn_usage_per_meter = ?, production_price = ?,
|
yarn_usage_per_meter = ?, production_price = ?,
|
||||||
image_url = ?, product_type = ?, yarn_types = ?, yarn_ratios = ?, updated_at = ?
|
image_url = ?, yarn_types = ?, yarn_ratios = ?, updated_at = ?
|
||||||
WHERE id = ?`
|
WHERE id = ?`
|
||||||
_, err := r.db.ExecContext(ctx, query,
|
_, err := r.db.ExecContext(ctx, query,
|
||||||
p.ProductName, p.FabricCode, p.Color, p.ColorCode,
|
p.ProductName, p.FabricCode, p.Color, p.ColorCode,
|
||||||
p.Weight, p.WarpWeight, p.WeftWeight, p.TotalWeight,
|
p.Weight, p.WarpWeight, p.WeftWeight, p.TotalWeight,
|
||||||
p.YarnUsagePerMeter, p.ProductionPrice,
|
p.YarnUsagePerMeter, p.ProductionPrice,
|
||||||
p.ImageURL, p.ProductType, p.YarnTypes, p.YarnRatios, time.Now(),
|
p.ImageURL, p.YarnTypes, p.YarnRatios, time.Now(),
|
||||||
p.ID,
|
p.ID,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@ -20,27 +20,16 @@ func NewProductService(repo *repository.ProductRepo, db *sql.DB) *ProductService
|
|||||||
return &ProductService{repo: repo, db: db}
|
return &ProductService{repo: repo, db: db}
|
||||||
}
|
}
|
||||||
|
|
||||||
type YarnRatio struct {
|
|
||||||
YarnName string `json:"yarn_name"`
|
|
||||||
YarnType string `json:"yarn_type"`
|
|
||||||
Ratio float64 `json:"ratio"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CreateProductRequest struct {
|
type CreateProductRequest struct {
|
||||||
ProductName string `json:"product_name"`
|
ProductName string `json:"product_name"`
|
||||||
FabricCode string `json:"fabric_code"`
|
FabricCode string `json:"fabric_code"`
|
||||||
Color string `json:"color"`
|
Color string `json:"color"`
|
||||||
ColorCode string `json:"color_code"`
|
ColorCode string `json:"color_code"`
|
||||||
Weight float64 `json:"weight"`
|
Weight float64 `json:"weight"`
|
||||||
WarpWeight *float64 `json:"warp_weight"`
|
|
||||||
WeftWeight *float64 `json:"weft_weight"`
|
|
||||||
TotalWeight *float64 `json:"total_weight"`
|
|
||||||
YarnUsagePerMeter float64 `json:"yarn_usage_per_meter"`
|
YarnUsagePerMeter float64 `json:"yarn_usage_per_meter"`
|
||||||
ProductionPrice *float64 `json:"production_price"`
|
ProductionPrice *float64 `json:"production_price"`
|
||||||
ImageURL *string `json:"image_url"`
|
|
||||||
ProductType string `json:"product_type"`
|
|
||||||
YarnTypes []string `json:"yarn_types"`
|
YarnTypes []string `json:"yarn_types"`
|
||||||
YarnRatios []YarnRatio `json:"yarn_ratios"`
|
YarnRatios []float64 `json:"yarn_ratios"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpdateProductRequest struct {
|
type UpdateProductRequest struct {
|
||||||
@ -49,15 +38,10 @@ type UpdateProductRequest struct {
|
|||||||
Color string `json:"color"`
|
Color string `json:"color"`
|
||||||
ColorCode string `json:"color_code"`
|
ColorCode string `json:"color_code"`
|
||||||
Weight float64 `json:"weight"`
|
Weight float64 `json:"weight"`
|
||||||
WarpWeight *float64 `json:"warp_weight"`
|
|
||||||
WeftWeight *float64 `json:"weft_weight"`
|
|
||||||
TotalWeight *float64 `json:"total_weight"`
|
|
||||||
YarnUsagePerMeter float64 `json:"yarn_usage_per_meter"`
|
YarnUsagePerMeter float64 `json:"yarn_usage_per_meter"`
|
||||||
ProductionPrice *float64 `json:"production_price"`
|
ProductionPrice *float64 `json:"production_price"`
|
||||||
ImageURL *string `json:"image_url"`
|
|
||||||
ProductType string `json:"product_type"`
|
|
||||||
YarnTypes []string `json:"yarn_types"`
|
YarnTypes []string `json:"yarn_types"`
|
||||||
YarnRatios []YarnRatio `json:"yarn_ratios"`
|
YarnRatios []float64 `json:"yarn_ratios"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type InboundRequest struct {
|
type InboundRequest struct {
|
||||||
@ -86,21 +70,6 @@ func (s *ProductService) List(ctx context.Context, companyID string) ([]model.Pr
|
|||||||
|
|
||||||
func (s *ProductService) Create(ctx context.Context, companyID string, req CreateProductRequest) (*model.Product, error) {
|
func (s *ProductService) Create(ctx context.Context, companyID string, req CreateProductRequest) (*model.Product, error) {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
|
|
||||||
yarnRatios := make(model.JSONYarnRatios, len(req.YarnRatios))
|
|
||||||
for i, yr := range req.YarnRatios {
|
|
||||||
yarnRatios[i] = model.ProductYarnRatio{
|
|
||||||
YarnName: yr.YarnName,
|
|
||||||
YarnType: yr.YarnType,
|
|
||||||
Ratio: yr.Ratio,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
productType := req.ProductType
|
|
||||||
if productType == "" {
|
|
||||||
productType = "raw_fabric"
|
|
||||||
}
|
|
||||||
|
|
||||||
p := &model.Product{
|
p := &model.Product{
|
||||||
ID: uuid.New().String(),
|
ID: uuid.New().String(),
|
||||||
CompanyID: companyID,
|
CompanyID: companyID,
|
||||||
@ -109,18 +78,13 @@ func (s *ProductService) Create(ctx context.Context, companyID string, req Creat
|
|||||||
Color: req.Color,
|
Color: req.Color,
|
||||||
ColorCode: req.ColorCode,
|
ColorCode: req.ColorCode,
|
||||||
Weight: req.Weight,
|
Weight: req.Weight,
|
||||||
WarpWeight: req.WarpWeight,
|
|
||||||
WeftWeight: req.WeftWeight,
|
|
||||||
TotalWeight: req.TotalWeight,
|
|
||||||
YarnUsagePerMeter: req.YarnUsagePerMeter,
|
YarnUsagePerMeter: req.YarnUsagePerMeter,
|
||||||
ProductionPrice: req.ProductionPrice,
|
ProductionPrice: req.ProductionPrice,
|
||||||
ImageURL: req.ImageURL,
|
|
||||||
ProductType: productType,
|
|
||||||
TotalStock: 0,
|
TotalStock: 0,
|
||||||
RawFabricMeters: 0,
|
RawFabricMeters: 0,
|
||||||
RawFabricRolls: 0,
|
RawFabricRolls: 0,
|
||||||
YarnTypes: model.JSONStringSlice(req.YarnTypes),
|
YarnTypes: req.YarnTypes,
|
||||||
YarnRatios: yarnRatios,
|
YarnRatios: req.YarnRatios,
|
||||||
CreatedAt: now,
|
CreatedAt: now,
|
||||||
UpdatedAt: now,
|
UpdatedAt: now,
|
||||||
}
|
}
|
||||||
@ -132,20 +96,6 @@ func (s *ProductService) Create(ctx context.Context, companyID string, req Creat
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *ProductService) Update(ctx context.Context, id, companyID string, req UpdateProductRequest) error {
|
func (s *ProductService) Update(ctx context.Context, id, companyID string, req UpdateProductRequest) error {
|
||||||
yarnRatios := make(model.JSONYarnRatios, len(req.YarnRatios))
|
|
||||||
for i, yr := range req.YarnRatios {
|
|
||||||
yarnRatios[i] = model.ProductYarnRatio{
|
|
||||||
YarnName: yr.YarnName,
|
|
||||||
YarnType: yr.YarnType,
|
|
||||||
Ratio: yr.Ratio,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
productType := req.ProductType
|
|
||||||
if productType == "" {
|
|
||||||
productType = "raw_fabric"
|
|
||||||
}
|
|
||||||
|
|
||||||
p := &model.Product{
|
p := &model.Product{
|
||||||
ID: id,
|
ID: id,
|
||||||
ProductName: req.ProductName,
|
ProductName: req.ProductName,
|
||||||
@ -153,15 +103,10 @@ func (s *ProductService) Update(ctx context.Context, id, companyID string, req U
|
|||||||
Color: req.Color,
|
Color: req.Color,
|
||||||
ColorCode: req.ColorCode,
|
ColorCode: req.ColorCode,
|
||||||
Weight: req.Weight,
|
Weight: req.Weight,
|
||||||
WarpWeight: req.WarpWeight,
|
|
||||||
WeftWeight: req.WeftWeight,
|
|
||||||
TotalWeight: req.TotalWeight,
|
|
||||||
YarnUsagePerMeter: req.YarnUsagePerMeter,
|
YarnUsagePerMeter: req.YarnUsagePerMeter,
|
||||||
ProductionPrice: req.ProductionPrice,
|
ProductionPrice: req.ProductionPrice,
|
||||||
ImageURL: req.ImageURL,
|
YarnTypes: req.YarnTypes,
|
||||||
ProductType: productType,
|
YarnRatios: req.YarnRatios,
|
||||||
YarnTypes: model.JSONStringSlice(req.YarnTypes),
|
|
||||||
YarnRatios: yarnRatios,
|
|
||||||
}
|
}
|
||||||
return s.repo.Update(ctx, p)
|
return s.repo.Update(ctx, p)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,112 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# Deploy iloom (backend services + frontend) in integrated mode against a running muyu-apiserver stack.
|
|
||||||
#
|
|
||||||
# Prerequisites:
|
|
||||||
# - muyu-apiserver already running (cd ../muyu-apiserver/deploy && docker compose up -d)
|
|
||||||
# - .env present in repo root (copy from .env.example and fill in secrets)
|
|
||||||
#
|
|
||||||
# Usage: ./scripts/deploy.sh [--no-mirror]
|
|
||||||
# --no-mirror Build images against upstream registries (Docker Hub, proxy.golang.org,
|
|
||||||
# official Alpine CDN) instead of the Harbor/Aliyun/goproxy.cn mirrors that
|
|
||||||
# the Dockerfiles use by default.
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
||||||
cd "$ROOT_DIR"
|
|
||||||
|
|
||||||
MUYU_NETWORK="deploy_default"
|
|
||||||
MUYU_MYSQL_CONTAINER="muyu-mysql"
|
|
||||||
SERVICES=(gateway auth-service purchaser-service textile-service washing-service frontend)
|
|
||||||
COMPOSE_FILE="docker-compose.integrated.yml"
|
|
||||||
NO_MIRROR=false
|
|
||||||
|
|
||||||
for arg in "$@"; do
|
|
||||||
case "$arg" in
|
|
||||||
--no-mirror) NO_MIRROR=true ;;
|
|
||||||
*) ;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
log() { printf '\n==> %s\n' "$1"; }
|
|
||||||
die() { printf '\nERROR: %s\n' "$1" >&2; exit 1; }
|
|
||||||
|
|
||||||
[ -f .env ] || die ".env not found in $ROOT_DIR. Copy .env.example to .env and fill in real values first."
|
|
||||||
while IFS='=' read -r key value; do
|
|
||||||
[[ -z "$key" || "$key" == \#* ]] && continue
|
|
||||||
export "$key=$value"
|
|
||||||
done < <(grep -v '^\s*#' .env | grep '=')
|
|
||||||
|
|
||||||
[ -n "${DB_DSN:-}" ] || die "DB_DSN is not set in .env"
|
|
||||||
[ -n "${JWT_SECRET:-}" ] || die "JWT_SECRET is not set in .env"
|
|
||||||
[ -n "${JWT_REFRESH_SECRET:-}" ] || die "JWT_REFRESH_SECRET is not set in .env"
|
|
||||||
[ -n "${INTERNAL_SERVICE_KEY:-}" ] || die "INTERNAL_SERVICE_KEY is not set in .env"
|
|
||||||
|
|
||||||
if [ "$NO_MIRROR" = true ]; then
|
|
||||||
log "Disabling build mirrors (--no-mirror): using upstream registries"
|
|
||||||
export BASE_REGISTRY=""
|
|
||||||
export GOPROXY="https://proxy.golang.org,direct"
|
|
||||||
export APK_MIRROR=""
|
|
||||||
fi
|
|
||||||
|
|
||||||
log "Checking muyu-apiserver dependencies"
|
|
||||||
docker network inspect "$MUYU_NETWORK" >/dev/null 2>&1 || \
|
|
||||||
die "Docker network '$MUYU_NETWORK' not found. Start muyu-apiserver first: cd ../muyu-apiserver/deploy && docker compose up -d"
|
|
||||||
docker inspect -f '{{.State.Running}}' "$MUYU_MYSQL_CONTAINER" 2>/dev/null | grep -q true || \
|
|
||||||
die "Container '$MUYU_MYSQL_CONTAINER' is not running. Start muyu-apiserver first."
|
|
||||||
for c in muyu-system-rpc muyu-inventory-rpc; do
|
|
||||||
docker inspect -f '{{.State.Running}}' "$c" >/dev/null 2>&1 || die "Container '$c' is not running."
|
|
||||||
done
|
|
||||||
echo "muyu-apiserver stack is up (network '$MUYU_NETWORK' found, mysql/system-rpc/inventory-rpc running)."
|
|
||||||
|
|
||||||
log "Applying iloom schema to shared muyu_wms database (idempotent)"
|
|
||||||
docker exec -i "$MUYU_MYSQL_CONTAINER" mysql -uroot -p"${MYSQL_ROOT_PASSWORD:?Set MYSQL_ROOT_PASSWORD in .env}" < scripts/init-iloom.sql
|
|
||||||
echo "iloom tables (ilm_*) ensured in muyu_wms."
|
|
||||||
|
|
||||||
log "Building and starting iloom services: ${SERVICES[*]}"
|
|
||||||
docker compose -f "$COMPOSE_FILE" up -d --build "${SERVICES[@]}"
|
|
||||||
|
|
||||||
log "Waiting for services to become healthy"
|
|
||||||
port_for() {
|
|
||||||
case "$1" in
|
|
||||||
gateway) echo "${GATEWAY_PORT:-8080}" ;;
|
|
||||||
auth-service) echo "${AUTH_SERVICE_PORT:-8081}" ;;
|
|
||||||
purchaser-service) echo "${PURCHASER_SERVICE_PORT:-8082}" ;;
|
|
||||||
textile-service) echo "${TEXTILE_SERVICE_PORT:-8083}" ;;
|
|
||||||
washing-service) echo "${WASHING_SERVICE_PORT:-8084}" ;;
|
|
||||||
frontend) echo "${FRONTEND_PORT:-3015}" ;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
path_for() {
|
|
||||||
case "$1" in
|
|
||||||
frontend) echo "/" ;;
|
|
||||||
*) echo "/health" ;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
all_ok=true
|
|
||||||
for svc in "${SERVICES[@]}"; do
|
|
||||||
port="$(port_for "$svc")"
|
|
||||||
path="$(path_for "$svc")"
|
|
||||||
ok=false
|
|
||||||
for _ in $(seq 1 30); do
|
|
||||||
if curl -fsS -o /dev/null "http://localhost:${port}${path}" 2>/dev/null; then
|
|
||||||
ok=true
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
sleep 2
|
|
||||||
done
|
|
||||||
if [ "$ok" = true ]; then
|
|
||||||
echo " [OK] $svc (http://localhost:${port}${path})"
|
|
||||||
else
|
|
||||||
echo " [FAIL] $svc (http://localhost:${port}${path}) did not become healthy in time"
|
|
||||||
all_ok=false
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ "$all_ok" = true ]; then
|
|
||||||
log "Deploy complete. All services healthy."
|
|
||||||
else
|
|
||||||
log "Deploy finished with failures. Check logs: docker compose -f $COMPOSE_FILE logs -f <service>"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
@ -1,16 +1,11 @@
|
|||||||
ARG BASE_REGISTRY=harbor-in-k3s.cheverjohn.me/library/
|
FROM golang:1.24-alpine AS builder
|
||||||
FROM ${BASE_REGISTRY}golang:1.24-alpine AS builder
|
|
||||||
ARG GOPROXY=https://goproxy.cn,direct
|
|
||||||
ENV GOPROXY=${GOPROXY}
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY shared/ ./shared/
|
COPY shared/ ./shared/
|
||||||
COPY textile-service/ ./textile-service/
|
COPY textile-service/ ./textile-service/
|
||||||
WORKDIR /app/textile-service
|
WORKDIR /app/textile-service
|
||||||
RUN go build -o /textile-service ./cmd/
|
RUN go build -o /textile-service ./cmd/
|
||||||
|
|
||||||
FROM ${BASE_REGISTRY}alpine:3.20
|
FROM alpine:3.20
|
||||||
ARG APK_MIRROR=mirrors.aliyun.com
|
|
||||||
RUN if [ -n "$APK_MIRROR" ]; then sed -i "s|dl-cdn.alpinelinux.org|$APK_MIRROR|g" /etc/apk/repositories; fi
|
|
||||||
RUN apk add --no-cache wget
|
RUN apk add --no-cache wget
|
||||||
COPY --from=builder /textile-service /textile-service
|
COPY --from=builder /textile-service /textile-service
|
||||||
EXPOSE 8083
|
EXPOSE 8083
|
||||||
|
|||||||
@ -12,7 +12,7 @@ type Config struct {
|
|||||||
func Load() *Config {
|
func Load() *Config {
|
||||||
return &Config{
|
return &Config{
|
||||||
Port: getEnv("PORT", "8083"),
|
Port: getEnv("PORT", "8083"),
|
||||||
DBDSN: getEnv("DB_DSN", ""),
|
DBDSN: getEnv("DB_DSN", "root:muyu2026@tcp(127.0.0.1:3306)/muyu_wms?parseTime=true&charset=utf8mb4"),
|
||||||
InternalServiceKey: getEnv("INTERNAL_SERVICE_KEY", "dev-internal-key"),
|
InternalServiceKey: getEnv("INTERNAL_SERVICE_KEY", "dev-internal-key"),
|
||||||
PurchaserServiceURL: getEnv("PURCHASER_SERVICE_URL", "http://localhost:8082"),
|
PurchaserServiceURL: getEnv("PURCHASER_SERVICE_URL", "http://localhost:8082"),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -91,8 +91,8 @@ func (r *PlanRepo) CountByFactory(ctx context.Context, factoryCompanyID string)
|
|||||||
query := `
|
query := `
|
||||||
SELECT
|
SELECT
|
||||||
COUNT(*),
|
COUNT(*),
|
||||||
COALESCE(SUM(CASE WHEN pp.status IN ('pending', 'in_progress') THEN 1 ELSE 0 END), 0),
|
SUM(CASE WHEN pp.status IN ('pending', 'in_progress') THEN 1 ELSE 0 END),
|
||||||
COALESCE(SUM(CASE WHEN pp.status = 'completed' THEN 1 ELSE 0 END), 0)
|
SUM(CASE WHEN pp.status = 'completed' THEN 1 ELSE 0 END)
|
||||||
FROM ilm_production_plan pp
|
FROM ilm_production_plan pp
|
||||||
JOIN ilm_plan_factory pf ON pp.id = pf.plan_id
|
JOIN ilm_plan_factory pf ON pp.id = pf.plan_id
|
||||||
WHERE pf.factory_id = ? AND pf.factory_type = 'textile'`
|
WHERE pf.factory_id = ? AND pf.factory_type = 'textile'`
|
||||||
|
|||||||
@ -1,16 +1,11 @@
|
|||||||
ARG BASE_REGISTRY=harbor-in-k3s.cheverjohn.me/library/
|
FROM golang:1.24-alpine AS builder
|
||||||
FROM ${BASE_REGISTRY}golang:1.24-alpine AS builder
|
|
||||||
ARG GOPROXY=https://goproxy.cn,direct
|
|
||||||
ENV GOPROXY=${GOPROXY}
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY shared/ ./shared/
|
COPY shared/ ./shared/
|
||||||
COPY washing-service/ ./washing-service/
|
COPY washing-service/ ./washing-service/
|
||||||
WORKDIR /app/washing-service
|
WORKDIR /app/washing-service
|
||||||
RUN go build -o /washing-service ./cmd/
|
RUN go build -o /washing-service ./cmd/
|
||||||
|
|
||||||
FROM ${BASE_REGISTRY}alpine:3.20
|
FROM alpine:3.20
|
||||||
ARG APK_MIRROR=mirrors.aliyun.com
|
|
||||||
RUN if [ -n "$APK_MIRROR" ]; then sed -i "s|dl-cdn.alpinelinux.org|$APK_MIRROR|g" /etc/apk/repositories; fi
|
|
||||||
RUN apk add --no-cache wget
|
RUN apk add --no-cache wget
|
||||||
COPY --from=builder /washing-service /washing-service
|
COPY --from=builder /washing-service /washing-service
|
||||||
EXPOSE 8084
|
EXPOSE 8084
|
||||||
|
|||||||
@ -12,7 +12,7 @@ type Config struct {
|
|||||||
func Load() *Config {
|
func Load() *Config {
|
||||||
return &Config{
|
return &Config{
|
||||||
Port: getEnv("PORT", "8084"),
|
Port: getEnv("PORT", "8084"),
|
||||||
DBDSN: getEnv("DB_DSN", ""),
|
DBDSN: getEnv("DB_DSN", "root:muyu2026@tcp(127.0.0.1:3306)/muyu_wms?parseTime=true&charset=utf8mb4"),
|
||||||
InternalServiceKey: getEnv("INTERNAL_SERVICE_KEY", "dev-internal-key"),
|
InternalServiceKey: getEnv("INTERNAL_SERVICE_KEY", "dev-internal-key"),
|
||||||
PurchaserServiceURL: getEnv("PURCHASER_SERVICE_URL", "http://localhost:8082"),
|
PurchaserServiceURL: getEnv("PURCHASER_SERVICE_URL", "http://localhost:8082"),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -140,8 +140,8 @@ func (r *PlanRepo) Completed(ctx context.Context, companyID string) ([]model.Was
|
|||||||
func (r *PlanRepo) CountByStatus(ctx context.Context, companyID string) (total, pending, completed int, err error) {
|
func (r *PlanRepo) CountByStatus(ctx context.Context, companyID string) (total, pending, completed int, err error) {
|
||||||
query := `SELECT
|
query := `SELECT
|
||||||
COUNT(*),
|
COUNT(*),
|
||||||
COALESCE(SUM(CASE WHEN status IN ('pending', 'washing') THEN 1 ELSE 0 END), 0),
|
SUM(CASE WHEN status IN ('pending', 'washing') THEN 1 ELSE 0 END),
|
||||||
COALESCE(SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END), 0)
|
SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END)
|
||||||
FROM ilm_washing_plan WHERE washing_factory_id = ?`
|
FROM ilm_washing_plan WHERE washing_factory_id = ?`
|
||||||
err = r.db.QueryRowContext(ctx, query, companyID).Scan(&total, &pending, &completed)
|
err = r.db.QueryRowContext(ctx, query, companyID).Scan(&total, &pending, &completed)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user