Jenkins 結合 Docker 為 .NET Core 項目實現低配版的 CI&CD

来源:https://www.cnblogs.com/myzony/archive/2019/09/25/11583702.html
-Advertisement-
Play Games

隨著項目的不斷增多,最開始單體項目手動執行 命令,手動發佈項目就不再適用了。一兩個項目可能還吃得消,10 多個項目每天讓你構建一次還是夠嗆。即便你的項目少,每次花費在發佈上面的時間累計起來都夠你改幾個 BUG 了。 所以我們需要自動化這個流程,讓項目的發佈和測試不再這麼繁瑣。在這裡我使用了 Jenk ...


隨著項目的不斷增多,最開始單體項目手動執行 docker build 命令,手動發佈項目就不再適用了。一兩個項目可能還吃得消,10 多個項目每天讓你構建一次還是夠嗆。即便你的項目少,每次花費在發佈上面的時間累計起來都夠你改幾個 BUG 了。

所以我們需要自動化這個流程,讓項目的發佈和測試不再這麼繁瑣。在這裡我使用了 Jenkins 作為基礎的 CI/CD Pipeline 工具,關於 Jenkins 的具體介紹這裡就不再贅述。在版本管理、構建項目、單元測試、集成測試、環境部署我分別使用到了 GogsDockerDocker Swarm(已與 Docker 整合) 這幾個軟體協同工作。

以下步驟我參考了 Continuous Integration with Jenkins and Docker 一文,並使用了作者提供的 groovy 文件和 slave.py 文件。

關於 Docker-CE 的安裝,請參考我的另一篇博文 《Linux 下的 Docker 安裝與使用》

一、Jenkins 的部署

既然都用了 Docker,我是不想在實體機上面安裝一堆環境,所以我使用了 Docker 的形式來部署 Jenkins 的 Master 和 Slave,省時省力。Master 就是調度管道任務的主機,也是唯一有 UI 供用戶操作的。而 Slave 就是具體的工作節點,用於執行具體的管道任務。

1.1 構建 Master 鏡像

第一步,我們在主機上建立一個 master 文件夾,並使用 vi 創建兩個 groovy 文件,這兩個文件在後面的 Dockerfile 會被使用到,下麵是 default-user.groovy 文件的代碼:

import jenkins.model.*
import hudson.security.*

def env = System.getenv()

def jenkins = Jenkins.getInstance()
jenkins.setSecurityRealm(new HudsonPrivateSecurityRealm(false))
jenkins.setAuthorizationStrategy(new GlobalMatrixAuthorizationStrategy())

def user = jenkins.getSecurityRealm().createAccount(env.JENKINS_USER, env.JENKINS_PASS)
user.save()

jenkins.getAuthorizationStrategy().add(Jenkins.ADMINISTER, env.JENKINS_USER)
jenkins.save()

接著再用 vi 創建一個新的 executors.groovy 文件,並輸入以下內容:

import jenkins.model.*
Jenkins.instance.setNumExecutors(0)

以上動作完成之後,在 master 文件夾下麵應該有兩個 groovy 文件。

兩個 master 所需要的 groovy 文件已經編寫完成,下麵來編寫 master 鏡像的 Dockerfile 文件,每一步的作用我已經用中文進行了標註。

# 使用官方的 Jenkins 鏡像作為基礎鏡像。
FROM jenkins/jenkins:latest
 
# 使用內置的 install-plugins.sh 來安裝插件。
RUN /usr/local/bin/install-plugins.sh git matrix-auth workflow-aggregator docker-workflow blueocean credentials-binding
 
# 設置 Jenkins 的管理員賬戶和密碼。
ENV JENKINS_USER admin
ENV JENKINS_PASS admin
 
# 跳過初始化安裝嚮導。
ENV JAVA_OPTS -Djenkins.install.runSetupWizard=false
 
# 將剛剛編寫的兩個 groovy 腳本複製到初始化文件夾內。
COPY executors.groovy /usr/share/jenkins/ref/init.groovy.d/
COPY default-user.groovy /usr/share/jenkins/ref/init.groovy.d/

# 掛載 jenkins_home 目錄到 Docker 捲。
VOLUME /var/jenkins_home

接著我們通過命令構建出 Master 鏡像。

docker build -t jenkins-master .

1.2 構建 Slave 鏡像

Slave 鏡像的核心是一個 slave.py 的 python 腳本,它主要執行的動作是運行 slave.jar 並和 Master 建立通信,這樣你的管道任務就能夠交給 Slave 進行執行。這個腳本所做的工作流程如下:

