• 當前位置:首頁 > IT技術 > Windows編程 > 正文

    C#開發BIMFACE系列42 服務端API之圖紙對比
    2021-10-18 17:40:52


    BIMFACE二次開發系列目錄???? ?【已更新最新開發文章,點擊查 看詳細】


    ?


    在我的前一篇博客??《??C#開發BIMFACE系列41 服務端API之模型對比??》??中詳細介紹了BIMFACE服務端接口模型對比的功能。 BIMFACE官方文檔提供的三維模型對比接口同樣也適用于二維CAD圖紙對比。下圖中是官方提供的對比示例程序。

    C#開發BIMFACE系列42 服務端API之圖紙對比_c#開發

    其中新增的圖元使用綠色標記、修改的圖元使用黃色標記、刪除的圖元使用紅色標記。

    下面介紹BIMFACE圖紙對比功能的原理與實現。

    圖紙對比可以對兩個圖紙文件進行差異性分析,確定兩個圖紙文件之間構件的幾何和屬性差異,包括增加的圖元構件、刪除的圖元和修改的圖元。

    特別說明:圖紙對比是在BIMFACE云端進行的,通常需要5~10分鐘。當模型對比完成后,BIMFACE能通知對比結果。


    前置條件



    • 您需要將修改前和修改后的圖紙上傳到云端并轉換成功以后才能發起圖紙對比;
    • 目前支持.dwg、.dwf單文件的圖紙對比。


    基本步驟



    1. 通過服務端API發起圖紙對比(對比前后模型文件的fileId);
    2. 等待云端對比任務執行;
    3. 對比完成后,在網頁端通過調用JavaScript API實現差異圖紙的顯示;
    4. 除了顯示差異圖紙,還需要調用服務端API獲取對比結果(包括新增、刪除、修改的圖元列表)。


    對比流程


      圖紙文件經過云端轉換后,生成了BIMFACE定義的數據包。因此,要對比兩個圖紙文件,實際上需要對比兩個文件的數據包。如下圖所示,文件B是文件A修改后的版本,對比完成之后,其結果包括兩個部分:


    • 幾何差異;
    • 變更構件及屬性。

    ?C#開發BIMFACE系列42 服務端API之圖紙對比_json_02

    BIMFACE提供了服務端API,用于發起對比,獲取對比狀態、獲取對比結果。請參考我的博客:


    ??C#開發BIMFACE系列30 服務端API之模型對比1:發起模型對比??


    ??C#開發BIMFACE系列31 服務端API之模型對比2:獲取模型對比狀態??


    ??C#開發BIMFACE系列32 服務端API之模型對比3:批量獲取模型對比狀態??


    ??C#開發BIMFACE系列33 服務端API之模型對比4:獲取模型對比結果??


    ??C#開發BIMFACE系列34 服務端API之模型對比5:獲取模型構建對比差異??


    測試程序


    C#開發BIMFACE系列42 服務端API之圖紙對比_json_03

    發起圖紙對比

    C#開發BIMFACE系列42 服務端API之圖紙對比_BIM  BIMFACE_04

    調用服務器端的API獲取對比結果

    C#開發BIMFACE系列42 服務端API之圖紙對比_服務端_05

    對比差異分為三類:新增、修改、刪除。由于CAD圖紙的展示類型包含 Model 與 Layer 兩種形式,

    C#開發BIMFACE系列42 服務端API之圖紙對比_BIM  BIMFACE_06

    C#開發BIMFACE系列42 服務端API之圖紙對比_BIM  BIMFACE_07

    差異結果中也是包含兩種展示類型的對比信息,所以可能有重復的圖元ID,需要手動過濾。

    返回結果對應的實體類如下



    1 /// <summary>
    2 /// 模型對比差異類
    3 /// </summary>
    4 public class ModelCompareDiff
    5 {
    6 /// <summary>
    7 /// 對比差異構件所屬類別ID。樣例 : "-2001320"
    8 /// </summary>
    9 [JsonProperty("categoryId", NullValueHandling = NullValueHandling.Ignore)]
    10 public string CategoryId { get; set; }
    11
    12 /// <summary>
    13 /// 對比差異構件所屬類別名稱。樣例 : "framework"
    14 /// </summary>
    15 [JsonProperty("categoryName", NullValueHandling = NullValueHandling.Ignore)]
    16 public string CategoryName { get; set; }
    17
    18 /// <summary>
    19 /// 對比構件差異類型:NEW、DELETE、CHANGE
    20 /// </summary>
    21 [JsonProperty("diffType", NullValueHandling = NullValueHandling.Ignore)]
    22 public string DiffType { get; set; }
    23
    24 /// <summary>
    25 /// 對比差異構件ID。樣例 : "296524"
    26 /// </summary>
    27 [JsonProperty("elementId", NullValueHandling = NullValueHandling.Ignore)]
    28 public string ElementId { get; set; }
    29
    30 /// <summary>
    31 /// 對比差異構件名稱
    32 /// </summary>
    33 [JsonProperty("elementName", NullValueHandling = NullValueHandling.Ignore)]
    34 public string ElementName { get; set; }
    35
    36 /// <summary>
    37 /// 對比差異構件的族名稱。樣例 : "framework 1"
    38 /// </summary>
    39 [JsonProperty("family", NullValueHandling = NullValueHandling.Ignore)]
    40 public string Family { get; set; }
    41
    42 /// <summary>
    43 /// 對比差異構件來源構件ID。樣例 : "0213154515478"
    44 /// </summary>
    45 [JsonProperty("id", NullValueHandling = NullValueHandling.Ignore)]
    46 public string Id { get; set; }
    47
    48 /// <summary>
    49 /// 對比差異構件變更文件ID,即(當前)變更后的文件ID。樣例 : "1136893002033344"
    50 /// </summary>
    51 [JsonProperty("followingFileId", NullValueHandling = NullValueHandling.Ignore)]
    52 public string FollowingFileId { get; set; }
    53
    54 /// <summary>
    55 /// 對比差異構件來源文件ID,即 (歷史)變更前的文件ID。樣例 : "0213154515478"
    56 /// </summary>
    57 [JsonProperty("previousFileId", NullValueHandling = NullValueHandling.Ignore)]
    58 public string PreviousFileId { get; set; }
    59
    60 /// <summary>
    61 /// 對比差異構件所屬專業。樣例 : "civil"
    62 /// </summary>
    63 [JsonProperty("specialty", NullValueHandling = NullValueHandling.Ignore)]
    64 public string Specialty { get; set; }
    65 }


    對比結果如下



    1 {
    2 "code": "success",
    3 "message": null,
    4 "data": {
    5 "data": [{
    6 "diffType": "NEW",
    7 "id": "1946876",
    8 "layer": "D1",
    9 "sheetId": "0",
    10 "sheetName": "Model",
    11 "type": "Model"
    12 }, {
    13 "diffType": "NEW",
    14 "id": "1946877",
    15 "layer": "D1",
    16 "sheetId": "0",
    17 "sheetName": "Model",
    18 "type": "Model"
    19 }, {
    20 "diffType": "NEW",
    21 "id": "1946878",
    22 "layer": "D1",
    23 "sheetId": "0",
    24 "sheetName": "Model",
    25 "type": "Model"
    26 }, {
    27 "diffType": "CHANGE",
    28 "id": "40539",
    29 "layer": "0",
    30 "sheetId": "0",
    31 "sheetName": "Model",
    32 "type": "Model"
    33 }, {
    34 "diffType": "CHANGE",
    35 "id": "40541",
    36 "layer": "0",
    37 "sheetId": "0",
    38 "sheetName": "Model",
    39 "type": "Model"
    40 }, {
    41 "diffType": "CHANGE",
    42 "id": "40542",
    43 "layer": "0",
    44 "sheetId": "0",
    45 "sheetName": "Model",
    46 "type": "Model"
    47 }, {
    48 "diffType": "CHANGE",
    49 "id": "22243",
    50 "layer": "AXIS",
    51 "sheetId": "0",
    52 "sheetName": "Model",
    53 "type": "Model"
    54 }
    55 ],
    56 "page": 1,
    57 "total": 7
    58 }
    59 }


    網頁中使用JS來實現圖紙展示與差異對比效果,以及點擊異動圖元后自動定位到構件所在的視角。官網示例請參考?https://bimface.com/developer-jsdemo#988

    官網的對比展示效果是將2張圖紙進行疊加對比顯示的,下面介紹另一種對比展示方式,2張圖紙分別展示,左側展示當前版本圖紙,右側展示歷史版本圖紙。

    C#開發BIMFACE系列42 服務端API之圖紙對比_c#開發_08

    點擊新增圖元項,自動定位(綠色標記)

    C#開發BIMFACE系列42 服務端API之圖紙對比_服務端_09

    點擊修改圖元項,自動定位(黃色標記)

    C#開發BIMFACE系列42 服務端API之圖紙對比_c#開發_10

    點擊刪除圖元項,自動定位(紅色標記)

    C#開發BIMFACE系列42 服務端API之圖紙對比_c#開發_11

    ?

    布局如下



    <body>
    <div class="nav"><a class="lg"><b>xxxx圖紙.dwg</b></a></div>
    <div id="container">
    <div class='latest'>
    <!--<div class='title'>
    <span>當前輪次(<b>當前版本</b>)</span>
    </div>-->
    </div>

    <div class='prev'>
    <!--<div class='title'>
    <span>上一輪次(<b>歷史版本</b>)</span>
    </div>-->
    </div>

    <div class="list">
    <h3>差異列表(<span>0</span>)</h3>
    <div class="detail">
    <ul class="bf-collapse add">
    <span class="bf-icon"></span>
    <span>新增圖元(<b>0</b>)</span>
    <div class="items"></div>
    </ul>

    <ul class="bf-collapse edit">
    <span class="bf-icon"></span>
    <span>修改圖元(<b>0</b>)</span>
    <div class="items"></div>
    </ul>

    <ul class="bf-collapse deletes">
    <span class="bf-icon"></span>
    <span>刪除圖元(<b>0</b>)</span>
    <div class="items"></div>
    </ul>
    </div>
    </div>
    </div>
    </body>


    腳本實現圖紙加載展示



    1  $(document).ready(function () {
    2 document.querySelector('.nav .lg b').innerHTML = sclc_desc + "【" + tzFileName1 + "】" + " 對比 【" + tzFileName2 + "】";
    3
    4 var success = getViewTokens(compareId);
    5 if (!success) {
    6 return;
    7 }
    8
    9 prev = previousFileViewToken;
    10 latest = followingFileViewToken;
    11 compare = compareViewToken;
    12
    13 var bimfaceLoaderConfig = new BimfaceSDKLoaderConfig();
    14 bimfaceLoaderConfig.viewToken = latest;
    15 BimfaceSDKLoader.load(bimfaceLoaderConfig, onSDKLoadSucceeded, onSDKLoadFailed);
    16 });
    17
    18 function onSDKLoadSucceeded(viewMetaData) {
    19 if (viewMetaData.viewType == "drawingView") {
    20 // 加載修改后圖紙
    21 var webAppConfig = new Glodon.Bimface.Application.WebApplicationDrawingConfig();
    22 webAppConfig.domElement = document.querySelector('.latest');
    23 latest = new Glodon.Bimface.Application.WebApplicationDrawing(webAppConfig);
    24 latest.load(viewMetaData.viewToken);
    25
    26 // 加載修改前圖紙
    27 latest.getViewer().getViewMetaData(prev,
    28 function (viewMetaData) {
    29 var webAppConfig = new Glodon.Bimface.Application.WebApplicationDrawingConfig();
    30 webAppConfig.domElement = document.querySelector('.prev');
    31 prev = new Glodon.Bimface.Viewer.ViewerDrawing(webAppConfig);
    32 prev.load(viewMetaData.viewToken);
    33 prev.addEventListener('Loaded', correspond);
    34 });
    35
    36 $.ajax({
    37 url: "Handlers/GetBIMCompareResultFromDBHandler.ashx",
    38 data: { compareId: compareId, modelType: '2D' },
    39 dataType: "json",
    40 type: "GET",
    41 async: false, //同步。函數有返回值,必修設置為同步執行
    42 success: function (data) {
    43 if (data.code == true) {
    44 var add = '', edit = '', deletes = '';
    45 if (data.news) {
    46 data.news.map((item, i) => {
    47 add += `<li class='add-item'>${item.elementId}</li>`;
    48 });
    49 document.querySelector('.add .items').innerHTML = add;
    50 document.querySelector('.add b').innerHTML = data.news.length;
    51 }
    52 if (data.changes) {
    53 data.changes.map((item, i) => {
    54 edit += `<li class='modify-item'>${item.elementId}</li>`;
    55 });
    56 document.querySelector('.edit .items').innerHTML = edit;
    57 document.querySelector('.edit b').innerHTML = data.changes.length;
    58 }
    59 if (data.deletes) {
    60 data.deletes.map((item, i) => {
    61 deletes += `<li class='delete-item'>${item.elementId}</li>`;
    62 });
    63 document.querySelector('.deletes .items').innerHTML = deletes;
    64 document.querySelector('.deletes b').innerHTML = data.deletes.length;
    65 }
    66 document.querySelector('.list h3 span').innerHTML =
    67 (data.deletes ? data.deletes.length * 1 : 0) +
    68 (data.changes ? data.changes.length * 1 : 0) +
    69 (data.news ? data.news.length * 1 : 0);
    70 } else {
    71 $.messager.alert('提示', data.message, 'warning');
    72 }
    73 },
    74 error: function (e) {
    75 $.messager.alert('提示', e, 'error');
    76 }
    77 });
    78 } else {
    79 $.messager.alert('提示', '對比的文件不是二維圖紙。', 'warning');
    80 }
    81 };
    82
    83 function onSDKLoadFailed(error) {
    84 alert("圖紙加載失敗。");
    85 };


    腳本實現差異項點擊事件



    1 // 同步新舊圖紙的平移和旋轉操作
    2 function correspond() {
    3 prevViewer = prev.getViewer();
    4 latestViewer = latest.getViewer();
    5 var state;
    6 bindEvent();
    7 (latestViewer.getViewer()).onViewChanges = function () {
    8 if (latestViewer.getCurrentState() == state) {
    9 return;
    10 }
    11 state = latestViewer.getCurrentState();
    12 prev.setState(state);
    13 }
    14
    15 setTimeout(function () {
    16 prevViewer.onViewChanges = function () {
    17 if (prev.getCurrentState() == state) {
    18 return;
    19 }
    20 state = prev.getCurrentState();
    21 latestViewer.getViewer().setState(state);
    22 }
    23 },
    24 10);
    25
    26 // 同步新舊圖紙的HOVER事件和CLICK事件
    27 //let ViewerEvent = Glodon.Bimface.Viewer.ViewerDrawingEvent;
    28 var ViewerEvent = Glodon.Bimface.Viewer.ViewerDrawingEvent;
    29
    30 latestViewer.addEventListener(ViewerEvent.ComponentsSelectionChanged,
    31 function (data) {
    32 prev.clearSelection();
    33 prev.selectByIds(data);
    34 });
    35
    36 prev.addEventListener(ViewerEvent.ComponentsSelectionChanged,
    37 function (data) {
    38 latestViewer.getViewer().clearSelection();
    39 latestViewer.selectByIds(data);
    40 });
    41
    42 latestViewer.addEventListener(ViewerEvent.Hover,
    43 function (data) {
    44 prev.clearHighlight();
    45 data.objectId && prev.highlightById(data.objectId);
    46 console.log(data.objectId);
    47 });
    48
    49 prev.addEventListener(ViewerEvent.Hover,
    50 function (data) {
    51 latestViewer.getViewer().clearHighlight();
    52 data.objectId && latestViewer.getViewer().highlightById(data.objectId);
    53 });
    54 }
    55
    56 function bindEvent() {
    57 var red = new Glodon.Web.Graphics.Color("#FF0000", 0.8);
    58 var yellow = new Glodon.Web.Graphics.Color("#FFF68F", 0.8);
    59 var blue = new Glodon.Web.Graphics.Color("#32CD99", 0.8);
    60 // 設置差異列表的交互
    61 // 獲取文檔中 class="detail" 的第一個元素: 差異列表內容的div
    62 var dom = document.querySelector('.detail');
    63
    64 // 差異列表的點擊事件
    65 // e 為MouseEvent事件,其target為點擊到的html元素
    66 dom.addEventListener('click',
    67 function (e) {
    68 console.log(e);
    69 var target = e.target;
    70 tagName = target.tagName;
    71 // 通過點擊對象的種類,決定交互
    72 if (tagName == 'SPAN') {
    73 // 如果是span,則展開/收起列表
    74 target.parentElement.toggleClass('bf-collapse');
    75 } else if (tagName == 'LI') {
    76 // 如果是li,則繪制矩形框
    77 // 獲取點擊的數值,對應圖元的id
    78 var id = target.innerText;
    79
    80 // 清除上一步的選中效果和boundingBox
    81 latest.getViewer().clearSelection();
    82 latest.getViewer().clearElementBox();
    83 prev.clearElementBox();
    84 prev.clearSelection();
    85
    86 switch (target.className) {
    87 // 新增圖元
    88 case "add-item":
    89 // 設置矩形框的樣式-藍色&云線
    90 prev.setElementBoxColor(blue);
    91 prev.setElementBoxStyle("CloudRect");
    92 latest.getViewer().setElementBoxColor(blue);
    93 latest.getViewer().setElementBoxStyle("CloudRect");
    94
    95 // 定位
    96 latest.getViewer().zoomToObject(id);
    97
    98 // 繪制矩形框
    99 var BBox = latest.getViewer().getObjectBoundingBox(parseInt(id));
    100 prev.showElementBoxByBBox(BBox, 1);
    101 console.log(BBox);
    102 latest.getViewer().showElementBoxByBBox(BBox, 1);
    103 break;
    104
    105 // 被修改圖元
    106 case "modify-item":
    107 // 設置矩形框的樣式-黃色&云線
    108 prev.setElementBoxColor(yellow);
    109 prev.setElementBoxStyle("CloudRect");
    110 latest.getViewer().setElementBoxColor(yellow);
    111 latest.getViewer().setElementBoxStyle("CloudRect");
    112
    113 // 定位
    114 prev.zoomToObject(id);
    115
    116 // 繪制矩形框
    117 var BBox = prev.getViewer().getObjectBoundingBox(parseInt(id));
    118 prev.showElementBoxByBBox(BBox, 1);
    119 latest.getViewer().showElementBoxByBBox(BBox, 1);
    120 break;
    121
    122 // 被刪除圖元
    123 case "delete-item":
    124 // 設置矩形框的樣式-紅色&云線
    125 prev.setElementBoxColor(red);
    126 prev.setElementBoxStyle("CloudRect");
    127 latest.getViewer().setElementBoxColor(red);
    128 latest.getViewer().setElementBoxStyle("CloudRect");
    129
    130 // 定位
    131 prev.zoomToObject(id);
    132
    133 // 繪制矩形框
    134 var BBox = prev.getViewer().getObjectBoundingBox(parseInt(id));
    135 prev.showElementBoxByBBox(BBox, 1);
    136 latest.getViewer().showElementBoxByBBox(BBox, 1);
    137 }
    138 }
    139 });
    140
    141 // 設置layout切換同步
    142 var layout = document.querySelector('.bf-family .bf-sub-toolbar');
    143 layout.addEventListener('click',
    144 function (e) {
    145 var target = e.target, tagName = target.tagName, name, views;
    146 if (tagName == 'SPAN') {
    147 name = target.innerText;
    148 } else if (tagName == 'DIV') {
    149 name = target.getAttribute('title');
    150 }
    151 views = prev.getViews();
    152 views.map((item, i) => {
    153 if (item.name == name) {
    154 prev.showViewById(item.id);
    155 }
    156 });
    157 });
    158
    159 // 顯示效果同步
    160 var state = { showLineWidth: true, mode: '普通模式', layout: 'model' }
    161 setInterval(() => {
    162 var lineWidth = latest.getViewer().getViewer().viewer.ShowLineWidth;
    163 var container = document.querySelectorAll('.bf-drawing-container ');
    164 if (lineWidth != state.showLineWidth) {
    165 state.showLineWidth = !state.showLineWidth;
    166 prev.showLineWidth(state.showLineWidth);
    167 }
    168 if (document.querySelector('input[mode=普通模式]') &&
    169 document.querySelector('input[mode=普通模式]').checked &&
    170 (state.mode != '普通模式')) {
    171 state.mode = '普通模式';
    172
    173 prev.setPrintMode('Normal');
    174 container[1].style.background = 'rgb(50,50,55)';
    175
    176 } else if (document.querySelector('input[mode=白底模式]') &&
    177 document.querySelector('input[mode=白底模式]').checked &&
    178 (state.mode != '白底模式')) {
    179 state.mode = '白底模式';
    180 prev.setPrintMode('White');
    181 container[1].style.background = 'rgb(255,255,255)';
    182 } else if (document.querySelector('input[mode=黑白模式]') &&
    183 document.querySelector('input[mode=黑白模式]').checked &&
    184 (state.mode != '黑白模式')) {
    185 state.mode = '黑白模式';
    186 prev.setPrintMode('Black');
    187 container[1].style.background = 'rgb(255,255,255)';
    188 }
    189 },
    190 1000);
    191
    192 // 圖層列表顯示同步
    193 var watch = function () {
    194 var layers = document.querySelector('.layers-panel');
    195 if (layers) {
    196 layers.addEventListener('click',
    197 function (e) {
    198 var data = latest.getViewer().getViewer().getLayers(),
    199 obj = {},
    200 arr = [],
    201 prevState = prev.getLayers();
    202 data.map(function (item, index) {
    203 obj[item.id] = item;
    204 });
    205 prevState.map(function (item, index) {
    206 if (obj[item.id]) {
    207 arr.push(obj[item.id]);
    208 } else {
    209 arr.push(item);
    210 }
    211 });
    212 prev.getViewer().changeLayers(arr);
    213 prev.getViewer().update();
    214 });
    215 } else {
    216 setTimeout(watch, 1000);
    217 }
    218 }
    219 watch();
    220 }


    問題思考 ???


    官方提供的示例中,對比的2個.dwg文件中,每個文件中僅包含一張圖紙,即一個圖框。在常規業務場景下,一個.dwg文件中包含多個圖框,如下圖

    C#開發BIMFACE系列42 服務端API之圖紙對比_json_12

    當前版本與歷史版本對比完成后,通過上述測試程序,在Web網頁中點擊差異項可以自動定位到圖元變化所在位置。是否可以知道差異項來自哪個圖框呢?

    答案是肯定的,實現方案參考下面兩篇博客《C#開發BIMFACE系列43 服務端API之圖紙拆分》、《C#開發BIMFACE系列44 服務端API之計算圖紙對比差異項來源自哪個圖框》。

    ?

    上述測試程序使用了 《BIMFace.SDK.CSharp》開源SDK。歡迎大家下載使用。

    ?


    BIMFACE二次開發系列目錄???? ?【已更新最新開發文章,點擊查看詳細】



    ?


    ?C#開發BIMFACE系列42 服務端API之圖紙對比_html_13技術棧

    ? ?

    ?1、Visual Studio、.C#/.NET、.NET Core、MVC、Web API、RESTful API、gRPC、SignalR、Python

    ?2、jQuery、Vue.js、Bootstrap

    ?3、數據庫:SQLServer、MySQL、PostgreSQL、Oracle、SQLite、Redis、MongoDB、ElasticSearch、TiDB、達夢DM、人大金倉、 神通、南大通用 GBase、華為 GaussDB 、騰訊 TDSQL 、阿里 PolarDB、螞蟻金服 OceanBase、東軟 OpenBASE、浪潮云溪數據庫 ZNBase

    ?4、ORM:Dapper、Entity Framework、FreeSql、SqlSugar、分庫分表、讀寫分離

    ?5、架構:領域驅動設計 DDD、ABP

    ?6、環境:跨平臺、Windows、Linux(CentOS、麒麟、統信UOS、深度Linux)、maxOS、IIS、Nginx、Apach

    ?7、移動App:Android、IOS、HarmonyOS、微信、小程序、快應用、Xamarin、uni-app、MUI、Flutter、Framework7、Cordova、Ionic、React Native、Taro、NutUI、Smobiler

    ? ?

    ?云原生、微服務、Docker、CI/CD、DevOps、K8S;

    ?Dapr、RabbitMQ、Kafka、分布式、大數據、高并發、負載均衡、中間件、RPC、ELK;

    ?.NET + Docker + jenkins + Github + Harbor + K8S;


    ?

    作者:張傳寧 ??微軟MCP、系統架構設計師、系統集成項目管理工程師、科技部創新工程師。

    ??????????專注于微軟.NET技術(.NET Core、Web、MVC、WinForm、WPF)、通用權限管理系統、工作流引擎、自動化項目(代碼)生成器、SOA 、DDD、 云原生(Docker、微服務、DevOps、CI/CD);PDF、CAD、BIM 審圖等研究與應用。

    ??????????多次參與電子政務、圖書教育、生產制造等企業級大型項目研發與管理工作。

    ??????????熟悉中小企業軟件開發過程:需求分析、架構設計、編碼測試、實施部署、項目管理。通過技術與管理幫助中小企業快速化實現互聯網技術全流程解決方案。

    ?????????



    ? ?

    本文摘自 :https://blog.51cto.com/u

    開通會員,享受整站包年服務
    国产呦精品一区二区三区网站|久久www免费人咸|精品无码人妻一区二区|久99久热只有精品国产15|中文字幕亚洲无线码