8/人天,小記一次 JAVA(APP後臺) 項目改造 .NET 過程(後臺代碼已完整開源於 Github)

Github: https://github.com/iccb1013/Jade.Net

前端

咱們只消耗了8/人天的時間,完成了所有工做,基於咱們 Jade.Net 的開源後臺代碼,任何小規模的後臺管理系統,均可以在極短的時間內完成。git


這是咱們在 2017 年早些時候開發的一個項目,甲方是一家工藝美術品企業,須要開發一款 APP 展現產品,並引入會員(多級代理),在線下單,返點等功能。 在立項後因爲一些緣由,選擇了使用 Java 來開發後臺管理部分,面向 IOS 和 Android 版客戶端提供服務。

項目的前期調研、分析、設計工做,及推動過程這裏不做過多討論,本文主要圍繞改造工做中的技術問題進行記錄和分析。

先簡單看一下,瞭解這是怎麼樣的一個項目,終端 APP 如圖:

image.png

image.png


後臺管理端,有兩個職責:
一)向 APP 端提供功能接口,如商品接口,會員接口等;
二)純後臺管理功能,如商品管理,會員管理等;

整體而言並不複雜。除開業務邏輯以外,是很普通的管理後臺。

下面我對此次改造工做的過程進行回顧與說明。
此次改造重構的難點是要保持 APP 接口的絕對兼容,不能影響生產環境的正常運行。


首先咱們分析原 Java 版項目:

Java 版後臺代碼結構引用關係比較混亂,後臺的基本權限、菜單、字典散落分佈在各處:

程序員


原後臺使用的是 MySql 數據庫,咱們此次改造要將數據庫換爲 SQL Server ,並使用 Entity Framework 做爲咱們的數據庫訪問層。

引用關係比較混亂,沒有表結構說明書,咱們須要從新覈對整個數據庫表結構的設計:

github

 




數據表字段名與類屬性名的不合理,讓咱們的核對工做不是很樂觀:

Java工程表結構2.png
Java工程表結構3.png


使用了 mybatis 作爲數據訪問層,咱們在改造過程當中,須要逐一覈對數據庫操做,以便在改造過程當中保持百分之百的兼容:數據庫

 





下面開始咱們的改造工做:

第一步:使用 SQL Server 重建數據庫

遷移數據庫分爲兩個步驟,一是建庫,二是遷移數據。
理論上來講建庫工做能夠導出原 MySQL 的建庫腳本調整後在 SQL Server 執行來創建數據庫,可是爲了後續工做更穩妥的開展,咱們採購了人工覈對,手工創建的方式,從新梳理表結構。
咱們根據 Java 代碼和原 MySQL 數據庫,分析出了詳細的的表結構設計:

image.png

在這一過程當中,也對原表結構進行了修訂與細節調整,主要修訂如下存在的問題:
1)字段命名模糊,好比字段 modify_person ,或 person_id 。系統中存在 後臺用戶 和 客戶 兩個概念,一個用表 user 存儲,一個用表 customer 存儲,因此一些表中的 person_id 指向很模糊。
2)字段命名不統一,好比一樣是備註,有些表使用 description ,有些表則使用 remark諸如此類,咱們在重構中進行了徹底的統一。
3)去除了原系統中不使用的表和字段,從新設計部分基礎結構方面的表,去除了原開發人員複用的不知名項目相關的字段關聯關係。
4)一些單詞拼寫錯誤。

此外,優化了一些表關聯結構及業務:
1)商品與商品分類的關係,由一個商品只能屬於一個分類,優化爲容許屬於多個分類,變爲相似標籤的概念。
2)商品的圖片存儲,由關聯表的一對多存儲,優化爲在商品表經過一個字段存儲 json 數組來保存圖片URL,這一優化可使後臺相關代碼簡化許多。
3)若干字段存儲不合理的問題,多是因爲開發過程當中的需求調整,開發人員對增長的字段存儲欠考慮,存儲的位置和結構存在問題,咱們在重構中進行了修正。

表和字段的命名方式,無論合不合理,畢竟咱們是重構,不是推倒重作,因此沒有太大變化,繼承了過去的規則和方式,可是去掉了「jade_」 的表名前綴。
梳理數據庫以後,咱們爲數據庫創建了完整的外鍵關聯關係,使用 Entity Framework 生成了實體模型:

image.png


第二步:遷移數據

在改造以後,須要將原數據庫中的數據徹底遷移過來,考慮到表結構發生了必定的變化,已沒法簡單的數據導出導入,咱們專門編寫了一系列的遷移腳本在上線前完成數據遷移工做。


第三步:.NET 版本後臺框架的搭建

首先咱們確立重構所要達到的目標:

1)徹底兼容 Java 版本向 APP 端提供的 API 接口,不影響線上 APP 的正常使用。
2)針對後臺管理功能所提供的 API 接口,使用更規範的方式獨立實現,不繼承 Java 版的後臺 API 接口。
3)後臺的所有從新實現,包括所有 UI,Java 版本的 UI 有不少缺陷一直被客戶詬病,體驗不佳。
4)以最小的代價完成這次重構,計劃 2 我的,2個週末,共 8 人天的時間完成所有工做。

對以上問題,咱們在開工前進行了簡要的分析,難度並不大,可是工做量不小。接口的問題,咱們定義兩套,一套後臺使用,一套 APP 使用,給 APP 使用的接口,只需參照 Java 版代碼定義 DTO 對象,實現與數據庫實體對象的映射關係便可,後臺接口和整個後臺管理端UI部分,在過去我作的 .NET Web 項目上進行大幅簡化複用便可。

爲了便於說明,我畫了一張簡要的結構圖來講明兩套 Api :

image.png

項目的實際規模並不大,且因爲咱們力求最小成本,因此如圖所見,項目的結構也一樣簡單,本次改造工做無需追求技術體系的先進性,可以知足項目的要求便可。
左邊後臺到 Api ,再到 Core 有現成的代碼能夠複用,實現了出入參協議、鑑權等基本功能,只須要實現業務邏輯便可,右側 AppApi 部分,則須要多一層協議轉換工做,將原 Java 版本的出入參協議轉換爲 Core 要求的協議格式,而原 Java 版的 Api 接口協議並不統一,須要一個一個接口覈查復刻。 

後臺解決方案結構以下:

image.png

CCPRestSDK
    咱們使用的短信平臺的 SDK。
Jade.Core
    業務邏輯層
Jade.Model
    數據庫實體模型
Jade.Model.Dto
    用於先後臺數據傳輸的對象定義,包括針對 Model 的傳輸對象定義和其它須要傳遞的對象定義。
Sheng.Kernal
    基礎類庫,提供瞭如反射,HTTP請求,對象映射等等基礎功能。在複用到本項目時通過了簡化。
Sheng.Web.Infrastructure
    用於 Web 項目的基礎類庫,提供了通用 Api 協議定義,控制器、DTO等共通的基礎功能,在複用到本項目時通過了簡化。
    這裏包括一個專門爲此項目寫的友盟推送實現,友盟官方沒有提供 C# 版 SDK。


考慮到這個項目比較簡單,我在這裏再也不對基本技術體系作太多贅述,而是經過兩個簡單的請求過程進行闡述,代碼已經開源在了 Github 上,能夠下載代碼後根據下文進行查看。

Github https://github.com/iccb1013/Jade.Net


Api 的請求過程


image.png

在 Areas 下提供了 Api 和 AppApi 兩個區域提供接口,咱們以 Api 下的 ProductController 爲例,它向後臺 UI 提供產品相關的接口:

image.png

以 UpdateProduct 接口進行說明,此接口用於更新產品,此接口接收前端傳入的商品信息,並更新數據庫中的商品信息:

image.png

此接口的 RequestArgs 方法是接口 Controller 的基礎 ApiBaseController 所提供的,用於把前端 Post  過來的內容反序列化成指定的對象。

Product_Info product = Mapper.Map<Product_Info>(args); 

做用是將 Dto 對象映射爲數據庫實體對象,這裏咱們使用了開源組件 AutoMapper。

有關 AutoMapper  能夠訪問:http://automapper.org/

引用 AutoMapper 組件後,只需定義不一樣對象間的映射規則便可,可適用於絕大多數狀況,Product_Info 的映射規則以下:

image.png

接口在完成對象映射後,調用 Core 中的方法來實現業務:

image.png

基本的 Entity Framework 操做,不做贅述。

這裏有一個細節,是圖中標出的  ShengMapper.SetValuesWithoutProperties 部分,這個方法把 傳入的 product 中數據拷貝到從數據庫取出來的 dbProduct 中,可是跳過2個指定的屬性和全部的虛屬性。
AutoMapper 不能定義同一個對象類型的映射規則,也不能靈活的在不一樣場景使用不一樣的規則,因此我寫了 ShengMapper 用於處理這種狀況。

ShengMapper 也是開源的:Github https://github.com/iccb1013/Sheng.Mapper

此外能夠留意到方法的返回對象是 NormalResult,這是一個 Core 層使用的通常返回對象:

image.png

至關於一個邏輯上不須要返回值的方法,但咱們須要知道它的執行狀態,如:

image.png