我們再建立一個 slave 文件夾,並使用 vi 將 python 腳本複製進去。

slave.py 的內容:

from jenkins import Jenkins, JenkinsError, NodeLaunchMethod
import os
import signal
import sys
import urllib
import subprocess
import shutil
import requests
import time

slave_jar = '/var/lib/jenkins/slave.jar'
slave_name = os.environ['SLAVE_NAME'] if os.environ['SLAVE_NAME'] != '' else 'docker-slave-' + os.environ['HOSTNAME']
jnlp_url = os.environ['JENKINS_URL'] + '/computer/' + slave_name + '/slave-agent.jnlp'
slave_jar_url = os.environ['JENKINS_URL'] + '/jnlpJars/slave.jar'
print(slave_jar_url)
process = None

def clean_dir(dir):
    for root, dirs, files in os.walk(dir):
        for f in files:
            os.unlink(os.path.join(root, f))
        for d in dirs:
            shutil.rmtree(os.path.join(root, d))

def slave_create(node_name, working_dir, executors, labels):
    j = Jenkins(os.environ['JENKINS_URL'], os.environ['JENKINS_USER'], os.environ['JENKINS_PASS'])
    j.node_create(node_name, working_dir, num_executors = int(executors), labels = labels, launcher = NodeLaunchMethod.JNLP)

def slave_delete(node_name):
    j = Jenkins(os.environ['JENKINS_URL'], os.environ['JENKINS_USER'], os.environ['JENKINS_PASS'])
    j.node_delete(node_name)

def slave_download(target):
    if os.path.isfile(slave_jar):
        os.remove(slave_jar)

    loader = urllib.URLopener()
    loader.retrieve(os.environ['JENKINS_URL'] + '/jnlpJars/slave.jar', '/var/lib/jenkins/slave.jar')

def slave_run(slave_jar, jnlp_url):
    params = [ 'java', '-jar', slave_jar, '-jnlpUrl', jnlp_url ]
    if os.environ['JENKINS_SLAVE_ADDRESS'] != '':
        params.extend([ '-connectTo', os.environ['JENKINS_SLAVE_ADDRESS' ] ])

    if os.environ['SLAVE_SECRET'] == '':
        params.extend([ '-jnlpCredentials', os.environ['JENKINS_USER'] + ':' + os.environ['JENKINS_PASS'] ])
    else:
        params.extend([ '-secret', os.environ['SLAVE_SECRET'] ])
    return subprocess.Popen(params, stdout=subprocess.PIPE)

def signal_handler(sig, frame):
    if process != None:
        process.send_signal(signal.SIGINT)

signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)

def master_ready(url):
    try:
        r = requests.head(url, verify=False, timeout=None)
        return r.status_code == requests.codes.ok
    except:
        return False

while not master_ready(slave_jar_url):
    print("Master not ready yet, sleeping for 10sec!")
    time.sleep(10)

slave_download(slave_jar)
print 'Downloaded Jenkins slave jar.'

if os.environ['SLAVE_WORING_DIR']:
    os.setcwd(os.environ['SLAVE_WORING_DIR'])

if os.environ['CLEAN_WORKING_DIR'] == 'true':
    clean_dir(os.getcwd())
    print "Cleaned up working directory."

if os.environ['SLAVE_NAME'] == '':
    slave_create(slave_name, os.getcwd(), os.environ['SLAVE_EXECUTORS'], os.environ['SLAVE_LABELS'])
    print 'Created temporary Jenkins slave.'

process = slave_run(slave_jar, jnlp_url)
print 'Started Jenkins slave with name "' + slave_name + '" and labels [' + os.environ['SLAVE_LABELS'] + '].'
process.wait()

print 'Jenkins slave stopped.'
if os.environ['SLAVE_NAME'] == '':
    slave_delete(slave_name)
    print 'Removed temporary Jenkins slave.'

上述腳本的工作基本與流程圖的一致,因為 Jenkins 針對 Python 提供了 SDK ,所以原作者使用 Python 來編寫的 “代理” 程式。不過 Jenkins 也有 RESTful API,你也可以使用 .NET Core 編寫類似的 “代理” 程式。

接著我們來編寫 Slave 鏡像的 Dockerfile 文件,因為國內伺服器訪問 Ubuntu 的源很慢,經常因為超時導致構建失敗,這裡切換成了阿裡雲的源,其內容如下:

FROM ubuntu:16.04
 
