1. 程式人生 > >3天搞定的小型B/S內部管理類軟體定製開發專案【軟體開發實戰10步驟詳解】

3天搞定的小型B/S內部管理類軟體定製開發專案【軟體開發實戰10步驟詳解】

 

   十一休假,杭州西湖邊逛了一圈只能用人山人海來形容,浙大紫金港校區也逛了一圈風景如畫,建設得真不錯很棒,假期就去了這2個地方,然後在家裡陪老婆、看孩子、洗尿布、打了幾局星際爭霸,在網上接了一個B/S架構的內部管理類定製軟體、淘寶上收了600元辛苦費後就開始行動了、現在把整個開發過程講解分享如下文。

客戶需求

   一個簡單的專案管理資訊的,新增、刪除、編輯、查詢等功能的實現,把大的框架做好就可以,細節他會自己完善好,想想也很簡單的一樣,本以為1天時間就足可以搞定了,結果足足幹了3天才算竣工,由於客戶也是軟體開發人員,溝通起來也比較順暢一些,否則可能不止需要3天了。

   專案雖然很小,但是由於跟我的主營方向是一致的,同時也想鍛鍊一下做專案的成熟程度有多高、有多快,所以也沒嫌棄錢多錢少,開開心心就把這個專案開始啟動了。專案雖然小但五臟俱全來形容了這個活兒了。

   很多必要的功能雖然在需求裡沒有提到,但是都是需要做好的,例如:

         01:系統的登入功能、這個是必然要有的。

         02:記住密碼功能,而且是需要有加密功能的。

         03:退出系統的功能,總需要能安全退出系統的吧。

         04:使用者管理功能,例如哪些使用者才能登入到系統裡來?

         05:使用者修改密碼功能,停用功能,設定密碼功能等。

         06:使用者可以訪問那些選單等?

         07:使用者有哪些操作許可權等?

         08:角色管理功能,使用者角色關係功能,角色的許可權功能。

         09:友善的選單導航功能。

         10:沒有許可權的友善提示功能。

         11:附件的管理、上傳、下載、刪除等等。

         12:查詢分頁功能等等,雖然都沒提出,但是這些功能都需要做好的。

整體開發思路

資料庫分2個設計:業務資料 + 許可權資料,互相不干擾,相互比較獨立一些。

整個系統分3個種干係人: 開發人員 + 系統管理員 + 操作人員,他們對系統各自的期望值及操作的內容如下圖。

為了快速搞定專案,畢竟專案的預算很有限,所有有些功能模組能不開發就不開發,那我們就用現成的成熟元件,通用許可權管理系統,做為使用者賬戶管理、許可權管理

選單管理的核心元件,這樣可以大大的提高工作效率,可以靈活配置管理這個小型B/S專案資訊管理系統了。

專案的具體開發製作步驟

步驟1:專案雖然很小,但是還是按規範的軟體開發步驟比較好,我們還是本著先有設計後有開發的思路,先不急著寫程式碼,我們先把資料庫結構設計好,本系統應該只設計一個表就可以了,我們先要想好哪些資料用什麼格式儲存,都需要哪些欄位來表示各種屬性,效果圖如下,我們用了資料庫建模工具PowerDesigner,來設計我們的業務模型中的資料模型吧,這就是所謂的開發上的領域模型吧,其實這個設計還是蠻關鍵的,設計亂了後面都會亂套了。

步驟2:通過用設計好的模型,我們先手工建立相應的資料庫及資料表。

步驟3:下圖是已經建立好的資料的效果圖,Project是業務資料庫、Project表是專案資訊表,後面的Base_Sequence 用於產生序列及唯一排序號、Base_Comment 用於修改記錄儲存功能,這些不用重複設計,直接從UserCenterV30資料中心裡複製過來結構就可以了,為了在軟體裡顯示一些資料出來,可以通過手工方式先錄入一些演示資料,這樣成就感會更多一些、信心也更足一些,因為很快就可以在軟體裡顯示資料出來了。

