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图吧
其实对于简单的权限管理应该就足够了,这个思路是来源于这参考 在这我就不在做解释了,这里面我只是简单的进行了一些优化。
2,分库分表设计。
有些业务数据过大我们可能需要进行分库分表设计。
说明:
对数据进行拆分一般都是以时间,在TableIndex中的StartDt和EndDt就是业务数据产生时间。针对不同的业务表类型,有一个TblType来进行区分。根据这2个字段然后就可以查询出该表是在那个数据库中。从而就能很轻松的查询出对应的数据。
3,可变列设计
很多时候我们已经把业务表设计好了,但经常会遇到**表再加一个列****,这时我们就会去修改表结构,然后再把相关的代码给修改了,这不是很麻烦吗?而我是这样做的,因为比较简单就不详细说明了。
基础代码
这个基础构架主要思想还是参考的Microsoft NLayerApp,但是想搞简单点也对他进行了大量瘦身。
这次我重构得最多的还是如何让系统能够适应多数据的支持,下面说一下经过了几次调整:
1,为了让系统够灵活,最好的方式当然是把SQL和程序分开,所以我把相应的SQL都做成了储存过程。(在SQL下没有什么问题,支持得很好!当我用同样的方式在ORACLE下就有问题了,最明显的就是ORACLE查询返回数据时就出问题了,具体的你们可以试一下。)
2,最后没办法还是只能用SQL语句的形式。
程序员还是用类图来说明吧。
说明:
先对这个类图结构来说明吧,这个类图的产生还是经过了几次重构,最明显的是从以前版本的“继承”->“组合”的演变,下面我还是详细对这个类图进行说明。
程序对多数据库的支持,程序一般要解决几个问题,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 internal override dynamic Mapping(SysButton item)
{
return new
{
SysId = item.SysId,
MenuId = item.MenuId,
BtnName = item.BtnName,
BtnIcon = item.BtnIcon,
BtnOrder = item.BtnOrder,
BtnFunction=item.BtnFunction,
RecordStatus = item.RecordStatus,
IsVisible = item.IsVisible
};
}
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的单元测试工具真的很方便,虽然第一次感觉很无聊,但在后面的重构有了它你会更快的知道重构是否有问题。
总结
在这篇文章中提得最多的2个字就是“重构”,我认为很多系统或者框架都是重构出来的不是一开始就设计出来的,这2个字看起简单就看你是不是愿意在上面花功夫。如果你愿意去做你肯定会有不一样的收获。
但是我觉得在做项目时不要过度重构,或者是过度设计,这样就会让你陷得很深。那么你的项目风险系数就明显增加,很有可能在指定的时候内完不成工作。这可不是我想表达的主张。
而我的建议在做项目时优先考虑完成然后逐步完善。
XXX项目总结的更多相关文章
- 【已解决】Error running 'xxx项目' Command line is too long(idea版)
[错误] Error running 'xxx项目': Command line is too long. Shorten command line for xxx or also for Sprin ...
- 老大说新项目的结构和 xxx 项目一样就可以了,我 ……(使用 Maven Archetype 快速创建项目)
前言 又要开发新项目了,还是创建新项目,怎么办?老大说按照 xxx 项目的结构创建一个新项目就可以了. 公众号:liuzhihangs,记录工作学习中的技术.开发及源码笔记:时不时分享一些生活中的见闻 ...
- XXX项目 android 开发笔记
1 工具? eclipse or android studio fragment 套用
- Failed to start component [StandardEngine[Catalina].StandardHost[localhost].StandardContext[/xxx项目名]]
可能是web.xml中的一些配置导致Tomcat启动失败,把web.xml中除 <welcome-file-list>外的全部配置删除后,就能正常启动Tomact了. 具体什么原因还不清楚 ...
- eclipse下maven项目保持原有目录结构配置resin运行环境
maven项目用起来很方便,但是它的目录结构和eclipse的目录结构是有区别的,故而在eclipse下的maven项目,直接运行调试是有一些问题的. 为了方便maven项目的运行调试,因而也就有了像 ...
- 用MVC4+EF改写XXX系统的计划--前言
感觉自己工作了三年,重来没有自己一个人写一个项目,从开始的策划,功能需求,业务逻辑,扩展,性能优化等等方面去做,从今天起准备发比半年时间重写XXX项目,每天中午和晚上分别花半个小时和一个小时开发,周末 ...
- php_Symfony_项目实战全过程记录
今天是2017年1月8号,正式接收到一个Symfony 的项目,准备全程记录遇到的问题及解决方法,之前被通知学习该框架,只是一直没有机会做项目,今天终于可以做了,希望2017把Symfony学的能会使 ...
- java程序员面试交流项目经验
粘贴自:https://blog.csdn.net/wangyuxuan_java/article/details/8778211 1:请你介绍一下你自己 这是面试官常问的问题.一般人回答这个问题过于 ...
- Maven 项目中的 pom.xml 文件内容说明
下面是一个比较全面的 POM 文件的结构,当然常用的并不需要这么多配置,视自己的项目需求而定. <project xmlns="http://maven.apache.org/POM/ ...
随机推荐
- android 异步加载框架 原理完全解析
一.手写异步加载框架MyAsycnTask(核心原理) 1.我为大家手写了一个异步加载框架,涵盖了异步加载框架核心原理. MyAsycnTask.java import android.os.Hand ...
- 图解VMware内存机制
在写<VMware内存机制初探>之后,原本是计划写一篇<VMware内存机制再探>的,讲一讲VMware内存机制中的另外几个重要内容,比如透明内存共享(TPS, Transpa ...
- myeclipse,eclipse打开当前文件所在文件夹
方法一: eclipse打开当前文件所在文件夹的插件Run-->External Tools-->External Tools Configurations...new 一个 progra ...
- [转] 利用SET STATISTICS IO和SET STATISTICS TIME 优化SQL Server查询性能
首先需要说明的是这篇文章的内容并不是如何调节SQL Server查询性能的(有关这方面的内容能写一本书),而是如何在SQL Server查询性能的调节中利用SET STATISTICS IO和SET ...
- BZOJ 1026: [SCOI2009]windy数
题目 人生中的第一道数位dp,很有趣,虽然我很快推出了结构,但是过程却迟迟没有写出来,最后看别人的题解才恍然大悟 d[i][j]表示数位为i,最高位为j的方案数 DpInit非常简单,复杂度应该是O( ...
- D3(Data-Driven-Document)中的一些细节
不定期更新,给自己看,如果能帮到别人,我也很开心. 1)好像 function中默认的两个参数d,和i,如果只有i,则i实际上是d 的内容. lineG.selectAll("line&qu ...
- nginx启动脚本
#!/bin/sh # # nginx - this script starts and stops the nginx daemin # # chkconfig: - 85 15 # descr ...
- Android应用:StatusBar状态栏、NavigationBar虚拟按键栏、ActionBar标题栏、Window屏幕内容区域等的宽高
一.屏幕中各种栏目以及屏幕的尺寸 当我们需要计算屏幕中一些元素的高度时,或许需要先获取到屏幕或者各种栏目的高度,下面这个类包含了Status bar状态栏,Navigation bar虚拟按键栏,Ac ...
- IOS XIB Cell自适应高度实现
1.代码实现Cell高度自适应的方法 通过代码来实现,需要计算每个控件的高度,之后获取一个cell的 总高度,比较常见的是通过lable的文本计算需要的高度. CGSize labelsize = [ ...
- Delphi 版 MIB_IF_ROW2
unit netioapi; interface uses Windows; type {$Z4} NDIS_MEDIUM = ( NdisMedium802_3, NdisMedium802_5, ...