diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml new file mode 100644 index 0000000..9dd470e --- /dev/null +++ b/.github/workflows/ci-cd.yml @@ -0,0 +1,180 @@ +name: TakeoutSaaS CI/CD + +on: + push: + branches: + - master + workflow_dispatch: + +env: + REGISTRY: ${{ secrets.REGISTRY }} + REGISTRY_USERNAME: ${{ secrets.REGISTRY_USERNAME }} + REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }} + DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }} + DEPLOY_USER: ${{ secrets.DEPLOY_USER }} + DEPLOY_PASSWORD: ${{ secrets.DEPLOY_PASSWORD }} + +jobs: + detect: + runs-on: ubuntu-latest + outputs: + services: ${{ steps.collect.outputs.services }} + image_tag: ${{ steps.collect.outputs.image_tag }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - id: collect + shell: bash + run: | + set -euo pipefail + + BASE="${{ github.event.before }}" + if [ -z "$BASE" ] || [ "$BASE" = "0000000000000000000000000000000000000000" ]; then + if git rev-parse HEAD^ >/dev/null 2>&1; then + BASE="$(git rev-parse HEAD^)" + else + BASE="" + fi + fi + + if [ -z "$BASE" ]; then + CHANGED=$(git ls-tree -r --name-only HEAD) + else + CHANGED=$(git diff --name-only "$BASE" HEAD || true) + fi + + echo "本次变更文件:" + echo "$CHANGED" + + deploy_all=false + services=() + + hit() { echo "$CHANGED" | grep -qE "$1"; } + + if hit '^src/(Domain|Application|Infrastructure|Core|Modules)/'; then deploy_all=true; fi + if hit '^Directory\.Build\.props$'; then deploy_all=true; fi + + if hit '^src/Api/TakeoutSaaS.AdminApi/'; then services+=("admin-api"); fi + if hit '^src/Api/TakeoutSaaS.MiniApi/'; then services+=("mini-api"); fi + if hit '^src/Api/TakeoutSaaS.UserApi/'; then services+=("user-api"); fi + + if $deploy_all || [ ${#services[@]} -eq 0 ]; then + services=("admin-api" "mini-api" "user-api") + fi + + printf '需要处理的服务: %s\n' "${services[*]}" + + SERVICES_LIST="${services[*]}" + export SERVICES_LIST + SERVICES_JSON=$(python - <<'PY' +import json, os +raw = os.environ.get("SERVICES_LIST", "").split() +print(json.dumps(raw)) +PY +) + + echo "services=$SERVICES_JSON" >> "$GITHUB_OUTPUT" + TAG=$(date +%Y%m%d%H%M%S) + echo "image_tag=$TAG" >> "$GITHUB_OUTPUT" + + build: + runs-on: ubuntu-latest + needs: detect + if: needs.detect.outputs.services != '[]' + strategy: + matrix: + service: ${{ fromJson(needs.detect.outputs.services) }} + steps: + - uses: actions/checkout@v4 + + - uses: docker/setup-buildx-action@v3 + + - uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ env.REGISTRY_USERNAME }} + password: ${{ env.REGISTRY_PASSWORD }} + + - name: Build and push ${{ matrix.service }} + env: + SERVICE: ${{ matrix.service }} + IMAGE_TAG: ${{ needs.detect.outputs.image_tag }} + run: | + set -euo pipefail + + case "$SERVICE" in + admin-api) + DOCKERFILE="src/Api/TakeoutSaaS.AdminApi/Dockerfile" + IMAGE="$REGISTRY/admin-api:$IMAGE_TAG" + ;; + mini-api) + DOCKERFILE="src/Api/TakeoutSaaS.MiniApi/Dockerfile" + IMAGE="$REGISTRY/mini-api:$IMAGE_TAG" + ;; + user-api) + DOCKERFILE="src/Api/TakeoutSaaS.UserApi/Dockerfile" + IMAGE="$REGISTRY/user-api:$IMAGE_TAG" + ;; + *) + echo "未知服务:$SERVICE" + exit 1 + ;; + esac + + if [ ! -f "$DOCKERFILE" ]; then + echo "未找到 Dockerfile: $DOCKERFILE" + exit 1 + fi + + docker build -f "$DOCKERFILE" -t "$IMAGE" . + docker push "$IMAGE" + + deploy: + runs-on: ubuntu-latest + needs: + - detect + - build + if: needs.detect.outputs.services != '[]' + strategy: + matrix: + service: ${{ fromJson(needs.detect.outputs.services) }} + steps: + - name: Install sshpass + run: sudo apt-get update && sudo apt-get install -y sshpass + + - name: Deploy ${{ matrix.service }} + env: + SERVICE: ${{ matrix.service }} + IMAGE_TAG: ${{ needs.detect.outputs.image_tag }} + run: | + set -euo pipefail + + case "$SERVICE" in + admin-api) + IMAGE="$REGISTRY/admin-api:$IMAGE_TAG" + PORT=7801 + ;; + mini-api) + IMAGE="$REGISTRY/mini-api:$IMAGE_TAG" + PORT=7701 + ;; + user-api) + IMAGE="$REGISTRY/user-api:$IMAGE_TAG" + PORT=7901 + ;; + *) + echo "未知服务:$SERVICE" + exit 1 + ;; + esac + + sshpass -p "$DEPLOY_PASSWORD" ssh -o StrictHostKeyChecking=no "$DEPLOY_USER@$DEPLOY_HOST" </dev/null || true +docker rm $SERVICE 2>/dev/null || true +docker run -d --name $SERVICE --restart=always -p $PORT:$PORT $IMAGE +EOF diff --git a/src/Api/TakeoutSaaS.AdminApi/Dockerfile b/src/Api/TakeoutSaaS.AdminApi/Dockerfile new file mode 100644 index 0000000..27f5733 --- /dev/null +++ b/src/Api/TakeoutSaaS.AdminApi/Dockerfile @@ -0,0 +1,12 @@ +FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build +WORKDIR /src +COPY . . +RUN dotnet restore src/Api/TakeoutSaaS.AdminApi/TakeoutSaaS.AdminApi.csproj +RUN dotnet publish src/Api/TakeoutSaaS.AdminApi/TakeoutSaaS.AdminApi.csproj -c Release -o /app/publish + +FROM mcr.microsoft.com/dotnet/aspnet:10.0 +WORKDIR /app +COPY --from=build /app/publish . +EXPOSE 7801 +ENV ASPNETCORE_URLS=http://+:7801 +ENTRYPOINT ["dotnet", "TakeoutSaaS.AdminApi.dll"] diff --git a/src/Api/TakeoutSaaS.MiniApi/Dockerfile b/src/Api/TakeoutSaaS.MiniApi/Dockerfile new file mode 100644 index 0000000..f737a92 --- /dev/null +++ b/src/Api/TakeoutSaaS.MiniApi/Dockerfile @@ -0,0 +1,12 @@ +FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build +WORKDIR /src +COPY . . +RUN dotnet restore src/Api/TakeoutSaaS.MiniApi/TakeoutSaaS.MiniApi.csproj +RUN dotnet publish src/Api/TakeoutSaaS.MiniApi/TakeoutSaaS.MiniApi.csproj -c Release -o /app/publish + +FROM mcr.microsoft.com/dotnet/aspnet:10.0 +WORKDIR /app +COPY --from=build /app/publish . +EXPOSE 7701 +ENV ASPNETCORE_URLS=http://+:7701 +ENTRYPOINT ["dotnet", "TakeoutSaaS.MiniApi.dll"] diff --git a/src/Api/TakeoutSaaS.UserApi/Dockerfile b/src/Api/TakeoutSaaS.UserApi/Dockerfile new file mode 100644 index 0000000..22e0ab8 --- /dev/null +++ b/src/Api/TakeoutSaaS.UserApi/Dockerfile @@ -0,0 +1,12 @@ +FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build +WORKDIR /src +COPY . . +RUN dotnet restore src/Api/TakeoutSaaS.UserApi/TakeoutSaaS.UserApi.csproj +RUN dotnet publish src/Api/TakeoutSaaS.UserApi/TakeoutSaaS.UserApi.csproj -c Release -o /app/publish + +FROM mcr.microsoft.com/dotnet/aspnet:10.0 +WORKDIR /app +COPY --from=build /app/publish . +EXPOSE 7901 +ENV ASPNETCORE_URLS=http://+:7901 +ENTRYPOINT ["dotnet", "TakeoutSaaS.UserApi.dll"]