步驟4:生成好相應的資料表後,可以用程式碼生成器生成一些沒有多少商業邏輯的程式碼了,所謂的新增、刪除、修改、查詢的程式碼都不用寫了,直接用程式碼生成器生成好就可以了,雖然這個技術不怎麼先進,但是省事省心,對技術的要求也很低,生成好的程式碼也比較規範一些,而且還支援多種資料庫,還是蠻好的,若生成好後,資料庫結構有變化了,也沒關係,重新生成一下就可以了,生成程式碼1分鐘足夠了,頂多是名稱空間可能需要整理一些就可以了,屬於是很經得起折騰的做法了。

步驟5:將生成好的程式碼,複製到相應的位置上,適當的修正一下名稱空間什麼的,因為是一個很小的專案,根本不用分層啥的,分那麼多層也是多餘的,其實分目錄就足夠可以了,若很懶惰的話目錄都可以不用分了,分層其實更適合大型軟體專案,小專案以見效最快為主,沒必要搞個7-8個層,這裡是是1個層也沒分,只是分了一些目錄,顯得思路更清晰一些。

步驟6:程式碼生成器不能生成所有的程式碼,有些程式碼還是有商業邏輯的,需要人工編寫,其中ProjectManager.cs 中的程式碼就是有具體商業邏輯的程式碼,這部分程式碼無法靠程式碼生成器自動生成好,需要按自己的需要進行編寫的,這裡有2個功能,一個是查詢功能的實現、另一個是更新資料時需要有修改記錄的功能,需要詳細記錄資料被變更的全過程,這些程式碼都需要人工編寫好,頁面上會呼叫這些程式碼。

步驟7:接下來該是規劃整個系統的選單的時候,需要好好規劃一下軟體的選單,到底需要有哪些選單,都叫什麼名字?當然這一步也可以在在最後做,我們同通用許可權管理元件可以靈活規劃整個系統的選單,基本上想怎麼設定就怎麼設定,想怎麼託摘、想怎麼排序都可以,規劃選單時會很爽,當然不只是開發人員用起來爽、而且系統管理員今後維護時也會很爽,系統的整個選單都可以靈活配置許可權、可以靈活更改,而不是寫死的選單,將來不依賴開發人員也可以靈活配置管理,靈活設定各種許可權限制等等。

步驟8:接下來,需要把相應的新增、修改、查詢、刪除功能頁面做出來,可以考慮到今後可能會擴充套件很多模組進來,所有目錄規劃得合理一些,說不定還要做人事管理、考勤管理、薪資管理、工作日誌管理等等很多模組,其實都是大同小異而已,更多是技術是體現在細節功能及業務邏輯的深入程度上。

步驟9:選單配置好,頁面製作好後,就可以實現登入系統的頁面了,當然這個也是用了現成的通用許可權管理系統的登入頁面,只需要製作一個登入介面,然後呼叫相應的登入API就可以了,自然就可以達到離職人員不能登入、沒有許可權的使用者不能登入、停用賬戶不能登入,不能重複登入、限制登入的IP地址等等額外的其他功能都可以整合在裡面了,開發人員也不用關心更多的細節,省心省事了。 

步驟10:登入好後,通用許可權管理系統的底層API,自然會顯示有許可權的選單,無權訪問的選單也不會出來了,哪個使用者可以訪問哪個選單等等,都可以通過通用許可權管理工具靈活配置管理,當然那個工具是給系統管理員、開發人員、實施人員使用的,並不是為了給普通業務操作員使用定位的。軟體的最終執行效果就如下圖了,由於是系統管理登入的所有選單都能顯示了,當然這裡還需要一個成熟穩定的分頁元件。

   底層程式碼生成器生成的程式碼,可能只佔用這個系統的程式碼的 1/3左右,但是編寫這些程式碼的時間也省了,整個開發過程不是可以節省1/3了,而且是純體力勞動而已。整個許可權管理等可能需要整個系統開發的1/3左右的精力,若這部分也不用開發了,又可以節省整個軟體專案開發的1/3時間。整個框架的穩定完善,也可以說是佔用整個軟體開發專案的1/3時間是應該的,若是2手空空開始開發的話。

   那我們的專案採用了 程式碼生成器 + 資料庫訪問元件 + 通用許可權元件 + 成熟的B/S開發框架 足足可以節省一個管理軟體專案開發的 2/3 的開發時間,可以把精力全部放在另外的 1/3 的業務邏輯優化、介面細節優化上。

   這樣也大大的降低了軟體專案的開發的失敗風險,誰說你不會在前面的2/3時間上失敗呢?未必能走到後面的1/3時間上來啊,對吧。  