有一些開發人員愛用 Exception 來返回業務結果,這樣作是很是不合適的,好比這裏的 商品編碼重複,他是一個業務操做的結果,而且這個結果是在咱們的預期以內的,不是一個 程序異常。
用異常來返回業務操做結果有兩個很是大的弊端,一是拋異常時很是影響性能,二是要區別對待真的程序異常和業務結果,也是十分麻煩的事情。總之,沒有理由這麼作。

NormalResult 還提供了一個重載,能夠返回指定類型的對象結果:

image.png

當 Core 層完成業務操做時,Controller 層的 API 會經過一個 ApiResult 對象來封裝 Api 接口的返回結果:

image.png

此處的 return ApiResult() 方法,是 Sheng.Web.Infrastructure 中的 BaseController 所提供的,能夠處理大多數 Api 返回結果:

 image.png

若是都不能知足,也能夠手工 new 一個 ApiResult 返回:

image.png

Hint 只在部分特殊狀況下使用,並不會爲每種操做結果安排一個錯誤碼,對於咱們的項目來講,多傳一些字節回去沒有問題,但有些特殊場景,前端須要知道具體的狀況針對性處理,這時咱們才使用錯誤碼。


Sheng.Web.Infrastructure 還提供了對於請求分頁列表數據的通用協議:

image.png

image.png
image.png

GetListDataArgs 對象使用一個 ParametersContainer 來存儲查詢條件,它實際上是一個鍵值對。避免爲每個查詢定義強類型的查詢入參對象,實在是太過麻煩了,也沒有必要。

咱們經過這樣的方式來處理查詢條件便可:

image.png


下面咱們再看一個 AppApi 的說明,咱們還以產品信息爲例,它的 Api 定義:

image.png

在 AppApi 中,爲了兼容既有的接口約定,作了許多轉換工做:

image.png

把原 APP 接口的查詢條件,轉換爲上文提到的 ParametersContainer:

image.png

這裏把 APP 接口的列表查詢入參,轉換爲咱們過去定義好的 GetListDataArgs:

image.png

這裏作一些字段轉換時的特殊標記:

image.png


此外,原 Java 版在向 APP 提供接口時,提供給 APP 的 DTO 對象,十分詭異的和數據庫模型不一致,好比數據庫字段名有下劃線,可是 DTO 傳輸模型沒有,還有一些字段的命名則是徹底不同,咱們利用 AutoMapper 來逐一映射:

image.png

至此,項目的結構已經徹底清楚,剩下的所有是業務邏輯層的業務操做。 

重構工做的完成的效果:

新後臺列表.jpg

新後臺編輯.jpg


前端 UI 的實現方法:

前端 UI 及腳本庫複用了我以前寫過的 Web 項目, Asp.net MVC,結合使用了先後端分離和 Razor 兩種方式。

Razor 引擎具備極高的開發效率,在作頁面數據展現時很是的方便,藉助 Razor 和 Asp.net MVC 的佈局頁和分佈頁技術,能夠快速而有效的搭建頁面框架。

以下圖,定義了一個用於通常列表頁面的佈局頁(模版):

image.png

能夠輕鬆的看出頁面定義了大致結構:標題,副標題,按鈕,查詢區,表格容器和分頁容器。表格容器和分頁容器並無使用 Razor,而是在具體視圖頁經過通常先後端分離的方式用腳本進行處理。

這是一個通常列表頁視圖實現的例子,基於上面的佈局頁,代碼量就只有不到100行,就實現了一個普通列表頁面。

image.png

只須要初始化table,主要是定義這個頁面中表格所具有的列,查詢參數便可,另外在定義一個查詢條件區,這個列表頁面就完成了。全部共通的功能都寫在了佈局頁和共享的腳本文件中。

編輯和查看頁面也使用了一樣的處理方式,再也不贅述。 

基於開發效率和實際項目須要考慮,咱們這裏沒有使用重量級的前端開發框架,而是複用了過去我寫的 js 腳本,這些腳本基於 jQuery 完成一些共通的功能來提升開發效率,如處理數據加載綁定,發起 Api 請求等操做。

common.js:

image.png

這裏的 __getDto 和 _setDto 方法,搭配頁面 HTML 的特殊標記,能夠實現前端對象的自動生成和綁定:

image.png

image.png

在 HTML 標籤中用 dtoproperty 屬性標記出 DTO 對象的屬性名後,使用 __getDto 方法便可自動生成前端對象。

image.png

使用 __setDto 方法,則能夠快速把 Api 返回的對象,加載到前端控件中。

如上圖所示,前端畫面簡單的保存,加載數據就完成了。


listViewCommon2.js,這是用於通常表頁的腳本,基於這裏的共通腳本,加上Razor 引擎的佈局頁功能,實現了不到 100 行 HTML 和 JS 代碼便可完成一個列表頁面,固然,若是追求技術上的更加完美,能夠繼續抽象,繼續封裝達到更好的效果:

image.png


editViewCommon.js ,這是用於通常編輯頁面的腳本:

image.png


你能夠從 Github 上下載代碼以後在 Jade.Shell 的 Scripts 目前下查閱這些腳本。

整個項目從純技術角度來講還有許多提升改進的空間,但咱們如今是作項目,不是作研究作框架產品,咱們能夠在將來的項目中經過項目推動的方式一步一步的提煉和完善咱們的開發模式和技術體系。

最終,2我的,2個週末,在大量複用過去代碼的基礎上,只消耗了8人天完成了本次改造工做。

Github https://github.com/iccb1013/Jade.Net


本次工做以後的一點心得體會,主要是幾個失誤的地方:

1)前期將項目交給過去的同事來作,沒有過多關注,致使了項目上線前遭遇許多問題,卻得不到妥善解決,我我的秉承誠以待人的原則,用人不疑,疑人不用,這一點我想沒有錯,錯的是我徹底放手沒有投入精力把控工程,除了報銷吃喝費用外基本不參與,這是一個教訓,不管何種狀況,應該必定程度的參與並把控好各項主要工做。

2)立項時預估後臺工做量只需1我的月,爲了分擔人員風險,我仍是多付了一我的的費用安排2我的來作,但在人員使用上,沒有作到風險規避。

3)過早的結清了人員費用,致使工做沒法順利推動,對於外包項目,費用結算必定要有計劃性,包括預留尾款。

最後我想談一談技術人員的「人設」問題,這是我今後項目上深入認識到的一個問題。

我作了超過十年的技術研發工做,但同時許多朋友說我不像一個程序員,我並不認爲程序員必定要雙肩包,開口只談技術,相反隨着年齡和閱歷的增加,我一天比一天認識到業務、行業的重要性,我很早就知道作技術是爲了什麼,作技術不是爲了作技術,而是爲了服務咱們的客戶,服務社會,服務一切須要的人,我更關心的是我要作什麼事情,個人目標是什麼。一直以來我認爲這是一個技術人員轉變和提升的核心觀念。

可是最近一到兩年,我慢慢意識到,有時咱們須要讓本身契合對方心理上的某種「人設」,就這個玉雕工做室的項目來講,我和客戶談需求和業務比較多,吃飯喝酒比較多,加上不那麼技術的形象(長頭髮,扎辮子),致使客戶始終不認爲我是一個技術人員,固然我也不太在乎這一點,可是,後來我意識到一些問題。

對於這種小型外包項目,特別是尚未接下來以前,客戶最在乎的仍是咱們有沒有技術實力作好,能不能給咱們作,這裏有一個角色帶入的問題,這時我不是供職於大公司的項目經理服務於既有客戶,扎辮子花衣服作需求也不是不能夠,可是當時我是一個要接項目的人,在互相不瞭解的狀況下,如何快速創建信任?最簡單的辦法是讓本身契合對方心理上的「人設」:我就是你要找的人。

許多客戶包括企業領導層,對技術人員都有本身所理解的「人設」,客戶不懂技術,領導也許也不那麼懂技術,他們要找有技術實力的人,怎麼辦呢,說白了,憑感受。

玉雕這個項目直到原來 Java 版後臺的兩個開發人員撂挑子,客戶都感嘆他們技術好,客戶是作工藝品的,和IT技術八杆子打不着邊,爲何?這件事讓我反思了好久,就是「人設」。

因而我剪了頭髮,換上襯衫,買一個瑞士軍刀電腦包(笑)。以便於我在不一樣的時候有不一樣的人設。

固然更重要的是對於技術和業務的種種理念,在和不一樣的人表達的時候,要特別注意表達的方式和技巧,以及表達到什麼度。對於水平比本身高的人,能夠隨意表達本身的想法,不要怕,可是遇到經驗或能力不如本身的人時,要特別當心,由於對方可能沒法體悟你的意思。

好比和一樣一個作過十年項目的老鳥說一句:技術不是那麼重要,也許雙方能夠會心一笑,重要的是彼此知道咱們所說的技術不重要的點,技術不重要的度在哪裏。可是若是和一個沒有太多各類項目經驗的人說這樣的話,多是不合適的,對方會不能理解,進行主觀的判斷你不行,由於你不技術。

惟一的辦法是不要太個性,要契合別人的人設,無論他有沒有道理。"木秀於林風必摧之",瑞士軍刀電腦包該背仍是要背(笑)。

json


本文聯合做者:
後端

曹旭升
QQ:279060597
Email:cao.silhouette@msn.com
http://blog.shengxunwei.com

範大宏
QQ:237194340
Email:whoarefan@gmail.com數組

 

歡迎朋友們加入咱們的微信羣: 微信

 

相關文章
相關標籤/搜索