# 安裝 Docker CLI。
RUN sed -i s@/archive.ubuntu.com/@/mirrors.aliyun.com/@g /etc/apt/sources.list && apt-get clean
RUN apt-get update --fix-missing && apt-get install -y apt-transport-https ca-certificates curl openjdk-8-jre python python-pip git

# 使用阿裡雲的鏡像源。
RUN curl -fsSL http://mirrors.aliyun.com/docker-ce/linux/ubuntu/gpg | apt-key add -
RUN echo "deb [arch=amd64] http://mirrors.aliyun.com/docker-ce/linux/ubuntu xenial stable" > /etc/apt/sources.list.d/docker.list

RUN apt-get update --fix-missing && apt-get install -y docker-ce --allow-unauthenticated
RUN easy_install jenkins-webapi

# 安裝 Docker-Compose 工具。
RUN curl -L https://github.com/docker/compose/releases/download/1.21.2/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose && chmod +x /usr/local/bin/docker-compose
RUN mkdir -p /home/jenkins
RUN mkdir -p /var/lib/jenkins

# 將 slave.py 文件添加到容器。
ADD slave.py /var/lib/jenkins/slave.py

WORKDIR /home/jenkins

# 配置 Jenkins Master 的一些連接參數和 Slave 信息。
ENV JENKINS_URL "http://jenkins"
ENV JENKINS_SLAVE_ADDRESS ""
ENV JENKINS_USER "admin"
ENV JENKINS_PASS "admin"
ENV SLAVE_NAME ""
ENV SLAVE_SECRET ""
ENV SLAVE_EXECUTORS "1"
ENV SLAVE_LABELS "docker"
ENV SLAVE_WORING_DIR ""
ENV CLEAN_WORKING_DIR "true"
 
CMD [ "python", "-u", "/var/lib/jenkins/slave.py" ]

繼續使用 docker build 構建 Slave 鏡像:

docker build -t jenkins-slave .

1.3 編寫 Docker Compose 文件

這裡的 Docker Compose 文件,我取名叫 docker-compose.jenkins.yaml ,主要工作是為了啟動 Master 和 Slave 容器。

version: '3.1'
services:
    jenkins:
        container_name: jenkins
        ports:
            - '8080:8080'
            - '50000:50000'
        image: jenkins-master
    jenkins-slave:
        container_name: jenkins-slave
        restart: always
        environment:
            - 'JENKINS_URL=http://jenkins:8080'
        image: jenkins-slave
        volumes:
            - /var/run/docker.sock:/var/run/docker.sock  # 將宿主機的 Docker Daemon 掛載到容器內部。
            - /home/jenkins:/home/jenkins # 將數據掛載出來,方便後續進行釋放。
        depends_on:
            - jenkins

執行 Docker Compose 之後,我們通過 宿主機 IP:8080 就可以訪問到 Jenkins 內部了,如下圖。

二、Gogs 的部署

我們內部開發使用的 Git 倉庫是使用 Gogs 進行搭建的,Gogs 官方提供了 Docker 鏡像,那我們可以直接編寫一個 Docker Compose 快速部署 Gogs。

docker-compose.gogs.yaml 文件內容如下:

version: '3.1'
services:
  gogs:
    image: gogs/gogs
    container_name: 'gogs'
    expose:
      - '3000:3000'
    expose:
      - 22
    volumes:
      - /var/lib/docker/Persistence/Gogs:/data  # 掛載數據捲。
    restart: always

執行以下命令後,即可啟動 Gogs 程式,訪問 宿主機 IP:3000 按照配置說明安裝 Gogs 即可,之後你就可以創建遠程倉庫了。

三、Gogs 與 Jenkins 的集成

雖然大部分都推薦 Jenkins 的 Gogs Webhook 插件,不過這個插件很久不更新了,而且不支持 版本發佈 事件。針對於該問題雖然官方有 PR #62,但一直沒有合併,等到合併的時候都是猴年馬月了。這裡還是建議使用 Generic Webhook Trigger ,用這個插件來觸發 Jenkins 的管道任務。

3.1 創建流水線項目

首先找到 Jenkins 的插件中心,搜索 Generic Webhook Trigger 插件,併進行安裝。

20190924210101.gif

繼續新建一個管道任務,取名叫做 TestProject,類型選擇 Pipeline 。

首先配置項目的數據來源,選擇 SCM,並且配置 Git 遠程倉庫的地址,如果是私有倉庫則還需要設置用戶名和密碼。

3.2 Jenkins 的 Webhook 配置

流水線項目建立完成後,我們就可以開始設置 Generic WebHook Trigger 的一些參數,以便讓遠程的 Gogs 能夠觸發構建任務。