開發總結

  目前3天內搞定一個獨立的內部管理系統,而且五臟俱全的是我從業10年左右的最快記錄,當然若第二次做類似的東西,可能有希望2天內可以搞定了。

  經過多年的積累,手上才會有一個執行穩定、可以靈活配置的B/S開發架構,雖然看看都很簡單,但是最起碼不斷完善幾年後,才會達到銅牆鐵壁的程度,我們經常會發現,開發一個小小的軟體,往往1個月也開發不完,很可能是由於沒有穩定的B/S開發架構導致的,這個架構也不是說技術,就是一個美觀的整體效果良好的執行穩定的B/S系統吧,從頭開發的B/S架構,由於需要調整很多細節環節、特別是頁面、選單的美觀設計等上會耗費很多精力。

   有時候介面設計良好、程式思路嚴謹、使用者互動效果好的B/S空框架也能賣出幾萬元,以前不能理解,現在是徹底能體會了,穩定的B/S架構的確可以值一些錢,因為是經過精雕細刻後才能形成最後的勞動成果的。

   軟體的大體效果做到這裡,大概花費了3天時間、接下來可能要花費的時間會更多,應該不止3天了,可能是30天,因為很多業務上的細節會耗費很多時間來調整,例如新增頁面的先後輸入的內容,頁面輸入的檢查、頁面的美觀設計、游標的順序優化、回車優化,輸入內容的完善補充等等,查詢頁面的排序順序優化、查詢內容的先後順序排版、表格中各列的寬度調整,一些提醒顏色的優化等等會有很多細節問題上還要耗費很久時間的, 做軟體其實就是做細節,你需要有一個良好的B/S開發框架,否則全部自己弄,不知道什麼時候才能弄好,有個良好的框架、再有良好的例子程式,整個管理系統,就是一個量的問題了,其次就是很多細節的優化上,會花費很多時間。

    有良好的穩定的B/S開發框架,前期工作只用了3天,若沒有這個,從頭開始做,可能30天也做不完,更不會把大多精力都花費在業務邏輯的細節優化上,可能更多的精力都耗費在B/S系統架構的穩定、優化上了,那這個軟體的質量就更沒保障了。

    有了穩定的B/S開發框架後,才好進行大規模生產、大量招聘人員做開發工作,若這些都沒有,招聘來一大堆開發人員,那很可能局面就亂套了,大家就容易亂來了。

    ProjectManager.cs手工編寫的商業邏輯程式碼如下:

程式碼 //------------------------------------------------------------
// All Rights Reserved , Copyright (C) 2010 , Jirisoft , Ltd .
//------------------------------------------------------------using System;
using System.Data;
using System.Data.Common;
using System.Collections.Generic;

