一、背景
公司的某一金融項目包含 12 個子系統,新需求一般按分支來開發,測完后合並到主干發布。開發團隊需要同時維護開發環境、測試環境、模擬環境(主干)。目前面臨最大的兩個問題:
1.子系統太多,每次發布(包括開發環境、測試環境、模擬環境)都比較耗時間
2.業務數據和報表的數據(主要是匯率、金額、手續費之類)仍依賴 Tester 人工驗證,一個 sprint 一般需要走一周 以上。
自動化測試和持續集成提了近一年,但由於各種因素的影響,還是在原地踏步。所幸仍有一兩個不怕背黑鍋的搭檔,今日終於邁出這最艱難的第一步。
二、關於持續集成(Continuous integration,CI)
一個比較簡單的定義如下:持續集成(CI)是一種實踐,可以讓團隊在持續的基礎 上收到反饋並進行改進,不必等到開發周期后期才尋找和修復缺陷。
一般地包括以下幾個步驟:
1.CI 服務器從版本管理服務器檢查代碼版本,如果有新的提交,則下載最新的源碼版本
2.編譯最新版本的源碼
3.運行所有測試腳本
4.進行代碼分析
5.產生更新包,提供發布(或寫腳本自動發布)
以上步驟串行執行,任何一個環節失敗,則該 build 失敗。CI 服務器將給開發團隊相應的反饋。
三、關於 CruiseControl.Net
CruiseControl.Net 是一款開源的自動集成工具 http://www.cruisecontrolnet.org/projects/ccnet/wiki。
源碼地址:https://github.com/ccnet/CruiseControl.NET 或 http://sourceforge.net/projects/ccnet/files/
四、環境准備
找一個干凈的虛擬機環境,WInServer2008/Win7,安裝以下工具:
安裝 IIS / SFTP
.NET/編譯/Test 環境:VS 2010(用到其中的 MsBuild,MsTest)
源碼管理服務器:VisualSVNServer 2.1
CC.NET 服務端:CruiseControl.NET-1.8.3
開發機(本機)安裝以下工具:
開發工具:VS 2010
源碼管理客戶端:TortoiseSVN-1.8
CC.NET 客戶端:CruiseControl.NET-CCTray
步驟
1.安裝 IIS / SFTP
控制版面>程序和功能>打開或關閉 Windows 功能
2. VS 2010
這里用的是旗艦版
3.安裝好 VisualSVNServer 2.1 ,添加一個項目 Test,添加兩個用戶 ci、harvey.choi
(申請公司開通 ci@XXXcompany.com 郵箱,后面配置 publisher 時會用到)
4.安裝 CC.NET 服務端,安裝目錄下有一個 webdashboard 文件夾,在 IIS 中添加一個網站,指向這個文件夾
5.創建一個測試解決方案,添加 Lib/LibTest 項目,提交到 SVN Server
測試的邏輯盡量簡單
1 //Lib/Calculator.cs 2 namespace Lib 3 { 4 public class Calculator 5 { 6 public int Add(params int[] items) 7 { 8 var result = 0; 9 foreach (var i in items) 10 result += i; 11 return result; 12 } 13 } 14 } 15 16 //LibTest/Test.cs 17 namespace LibTest 18 { 19 [TestClass] 20 public class Test 21 { 22 [TestMethod] 23 public void Calculator_Add() 24 { 25 var c = new Lib.Calculator(); 26 27 var result = c.Add(1, 2, 3); 28 29 Assert.AreEqual(result, 6); 30 } 31 } 32 }
五、CC.NET 配置
CC.NET 的配置文件是 安裝目錄\server\ccnet.config
官方文檔:http://www.cruisecontrolnet.org/attachments/download/10/customisingcruisecontrol-net.pdf
在線:http://www.cruisecontrolnet.org/projects/ccnet/wiki/Configuration
CC.NET 提供 windows 服務 及 控制台程序兩種方式運行。windows 服務需要手動啟動(可按需修改為自動啟動);控制台程序為 安裝目錄\安裝目錄\server\ccnet.exe。本 demo 中用控制台方式運行,方便查看運行結果。
1.源代碼管理
ccnet.config 配置如下:
<cruisecontrol xmlns:cb="urn:ccnet.config.builder"> <project name="Lib.Sln"> <!--標簽--> <labeller type="dateLabeller"/> <artifactDirectory>C:\CC.NET\Server\Test\ArtifactDirectory</artifactDirectory> <!--項目的目錄--> <workingDirectory >C:\CC.NET\Server\Test\WorkingDirectory</workingDirectory> <!--自動構建結果的查看地址--> <webURL>http://vw-caihaihua/CC/server/local/project/Lib.Sln/ViewProjectReport.aspx</webURL> <!--自動運行時間間隔--> <triggers> <!--源碼修改觸發--> <intervalTrigger seconds="10" /> </triggers> <maxSourceControlRetries>5</maxSourceControlRetries> <!--源代碼管理(SVN)--> <sourcecontrol type="svn"> <trunkUrl>https://vw-caihaihua/svn/Test/trunk/</trunkUrl> <executable>C:\Program Files (x86)\VisualSVN Server\bin\svn.exe</executable> <workingDirectory>C:\CC.NET\Server\Test\WorkingDirectory\</workingDirectory> <username>ci</username> <password>123456</password> </sourcecontrol> </project> </cruisecontrol>
執行 ccnet.exe 得到結果如下。配置的工作目錄中已經是最新版本的代碼:
我們可以 CC.NET 自帶的 Dashboard 站點查看實時的信息,另外 Dashboard 站點還提供了 ForceBuild 功能:
2.觸發器
CC.NET 提供多種觸發器。本 Demo 中使用兩種觸發器
intervalTrigger 間隔觸發,條件為如果源碼有更新(IfModificationExists),同時配置 modificationDelaySeconds 節點;為了方便看到效果,這里 intervalTrigger seconds 設為 10,modificationDelaySeconds 設為 30。
scheduleTrigger 定時觸發,條件為強制觸發(ForceBuild),用於 DailyBuild。
ccnet.config 配置如下:
<!--自動運行時間間隔--> <triggers> <!--源碼修改觸發--> <intervalTrigger seconds="10" buildCondition="IfModificationExists" /> <!--每日構建--> <scheduleTrigger time="19:00" buildCondition="ForceBuild"> <weekDays> <!--<weekDay>Sunday</weekDay>--> <weekDay>Monday</weekDay> <weekDay>Tuesday</weekDay> <weekDay>Wednesday</weekDay> <weekDay>Thursday</weekDay> <weekDay>Friday</weekDay> <!--<weekDay>Saturday</weekDay>--> </weekDays> </scheduleTrigger> </triggers> <!--對源碼修改延遲處理時間間隔--> <modificationDelaySeconds>30</modificationDelaySeconds> <maxSourceControlRetries>5</maxSourceControlRetries>
客戶端修改代碼並提交到 SVN,結果如下:
3.編譯
編譯方式可以用 VS 工具,也可以用 .NET Framework 自帶的 MsBuild。
ccnet.config 配置如下:
<tasks>
<!--<devenv> <solutionfile>C:\CC.NET\Server\Test\WorkingDirectory\Lib.sln</solutionfile> <configuration>Debug</configuration> </devenv>--> <!--清理解決方案--> <msbuild> <executable>C:\Windows\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe</executable> <buildArgs>/t:clean /t:rebuild /p:configuration=debug</buildArgs> <workingDirectory>C:\CC.NET\Server\Test\WorkingDirectory</workingDirectory> <projectFile>Lib.sln</projectFile> <logger>ThoughtWorks.CruiseControl.MsBuild.XmlLogger,C:\Program Files (x86)\CruiseControl.NET\server\ThoughtWorks.CruiseControl.MsBuild.dll</logger> </msbuild> </tasks>
Dashboard 站點中觸發 ForceBuild,結果如下:
4.運行 UnitTest
測試腳本用的是 VS 自帶的 UnitTest。
ccnet.config 配置如下:
<tasks> <!--運行UnitTest--> <exec> <executable>C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\MSTest.exe</executable> <baseDirectory>C:\CC.NET\Server\Test\WorkingDirectory</baseDirectory> <buildArgs>/testcontainer:LibTest\bin\Debug\LibTest.dll</buildArgs> <buildTimeoutSeconds>6000</buildTimeoutSeconds> </exec> </tasks>
Dashboard 站點中觸發 ForceBuild,結果如下:
客戶端修改測試腳本,提交,結果如下:
[TestMethod] public void Calculator_Add() { var c = new Lib.Calculator(); var result = c.Add(1, 2, 3); Assert.AreEqual(result, 6); //Error Test Assert.AreEqual(7, 6); }
5.發布網站(Asp.NET 網站/WebServcie/WcfService)
首先在解決方案中添加 WcfService/WcfServiceTest 項目,提交到 SVN
公司的 Wcf 服務是寄宿在 IIS 上,我們需要用 MsBuild 編譯成安裝包。
ccnet.config 配置如下:
<tasks>
<!--發布Wcf服務到本機--> <msbuild> <executable>C:\Windows\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe</executable> <workingDirectory>C:\CC.NET\Server\Test\WorkingDirectory\WcfService</workingDirectory> <projectFile>WcfService.csproj</projectFile> <buildArgs> /t:ResolveReferences;Compile /t:_CopyWebApplication /p:Configuration=Release /p:WebProjectOutputDir=C:\CC.NET\Server\Test\PublishDirectory\WcfService /p:OutputPath=C:\CC.NET\Server\Test\PublishDirectory\WcfService\bin </buildArgs> </msbuild> </tasks>
結果如下:
6.WcfService Test
由於項目的 Wcf 服務是寄宿在 IIS。VS 工具中用到的是 Asp.NET Development Server(WebDev.WebServer40.EXE),但我沒想到怎么用。
此處用到的方法是在 IIS 中創建一個站點,用 MsBuild 發布 Wcf 服務到該站點(第 5 點)。然后把 WcfServiceTest 的服務引用指向該站點。
ccnet.config 配置如下:
<!--運行UnitTest--> <exec> <executable>C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\MSTest.exe</executable> <baseDirectory>C:\CC.NET\Server\Test\WorkingDirectory</baseDirectory> <buildArgs>/testcontainer:WcfServiceTest\bin\Debug\WcfServiceTest.dll</buildArgs> <buildTimeoutSeconds>6000</buildTimeoutSeconds> </exec>
結果如下:
7.ftp 發布
當開發環境(或測試環境)在 CC.NET 服務器,可以用第 5 點的方法來完成自動發布。
但是往往 CI 跟環境是獨立的。這時候我們可以用 ftp 來發布。
首先添加一個 ftp 站點,指向開發環境(或測試環境)的 Wcf 服務的目錄,創建一個賬戶 admin 並分配權限到該 ftp 站點。
ccnet.config 配置如下:
<tasks> <ftp> <serverName>127.0.0.1</serverName> <userName>admin</userName> <password>admin</password> <action>UploadFolder</action> <ftpFolderName></ftpFolderName> <localFolderName>C:\CC.NET\Server\Test\PublishDirectory\WcfService</localFolderName> <recursiveCopy>true</recursiveCopy> <timeDifference>1</timeDifference> </ftp> </tasks>
ForceBuild 結果如下:
8.打包
發布更新包配置如下:
<tasks>
<package> <name>Lib.sln</name> <compression>9</compression> <manifest type="defaultManifestGenerator" /> <packageList> <packageFile sourceFile="C:\CC.NET\Server\Test\PublishDirectory\WcfService\*.svc" targetFolder="WcfService" /> <packageFile sourceFile="C:\CC.NET\Server\Test\PublishDirectory\WcfService\*.Release.config" targetFolder="WcfService" /> <packageFile sourceFile="C:\CC.NET\Server\Test\PublishDirectory\WcfService\bin\*.dll" targetFolder="WcfService\bin" /> <!--<packageFolder sourceFolder="C:\CC.NET\Server\Test\PublishDirectory\WcfService" targetFolder="WcfService" fileFilter="*.*" flatten="false" includeSubFolders="false" />--> </packageList> </package> </tasks>
ForceBuild 結果如下:
我們可以通過 CCTracy 工具下載下載更新包:
9.歷史備份
配置 publishDir ,則每次 Build 成功后都會把該版本的源碼備份到指定目錄,配置如下:
<publishers> <!--標簽備份(如果成功)--> <buildpublisher> <sourceDir>C:\CC.NET\Server\Test\WorkingDirectory</sourceDir> <publishDir>C:\CC.NET\Server\Test\HistoryVersion</publishDir> </buildpublisher> <modificationHistory/> <statistics/> <xmllogger/> </publishers>
10.郵件通知
CC.NET 提供 email 方式反饋給開發團隊,觸發條件有 Always/Success/Change/Fixed/Failed/Exception
(1)Modifier:一般情況下,SVN 的用戶名跟公司郵箱名是對應的,用 converters 配置簡單實現,更復雜的場景尚待研究
(2)特定人員或角色:配置 users + groups。
按設想的場景中,CI leader 需要得到 Change 和 Exception 的反饋;Tester 需要得到 Fixed 的反饋;開發團隊需要得到 Success/Fixed/Failed 的反饋;源碼修改者需要得到 Failed/Fixed 的反饋。合理性尚待驗證。
ccnet.config 配置如下:
<publishers> <!--郵件通知--> <email mailhost="smtp.live.com" mailport="25" mailhostUsername="ci@XXXXCompany.com" mailhostPassword="*********" from="ci@XXXXCompany.com" useSSL="TRUE" includeDetails="true"> <!--郵件標題配置--> <subjectPrefix>[CI@XXXCompany]</subjectPrefix> <subjectSettings> <!-- Success/Broken/StillBroken/Fixed/Exception--> <subject buildResult="Success" value="${CCNetProject} Build Successful: Label ${CCNetLabel}, last checkin(s) by ${CCNetModifyingUsers}.(at ${CCNetBuildDate} ${CCNetBuildDate})" /> <subject buildResult="Fixed" value="${CCNetProject} Build Fixed: Label ${CCNetLabel}, last checkin(s) by ${CCNetModifyingUsers}.(at ${CCNetBuildDate} ${CCNetBuildDate})" /> <subject buildResult="Broken" value="${CCNetProject} Broke: last checkin(s) by ${CCNetFailureUsers}.(at ${CCNetBuildDate} ${CCNetBuildDate})" /> <subject buildResult="StillBroken" value="${CCNetProject} Still Broken: last checkin(s) by ${CCNetFailureUsers}.(at ${CCNetBuildDate} ${CCNetBuildDate})" /> <subject buildResult="Exception" value="${CCNetProject} In Exception: Please check status of network / sourcecontrol.(at ${CCNetBuildDate} ${CCNetBuildDate})" /> </subjectSettings> <!--通知相關的源碼修改者--> <converters> <regexConverter find="$" replace="@XXXXCompany.com"/> </converters> <modifierNotificationTypes> <notificationType>Failed</notificationType> <notificationType>Fixed</notificationType> </modifierNotificationTypes> <!--通知特定人員(角色)-->
<users> <user group="leader" name="ci.XXXCompany" address="ci@XXXCompany.com"/> <user group="developer" name="harvey.choi" address="harvey.choi@XXXXCompany.com"/> <user group="tester" name="jolin" address="jolin.tang@XXXCompany.com"/> </users> <groups> <group name="leader"> <notifications> <!--Always/Success/Change/Fixed/Failed/Exception --> <notificationType>Change</notificationType> <notificationType>Exception</notificationType> </notifications> </group> <group name="developer"> <notifications> <notificationType>Success</notificationType> <notificationType>Fixed</notificationType> <notificationType>Failed</notificationType> </notifications> </group> <group name="tester"> <notifications> <notificationType>Fixed</notificationType> </notifications> </group> </groups> </email> <xmllogger/> </publishers>
用 harvey.choi 賬戶修改源碼並提交到 SVN。結果如下:
harvey.choi@XXXXCompany.com 收到以下郵件:
完整的 ccnet.config 配置如下,注意 tasks 是按順序串行執行,任一步驟出錯則整個 build 失敗:
<cruisecontrol xmlns:cb="urn:ccnet.config.builder"> <project name="Lib.Sln"> <!--標簽--> <labeller type="dateLabeller"/> <artifactDirectory>C:\CC.NET\Server\Test\ArtifactDirectory</artifactDirectory> <!--項目的目錄--> <workingDirectory >C:\CC.NET\Server\Test\WorkingDirectory</workingDirectory> <!--自動構建結果的查看地址--> <webURL>http://vw-caihaihua/CC/server/local/project/Lib.Sln/ViewProjectReport.aspx</webURL> <!--自動運行時間間隔--> <triggers> <!--源碼修改觸發--> <intervalTrigger seconds="10" buildCondition="IfModificationExists " /> <!--每日構建--> <scheduleTrigger time="19:00" buildCondition="ForceBuild"> <weekDays> <!--<weekDay>Sunday</weekDay>--> <weekDay>Monday</weekDay> <weekDay>Tuesday</weekDay> <weekDay>Wednesday</weekDay> <weekDay>Thursday</weekDay> <weekDay>Friday</weekDay> <!--<weekDay>Saturday</weekDay>--> </weekDays> </scheduleTrigger> </triggers> <!--對源碼修改延遲處理時間間隔--> <modificationDelaySeconds>30</modificationDelaySeconds> <maxSourceControlRetries>5</maxSourceControlRetries> <!--源代碼管理(SVN)--> <sourcecontrol type="svn"> <trunkUrl>https://vw-caihaihua/svn/Test/trunk/</trunkUrl> <executable>C:\Program Files (x86)\VisualSVN Server\bin\svn.exe</executable> <workingDirectory>C:\CC.NET\Server\Test\WorkingDirectory\</workingDirectory> <username>ci</username> <password>123456</password> </sourcecontrol> <tasks> <!--<devenv> <solutionfile>C:\CC.NET\Server\Test\WorkingDirectory\Lib.sln</solutionfile> <configuration>Debug</configuration> </devenv>--> <!--清理解決方案--> <msbuild> <executable>C:\Windows\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe</executable> <buildArgs>/t:clean /t:rebuild /p:configuration=debug</buildArgs> <workingDirectory>C:\CC.NET\Server\Test\WorkingDirectory</workingDirectory> <projectFile>Lib.sln</projectFile> <logger>ThoughtWorks.CruiseControl.MsBuild.XmlLogger,C:\Program Files (x86)\CruiseControl.NET\server\ThoughtWorks.CruiseControl.MsBuild.dll</logger> </msbuild> <!--運行UnitTest--> <exec> <executable>C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\MSTest.exe</executable> <baseDirectory>C:\CC.NET\Server\Test\WorkingDirectory</baseDirectory> <buildArgs>/testcontainer:LibTest\bin\Debug\LibTest.dll</buildArgs> <buildTimeoutSeconds>6000</buildTimeoutSeconds> </exec> <!--發布Wcf服務到本機--> <msbuild> <executable>C:\Windows\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe</executable> <workingDirectory>C:\CC.NET\Server\Test\WorkingDirectory\WcfService</workingDirectory> <projectFile>WcfService.csproj</projectFile> <buildArgs> /t:ResolveReferences;Compile /t:_CopyWebApplication /p:Configuration=Release /p:WebProjectOutputDir=C:\CC.NET\Server\Test\PublishDirectory\WcfService /p:OutputPath=C:\CC.NET\Server\Test\PublishDirectory\WcfService\bin </buildArgs> </msbuild> <!--運行UnitTest--> <exec> <executable>C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\MSTest.exe</executable> <baseDirectory>C:\CC.NET\Server\Test\WorkingDirectory</baseDirectory> <buildArgs>/testcontainer:WcfServiceTest\bin\Debug\WcfServiceTest.dll</buildArgs> <buildTimeoutSeconds>6000</buildTimeoutSeconds> </exec> <!--啟動 Asp.NET Development Server--> <!--<exec> <executable>C:\Program Files (x86)\Common Files\microsoft shared\DevServer\10.0\WebDev.WebServer40.EXE</executable> <buildArgs>/port:9999 /path:C:\CC.NET\Server\Test\PublishDirectory\WcfService</buildArgs> <buildTimeoutSeconds>6000</buildTimeoutSeconds> </exec>--> <!--ftp發布Wcf服務到開發環境--> <ftp> <serverName>127.0.0.1</serverName> <userName>admin</userName> <password>admin</password> <action>UploadFolder</action> <ftpFolderName></ftpFolderName> <localFolderName>C:\CC.NET\Server\Test\PublishDirectory\WcfService</localFolderName> <recursiveCopy>true</recursiveCopy> <timeDifference>1</timeDifference> </ftp> <package> <name>Lib.sln</name> <compression>9</compression> <manifest type="defaultManifestGenerator" /> <packageList> <packageFile sourceFile="C:\CC.NET\Server\Test\PublishDirectory\WcfService\*.svc" targetFolder="WcfService" /> <packageFile sourceFile="C:\CC.NET\Server\Test\PublishDirectory\WcfService\*.Release.config" targetFolder="WcfService" /> <packageFile sourceFile="C:\CC.NET\Server\Test\PublishDirectory\WcfService\bin\*.dll" targetFolder="WcfService\bin" /> <!--<packageFolder sourceFolder="C:\CC.NET\Server\Test\PublishDirectory\WcfService" targetFolder="WcfService" fileFilter="*.*" flatten="false" includeSubFolders="false" />--> </packageList> </package> </tasks> <state type="state" directory="C:\CC.NET\server\CCState"/> <publishers> <!--標簽備份(如果成功)--> <buildpublisher> <sourceDir>C:\CC.NET\Server\Test\WorkingDirectory</sourceDir> <publishDir>C:\CC.NET\Server\Test\HistoryVersion</publishDir> </buildpublisher> <modificationHistory/> <statistics/> <!--郵件通知--> <email mailhost="smtp.live.com" mailport="25" mailhostUsername="ci@XXXCompany.com" mailhostPassword="******" from="ci@XXXCompany.com" useSSL="TRUE" includeDetails="true"> <!--郵件標題配置--> <subjectPrefix>[CI@XXXCompany]</subjectPrefix> <subjectSettings> <!-- Success/Broken/StillBroken/Fixed/Exception--> <subject buildResult="Success" value="${CCNetProject} Build Successful: Label ${CCNetLabel}, last checkin(s) by ${CCNetModifyingUsers}.(at ${CCNetBuildDate} ${CCNetBuildDate})" /> <subject buildResult="Fixed" value="${CCNetProject} Build Fixed: Label ${CCNetLabel}, last checkin(s) by ${CCNetModifyingUsers}.(at ${CCNetBuildDate} ${CCNetBuildDate})" /> <subject buildResult="Broken" value="${CCNetProject} Broke: last checkin(s) by ${CCNetFailureUsers}.(at ${CCNetBuildDate} ${CCNetBuildDate})" /> <subject buildResult="StillBroken" value="${CCNetProject} Still Broken: last checkin(s) by ${CCNetFailureUsers}.(at ${CCNetBuildDate} ${CCNetBuildDate})" /> <subject buildResult="Exception" value="${CCNetProject} In Exception: Please check status of network / sourcecontrol.(at ${CCNetBuildDate} ${CCNetBuildDate})" /> </subjectSettings> <!--收件人配置--> <converters> <regexConverter find="$" replace="@XXXCompany.com"/> </converters> <modifierNotificationTypes> <notificationType>Failed</notificationType> <notificationType>Fixed</notificationType> </modifierNotificationTypes> <users> <user group="leader" name="ci.XXXCompany" address="ci@XXXCompany.com"/> <user group="developer" name="harvey.choi" address="harvey.choi@XXXCompany.com"/> <user group="tester" name="jolin" address="jolin.tang@XXXCompany.com"/> </users> <groups> <group name="leader"> <notifications> <!--Always/Success/Change/Fixed/Failed --> <notificationType>Change</notificationType> <notificationType>Exception</notificationType> </notifications> </group> <group name="developer"> <notifications> <notificationType>Success</notificationType> <notificationType>Fixed</notificationType> <notificationType>Failed</notificationType> </notifications> </group> <group name="tester"> <notifications> <notificationType>Fixed</notificationType> </notifications> </group> </groups> </email> <xmllogger/> </publishers> </project> </cruisecontrol>
六、結語
CI走得好累,概括來說:高層的態度是關鍵,如果領導不批啥都不用說;舊員工的態度往往是最大的阻力,一個有歷史的團隊總有一些人更願意按舊的流程走,不願意接受新的東西,不求有功但求無過。
關於 CC.NET 中文的資料相對比較少,最好還是耐心的啃下官方的 E 文文檔。
這個 CC.NET 的 Demo 斷斷續續搭了一周(日常任務不變,時間得自己擠),也差不多有定論。當然從 HelloWorld 到真正落地還需解決n多問題。
如果你發現 Demo 有哪些不正確的,歡迎跟帖指正。