我們為 TestProject 創建一個 Token,這個 Token 是跟流水線任務綁定了,說白了就是流水線任務的一個標識。建議使用隨機 Guid 作為 Token,不然其他人都可以隨便觸發你的流水線任務進行構建了。

3.3 Gogs 的 Webhook 配置

接著來到剛剛我們建好的倉庫,找到 倉庫設置->管理 Web 鉤子->添加 Web 鉤子->Gogs

因為觸發構建不可能每次提交都觸發,一般來說都是創建了某個合併請求,或者發佈新版本的時候就會觸發流水線任務。因此這裡你可以根據自己的情況來選擇觸發事件,這裡我以合併請求為例,你可以在鉤子設置頁面點擊 測試推送。這樣就可以看到 Gogs 發送給 Jenkins 的 JSON 結構是怎樣的,你就能夠在 Jenkins 那邊有條件的進行處理。

不過測試推送只能夠針對普通的 push 事件進行測試,像 合併請求 或者 版本發佈 這種事件只能自己模擬操作了。在這裡我新建了一個用戶,Fork 了另一個帳號建立的 TestProject 倉庫。

在 Fork 的倉庫裡面,我新建了一個 Readme.md 文件,然後點擊創建合併,這個時候你看 Gogs 的 WebHook 推送記錄就有一條新的數據推送給 Jenkins,同時你也可以在 Jenkins 看到流水線任務被觸發了。

3.4 限定任務觸發條件

通過上面的步驟,我們已經將 Gogs 和 Jenkins 中的具體任務進行了綁定。不過還有一個比較尷尬的問題是,Gogs 的合併事件不僅僅包括創建合併,它的原始描述是這樣說的。

合併請求事件包括合併被開啟、關閉、重新開啟、編輯、指派、取消指派、更新標簽、清除標簽、設置里程碑、取消設置里程碑或代碼同步。

如果我們僅僅是依靠上面的配置,那麼上述所有行為都會觸發構建操作,這肯定不是我們想要的效果。還好 Generic Webhook 為我們提供了變數獲取,以及 Webhook 過濾。

我們從 Gogs 發往 Jenkins 的請求中可以看到,在 JSON 內部包含了一個 action 欄位,裡面就是本次的操作標識。那麼我們就可以想到通過判斷 action 欄位是否等於 opened 來觸發流水線任務。

首先,我們增加 2 個 Post content parameters 參數,分別獲取到 Gogs 傳遞過來的 action 和 PR 的 Id,這裡我解釋一下幾個文本框的意思。

除了這兩個 Post 參數以外,在請求頭中,Gogs 還攜帶了具體事件,我們將其一起作為過濾條件。**需要註意的是,針對於請求頭的參數,在轉換成變數時,插件會將字元轉為小寫,並會使用 "_" 代替 "-"。**

最後我們編寫一個 Optional filter ,它的 Expression 參數是正則表達式,下麵的 Text 即是源字元串。實現很簡單,當 Text 裡面的內容滿足正則表達式的時候,就會觸發流水線任務。

所以我們的 Text 字元串就是由上面三個變數的值組成,然後和我們預期的值進行匹配即可。

當然,你還想整一些更加炫酷的功能,可以使用 Jenkins 提供的 Http Request 之類的插件。因為 Gogs 提供了 API 介面,你就可以在構建完成之後,回寫給 Gogs,用於提示構建結果。

這樣的話,這種功能就有點像 Github 上面的機器人帳號了。

四、完整的項目示例

在上一節我們通過 Jenkins 的插件完成了遠程倉庫推送通知,當我們合併代碼時,Jenkins 會自動觸發執行我們的管道任務。接下來我將建立一個 .NET Core 項目,該項目擁有一個 Controller,接收到請求之後輸出 “Hello World”。隨後為該項目建立一個 xUnit 的測試項目,用於執行單元測試。

整個項目的結構如下圖:

我們需要編寫一個 UnitTest.Dockerfile 鏡像,用於執行 xUnit 單元測試。

FROM mcr.microsoft.com/dotnet/core/sdk:2.2

# 還原 NuGet 包。
WORKDIR /home/app
COPY ./ ./
RUN dotnet restore

ENTRYPOINT ["dotnet", "test" , "--verbosity=normal"]

之後為部署操作編寫一個 Deploy.Dockerfile ,這個 Dockerfile 首先還原了 NuGet 包,然後通過 dotnet publish 命令發佈了我們的網站。

FROM mcr.microsoft.com/dotnet/core/sdk:2.2 as build-image