namespace Project
{
    
using DotNet.DbUtilities;
    
using DotNet.Manager;
    
using DotNet.Utilities;
    
using DotNet.Business;

    
///<summary>/// ProjectManager
    
/// 專案跟進表
    
////// 修改紀錄
    
//////        2010-09-28 版本:1.0 JiRiGaLa 建立主鍵。
    
////// 版本:1.0
    
//////<author>///<name>JiRiGaLa</name>///<date>2010-09-28</date>///</author>///</summary>publicpartialclass ProjectManager : BaseManager, IBaseManager
    {
        
public DataTable Search(string userId, string searchValue)
        {
            
// 一、這裡是開始進行動態SQL語句拼接,欄位名、表明都進行了常量定義,表名欄位名發生變化時,很容易就知道程式哪裡都呼叫了這些。string sqlQuery =string.Empty;
            sqlQuery 
=" SELECT * "+" FROM "+this.CurrentTableName
                    
+" WHERE "+ ProjectTable.FieldDeleteMark +" =  0 ";

            
// 二、我們認為 userId 這個查詢條件是安全,不是人為輸入的引數,所以直接進行了SQL語句拼接if (!String.IsNullOrEmpty(userId))
            {
                sqlQuery 
+=" AND "+ ProjectTable.FieldCreateUserId +" = '"+ userId +"'";
            }

            
// 三、這裡是進行引數化的準備,因為是多個不確定的查詢引數,所以用了List。            List<DbParameter> dbParameters =new List<DbParameter>();

            
// 四、這裡看查詢條件是否為空            searchValue = searchValue.Trim();
            
if (!String.IsNullOrEmpty(searchValue))
            {
                
// 五、這裡是進行支援多種資料庫的引數化查詢                sqlQuery +=" AND ("+ ProjectTable.FieldKeHuMingCheng +" LIKE "+ DbHelper.GetParameter(ProjectTable.FieldKeHuMingCheng);
                sqlQuery 
+=" OR "+ ProjectTable.FieldKeHuXiangMuMingCheng +" LIKE "+ DbHelper.GetParameter(ProjectTable.FieldKeHuXiangMuMingCheng);
                sqlQuery 
+=" OR "+ ProjectTable.FieldCreateUserRealname +" LIKE "+ DbHelper.GetParameter(ProjectTable.FieldCreateUserRealname);
                sqlQuery 
+=" OR "+ ProjectTable.FieldDescription +" LIKE "+ DbHelper.GetParameter(ProjectTable.FieldDescription) +")";

                
// 六、這裡是判斷,使用者是否已經輸入了%if (searchValue.IndexOf("%"<0)
                {
                    searchValue 
="%"+ searchValue +"%";
                }

                
// 七、這裡生成支援多資料庫的引數                dbParameters.Add(DbHelper.MakeInParam(ProjectTable.FieldKeHuMingCheng, searchValue));
                dbParameters.Add(DbHelper.MakeInParam(ProjectTable.FieldKeHuXiangMuMingCheng, searchValue));
                dbParameters.Add(DbHelper.MakeInParam(ProjectTable.FieldCreateUserRealname, searchValue));
                dbParameters.Add(DbHelper.MakeInParam(ProjectTable.FieldDescription, searchValue));
            }
            sqlQuery 
+=" ORDER BY "+ ProjectTable.FieldSortCode +" DESC ";

            
// 八、這裡是將List轉換為陣列,進行資料庫查詢return DbHelper.Fill(sqlQuery, dbParameters.ToArray());
        }
        
        
///<summary>/// 更新(帶有修改記錄功能)
        
///</summary>///<param name="projectEntity">實體</param>///<param name="changeLog">修改記錄</param>///<returns>影響行數</returns>publicint Update(ProjectEntity projectEntity, bool changeLog)
        {
            
// 若不需要修改記錄if (!changeLog)
            {
                
returnthis.UpdateEntity(projectEntity);
            }

            String changeMessage 
= String.Empty;

            
// 獲取原來的資料            ProjectEntity oldProjectEntity =this.GetEntity((int)projectEntity.Id);
            
if (oldProjectEntity.KeHuXiangMuMingCheng != projectEntity.KeHuXiangMuMingCheng)
            {
                changeMessage 
+="客戶專案名稱被修改為:"+ projectEntity.KeHuXiangMuMingCheng +" 原值:"+ oldProjectEntity.KeHuXiangMuMingCheng +"<br>";
            }
            
if (oldProjectEntity.KeHuMingCheng != projectEntity.KeHuMingCheng)
            {
                changeMessage 
+="客戶名稱被修改為:"+ projectEntity.KeHuMingCheng +" 原值:"+ oldProjectEntity.KeHuMingCheng +"<br>";
            }

            
if (oldProjectEntity.KaiGaiRiQi != projectEntity.KaiGaiRiQi)
            {
                
// changeMessage += "開改模日期被修改為:" + ((DateTime)projectEntity.KaiGaiRiQi).ToString(BaseSystemInfo.DateFormat) + " 原值:" + ((DateTime)oldProjectEntity.KaiGaiRiQi).ToString(BaseSystemInfo.DateFormat) + "<br>";            }

            
if (!String.IsNullOrEmpty(changeMessage))
            {
                BaseCommentManager commentManager 
=new BaseCommentManager(this.DbHelper, this.UserInfo);
                commentManager.Add(
"工程管理", projectEntity.Id.ToString(), projectEntity.KeHuXiangMuMingCheng, changeMessage, false, String.Empty, falsethis.UserInfo.IPAddress);
            }

            
returnthis.UpdateEntity(projectEntity);
        }
    }
}