• XXX项目总结


    前言

    昨天晚上睡不着总感觉有些什么东西需要记下来,所以就有了这篇文章。做了好几次的重构是应该有一个总结以备以后查阅,当然有些设计思想是通过自己的总结也有些是吸收别人的思想,同样如果能对你有所帮助那就再好不过了。

    于其说XXX项目总结,其实是对我弄的一套基础架构进行总结,只是用在这个项目中进行实践。

    废话就不多说了直接进入主题吧,系统的基础架构,说白了还是以用户为中心的权限控制等基础功能,这个系统可以实现对不同数据库的支持,没有采用ORM框架,还是以原生的SQL为主降低学习成本。

    1,开发环境:VS2012 +Resharper,SVN,PLSQL,ORACLE 11G,Arcgis

    2,开发组件:ASP.NET MVC 4,Dapper(是ADO.NET 的扩展),Newtonsoft.Json(对JSON的处理),Ninject(用于依赖注入),Aspose.Words(用于生成Word模版),Jquery EasyUi(用于做前端),Arcgis for javascript V3.8(用于对地图开发)

    3,开发时间:13年-10月至14年-2月

    4,开发人员:2人

    数据库

    1,权限库设计。

    还是先来看一下ER图吧

    image

    其实对于简单的权限管理应该就足够了,这个思路是来源于这参考 在这我就不在做解释了,这里面我只是简单的进行了一些优化。

    2,分库分表设计。

    有些业务数据过大我们可能需要进行分库分表设计。

    image

    说明:

    对数据进行拆分一般都是以时间,在TableIndex中的StartDt和EndDt就是业务数据产生时间。针对不同的业务表类型,有一个TblType来进行区分。根据这2个字段然后就可以查询出该表是在那个数据库中。从而就能很轻松的查询出对应的数据。

    3,可变列设计

    很多时候我们已经把业务表设计好了,但经常会遇到**表再加一个列****,这时我们就会去修改表结构,然后再把相关的代码给修改了,这不是很麻烦吗?而我是这样做的,因为比较简单就不详细说明了。

    image

    基础代码

    这个基础构架主要思想还是参考的Microsoft NLayerApp,但是想搞简单点也对他进行了大量瘦身。

    这次我重构得最多的还是如何让系统能够适应多数据的支持,下面说一下经过了几次调整:

    1,为了让系统够灵活,最好的方式当然是把SQL和程序分开,所以我把相应的SQL都做成了储存过程。(在SQL下没有什么问题,支持得很好!当我用同样的方式在ORACLE下就有问题了,最明显的就是ORACLE查询返回数据时就出问题了,具体的你们可以试一下。)

    2,最后没办法还是只能用SQL语句的形式。

    程序员还是用类图来说明吧。

    image

    说明:

    先对这个类图结构来说明吧,这个类图的产生还是经过了几次重构,最明显的是从以前版本的“继承”->“组合”的演变,下面我还是详细对这个类图进行说明。

    程序对多数据库的支持,程序一般要解决几个问题,1,数据库连接。2,INSERT,UPDATE的SQL语句,3,对于不同数据库它的分页SQL也不一样。4,参数化前缀也不一样,如:sql server 是’@’,oracle是’:’。

    为了让Repository和具体的数据源解耦,所以在构造函数中接收了ISql的参数用组合的方法解决,ISql主要就是为了解决像我上面说的4个问题。像查询和删除的SQL结构基本没有变化,所以我在ISql接口中就只定义了插入和修改字段。

    为了让插入和修改SQL脚本能够复用,参数化前缀就不能过早写死,所以我SqlBase接受了IAppContext接口。

    其实用类图一展示还是很清晰的,我这只对Repository的2个方法进行说明一下。

    Repository类说明

    Mapping:这个方法,主要解决类中有哪些字段要添加到数据库,如,一个类有时不一定所有的属性都是数据库的字段,那么我们就可以指定哪些属性要添加到数据库

    Code

    DeleteTrans:这个方法,主要是解决一些级联删除,如删除User同时还要把UserInfo的数据给删除掉,同时必须在一个事务下。

    public virtual int DeleteTrans(string sysId, Func<string, IDbTransaction, int> parent, Func<string, IDbTransaction, int> child)
            {
                using (var connection = Connection)
                {
                    using (var tran = connection.BeginTransaction(IsolationLevel.ReadCommitted))
                    {
                        int result = 0;
                        if ((result += child(sysId, tran)) >= 0)
                        {
                            if ((result += parent(sysId, tran)) >= 0)
                            {
                                tran.Commit();
                                return result;
                            }
                            tran.Rollback();
                            return result;
                        }
                        tran.Rollback();
                        return result;
                    }
                }
            }

    虽然重构了很多次,但是我觉得还有很多不满意的地方,如,我最终目标是把这个基础框架封装成一个组件的形式。以后最好不修改里面的代码,如果有依赖关系都最好用接口的方式,总的来说就是想尽量做到面向对象的“开放封闭”原则以及真正的面象接口编程。有这样的要求是深受ArcEngine那华丽设计的影响,促使我觉得做项目和做产品真的是2回事。

    UI

    在UI方面主要还是对EASYUI的方法扩展和对Arcgis for js的方法重构。

    一,对EASYUI的方法扩展

    1,为了让一个FORM中能够加载,像CheckBox,combobox,radiobox等类型控件。所以我重写了一个myLoad方法:

    $.extend($.fn.form.methods, {
        myload: function (jq, data) {
            return jq.each(
                function () {
                    loadVal(this, data);
                }
            );
    
            function loadVal(formElement, data) {
                if (!$.data(formElement, "form")) {
                    $.data(formElement, "form", {
                        options: $.extend({}, $.fn.form.defaults)
                    });
                }
                var opts = $.data(formElement, "form").options;
                if (typeof data == "string") {
                    var optSource = {};
                    if (opts.onBeforeLoad.call(formElement, optSource) == false) {
                        return;
                    }
                    $.ajax({
                        url: data,
                        data: optSource,
                        dataType: "json",
                        success: function (data) {
                            bindValue(data);
                        },
                        error: function () {
                            opts.onLoadError.apply(formElement, arguments);
                        }
                    });
                } else {
                    bindValue(data);
                }
    
                function bindValue(data) {
                    var form = $(formElement);
                    for (var name in data) {
                        var val = data[name];
                        var rr = setRadioAndCheckBox(name, val);
                        if (!rr.length) {
                            var f = form.find("input[numberboxName="" + name + ""]");
                            if (f.length) {
                                f.numberbox("setValue", val);
                            } else {
                                if (typeof val === 'object' && val != null) {
                                    $.each(val, function (cName, value) {
    
                                        var crr = setRadioAndCheckBox(name + "." + cName, value);
                                        if (!crr.length) {
                                            var cf = form.find("input[numberboxName="" + name + "." + cName + ""]");
                                            if (cf.length) {
                                                cf.numberbox("setValue", value);
                                            } else {
                                                $("input[name="" + name + "." + cName + ""]", form).val(value);
                                                $("textarea[name="" + name + "." + cName + ""]", form).val(value);
                                                $("select[name="" + name + "." + cName + ""]", form).val(value);
                                            }
                                        }
                                    });
                                } else {
                                    $("input[name="" + name + ""]", form).val(val);
                                    $("textarea[name="" + name + ""]", form).val(val);
                                    $("select[name="" + name + ""]", form).val(val);
                                }
                            }
                        }
                        setEasyUiCtrl(name, val);
                    }
                    opts.onLoadSuccess.call(formElement, data);
                    setValBox(formElement);
                }
    
                ;
    
                function setRadioAndCheckBox(name, val) {
                    var rr = $(formElement).find("input[name="" + name + ""][type=radio], input[name="" + name + ""][type=checkbox]");
                    rr._propAttr("checked", false);
                    rr.each(function () {
                        var f = $(this);
                        //避免字符串大小写问题
                        if (f.val().toUpperCase() == String(val).toUpperCase() || $.inArray(f.val(), val) >= 0) {
                            f._propAttr("checked", true);
                        }
                    });
                    return rr;
                }
    
                ;
    
                function setEasyUiCtrl(name, val) {
                    var form = $(formElement);
                    var cc = ["combobox", "combotree", "combogrid", "datetimebox", "datebox", "combo"];
                    var c = form.find("[comboName="" + name + ""]");
                    if (c.length) {
                        for (var i = 0; i < cc.length; i++) {
                            var type = cc[i];
                            if (c.hasClass(type + "-f")) {
                                if (c[type]("options").multiple) {
                                    c[type]("setValues", val);
                                } else {
                                    c[type]("setValue", val);
                                }
                                return;
                            }
                        }
                    }
                }
    
                ;
    
                function setValBox(eleForm) {
                    if ($.fn.validatebox) {
                        var t = $(eleForm);
                        t.find(".validatebox-text:not(:disabled)").validatebox("validate");
                        var valBox = t.find(".validatebox-invalid");
                        valBox.filter(":not(:disabled):first").focus();
                        return valBox.length == 0;
                    }
                    return true;
                }
    
                ;
            }
        }
    });

    2,权限需要动态的添加按钮

    $.extend($.fn.datagrid.methods, {
        addToolbarItem: function (jq, items) {
            return jq.each(function () {
                var dpanel = $(this).datagrid('getPanel');
                var toolbar = dpanel.children("div.datagrid-toolbar");
                if (!toolbar.length) {
                    toolbar = $("<div class="datagrid-toolbar"><table cellspacing="0" cellpadding="0"><tr></tr></table></div>").prependTo(dpanel);
                    $(this).datagrid('resize');
                }
                var tr = toolbar.find("tr");
                for (var i = 0; i < items.length; i++) {
                    var btn = items[i];
                    if (btn == "-") {
                        $("<td><div class="datagrid-btn-separator"></div></td>").appendTo(tr);
                    } else {
                        var td = $("<td></td>").appendTo(tr);
                        var b = $("<a href="javascript:void(0)"></a>").appendTo(td);
                        b[0].onclick = eval(btn.handler || function () { });
                        b.linkbutton($.extend({}, btn, {
                            plain: true
                        }));
                    }
                }
            });
        },
        removeToolbarItem: function (jq, param) {
            return jq.each(function () {
                var dpanel = $(this).datagrid('getPanel');
                var toolbar = dpanel.children("div.datagrid-toolbar");
                var cbtn = null;
                if (typeof param == "number") {
                    cbtn = toolbar.find("td").eq(param).find('span.l-btn-text');
                } else if (typeof param == "string") {
                    cbtn = toolbar.find("span.l-btn-text:contains('" + param + "')");
                }
                if (cbtn && cbtn.length > 0) {
                    cbtn.closest('td').remove();
                    cbtn = null;
                }
            });
        },
        removeAllToolbar: function(jq) {
            return jq.each(function() {
                var dpanel = $(this).datagrid('getPanel');
                var toolbar = dpanel.children("div.datagrid-toolbar");
                var tr = toolbar.find("tr");
                tr.empty();
            });
        }
    });

    3,让easyui的datagrid的列名的内容单独指定:

    $.extend($.fn.datagrid.defaults, {
        onLoadSuccess: function () {
            var target = $(this);
            var opts = $.data(this, "datagrid").options;
            var panel = $(this).datagrid("getPanel");
            //获取列
            var fields = $(this).datagrid('getColumnFields', false);
            //datagrid头部 table 的第一个tr 的td们,即columns的集合
            var headerTds = panel.find(".datagrid-view2 .datagrid-header .datagrid-header-inner table tr:first-child").children();
            //重新设置列表头的对齐方式
            headerTds.each(function(i, obj) {
                var col = target.datagrid('getColumnOption', fields[i]);
                if (!col.hidden && !col.checkbox) {
                    var headalign = col.headalign || col.align || 'left';
                    $("div:first-child", obj).css("text-align", headalign);
                }
            });
        }
    });

    二,Arcgis for js的方法重构

    最开始我们是用的Legacy Module,把每个功能点都做成的一个JS,虽然该有功能都实现但是这样就会出现很多个JS及JS的全局变量问题,最后通过用AMD方式用 DOJO提取成类->参考 。经过这样一翻重构代码看起来要比最开始清楚明白得多。

    而有关arcgis for js的开发可以参考Arcgis for javascript 开发思维导图

    测试

    对于测试我觉得项目一定要做相应的单元测试,用Resharper的单元测试工具真的很方便,虽然第一次感觉很无聊,但在后面的重构有了它你会更快的知道重构是否有问题。

    image

    总结

    在这篇文章中提得最多的2个字就是“重构”,我认为很多系统或者框架都是重构出来的不是一开始就设计出来的,这2个字看起简单就看你是不是愿意在上面花功夫。如果你愿意去做你肯定会有不一样的收获。

    但是我觉得在做项目时不要过度重构,或者是过度设计,这样就会让你陷得很深。那么你的项目风险系数就明显增加,很有可能在指定的时候内完不成工作。这可不是我想表达的主张。

    而我的建议在做项目时优先考虑完成然后逐步完善。

  • 相关阅读:
    作业七:用户体验设计案例分析
    作业五:需求分析
    作业四:结对编程 词频统计
    作业四:结对编程,词频统计
    作业三:词频统计
    作业2
    Github注册过程
    ArrayList的说明及简单用法
    Java类中成员变量、局部变量、静态变量的区别
    AspNetCore.SignalR的JwtBearer认证
  • 原文地址:https://www.cnblogs.com/jiguixin/p/3586976.html
Copyright © 2020-2023  润新知