# 還原 NuGet 包。
WORKDIR /home/app
COPY ./ ./
RUN dotnet restore

# 發佈鏡像。
COPY ./ ./
RUN dotnet publish ./TestProject.WebApi/TestProject.WebApi.csproj -o /publish/

FROM mcr.microsoft.com/dotnet/core/aspnet:2.2
WORKDIR /publish
COPY --from=build-image /publish .

ENTRYPOINT ["dotnet", "TestProject.WebApi.dll"]

兩個 Dockerfile 編寫完成之後,將其存放在項目的根目錄,以便 Slave 進行構建。

Dockerfile 編寫好了,那麼我們還要分別為兩個鏡像編寫 Docker Compose 文件,用於執行單元測試和部署行為,用於部署的文件名稱叫做 docker-compose.Deploy.yaml,內容如下:

version: '3.1'

services:
  backend:
    container_name: dev-test-backend
    image: dev-test:B${BUILD_NUMBER}
    ports: 
      - '5000:5000'
    restart: always

然後我們需要編寫運行單元測試的 Docker Compose 文件,名字叫做 docker-compose.UnitTest.yaml,內容如下:

version: '3.1'

services:
  backend:
    container_name: dev-test-unit-test
    image: dev-test:TEST${BUILD_NUMBER}

五、編寫 Jenkinsfile

node('docker') {
 
    stage '簽出代碼'
        checkout scm
    stage '單元測試'
        sh "docker build -t dev-test:TEST${BUILD_NUMBER} -f UnitTest.Dockerfile ."
        sh "docker-compose -f docker-compose.UnitTest.yaml up --force-recreate --abort-on-container-exit"
        sh "docker-compose -f docker-compose.UnitTest.yaml down -v"
    stage '部署項目'
        sh "docker build -t dev-test:B${BUILD_NUMBER} -f Deploy.Dockerfile ."
        sh 'docker-compose -f docker-compose.Deploy.yaml up -d'
}

六、最後的效果

上述操作完成之後,將這些文件放在項目根目錄。

回到 Jenkins,你可以手動執行一下任務,然後項目就被成功執行了。

至此,我們的 “低配版” CI、CD 環境就搭建成功了。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 場景 Winforn中設置ZedGraph曲線圖的屬性、坐標軸屬性、刻度屬性: https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/100112573 在主窗體中有一個ZedGraphControl控制項,如果要在本窗體獲取此控制項對象則通 ...
  • 前段時間因為公司的一個 WebFrom 項目設計到跨時區的問題,處理了一段時間,終於解決了,寫個博客記錄一下,方便以後回顧以及給他人提供一個參考的方法。 本次的項目因為跨越了多個時區,在一些時間上會受到時區的影響,比如在美國分部使用系統插入了一條數據,在美國分部顯示的時間是“2019-09-25 0 ...
  • 問題 前同事編寫的對中控考勤機數據集成項目當中,打卡數據不能實時進行上傳到平臺當中,一直靠定時全量上傳來同步數據。 閱讀代碼後,發現代碼中有實時上傳數據的邏輯,但是運行一段時間後,中控zkemkeeper SDK中的事件失效,導致員工打卡數據沒有實時上傳。 原因 查看中控SDK Demo中的示例代碼 ...
  • 場景 Winforn中設置ZedGraph曲線圖的屬性、坐標軸屬性、刻度屬性: https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/100112573 初次載入ZedGraph後,可以進行一些曲線圖屬性的設置,使曲線圖重新載入。 思路: ...
  • 場景 Winforn中設置ZedGraph曲線圖的屬性、坐標軸屬性、刻度屬性: https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/100112573 初次載入ZedGraphControl時可以通過其屬性設置其Size大小,然後實現一 ...
  • 前提 入行已經7,8年了,一直想做一套漂亮點的自定義控制項,於是就有了本系列文章。 GitHub:https://github.com/kwwwvagaa/NetWinformControl 碼雲:https://gitee.com/kwwwvagaa/net_winform_custom_contr ...
  • 場景 Winform中自定義xml配置文件後對節點進行讀取與寫入: https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/100532137 在上面實現對xml配置文件進行節點的讀取和寫入時,發現一個問題, 就是節點的內容為空相應的xml ...
  • 直接上效果圖和源碼: 1.先全選數據,然後點擊導出 2.前端ui使用取值 不懂使用Layui的可以去官網有詳細文檔連接:https://www.layui.com 3.這是後臺控制器必須要引入的文件,可以在vs工具->NuGet包管理->程式包管理設置 (裡面搜索) 4.這是後臺代碼,事件,從dal ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...