#0002:Saas模式提了那么多年,为何还不能被中国企业接受

话说SaaS在国外很流行,国内也有很多应用很火,但一牵涉到企业级应用,就会发现企业还是不买单,宁愿独立部署版。

这到底为什么?

我举个国外很火的CRM应用Salesforce,国内也有很多云平台的CRM,市场做得也挺好,中小企业免费用,或者入门版象征性收费一下。但如果大点的企业,你让他们用SaaS的CRM,他们就会有顾虑:怕客户资料被卖给竞争对手,怕数据丢失,不管是天灾还是人祸,从使用风险和不使用的风险对比来看,还是不能使用。

毕竟公司的客户等核心数据是公司正常运作的保障,随便找一个第三方SaaS服务商,让他们来保障数据的安全,理性思考一下,都不会接受。

假如我有几百万会员或客户,我也不敢用SaaS的软件来管理客户。

通过看前面的文字,你们知道我也在做类似SaaS的企业应用。目前SaaS的问题,也是我考虑的问题,这影响到我如何设计和架构系统。是每个租户独立的程序和数据库?还是相同的程序和一个数据库,一个数据表用商户号字段隔离数据,还是不同商户不同的表?亦或者不同的程序,不同的数据库,而部署、维护、升级起来都可以及其简化和容易?

看问题需要两面性,反过来看,为何目前还是有不少SaaS应用在国内很流行,被中国企业所接纳?只能说类似的应用,传统的软件做的太差了,不是功能和用户体验,而是交付应用以前太重,而基于互联网的SaaS应用,主要简单注册一下,就能用起来,这样软件提供商就能有精力做用户体验、在线培训等及时又高效的服务了,所以当前的SaaS企业应用的优势是:缩短了交付流程

但企业应用所需要的不仅仅是快捷交付,独立部署、个性化定制、付费后的软件话语权等一个都不能少。

好在技术总是日新月异,不是凭空冒出来的,新的Docker技术如果能被充分利用,交付软件时,连安装、配置时间都省了,开箱即用。

同时独立部署的安全性等中国企业最关注的的问题,也迎刃而解了,真得就不需要目前公有云的SaaS应用了。

旺财C# .NET代码生成器支持DTcms MySQL版生成了

昨天跟一资深老用户沟通之后,发现DTcms MySql版用得人越来越多了,整个运行于Linux主机下,比一定要Windows和MSSQL数据库的要求降低了很多,优势太明显。

于是昨晚加班,国庆假期前,搞定如下升级:

1、完成MySql.Data.dll更新

消除了报错:MySql.Data.MySqlClient.MySqlException:Fatal error encountered attempting to read the resultset,以便支持较新版本的MySql数据库。

2、完善读取MySql数据库的表列表和字段属性的脚本

SELECT table_name TableName,TABLE_COMMENT TableDescription
FROM INFORMATION_SCHEMA.TABLES WHERE table_type = ‘base table’
ORDER BY table_name

SELECT CASE ORDINAL_POSITION WHEN 1 THEN TABLE_NAME ELSE ” END TableName,
ORDINAL_POSITION 序,COLUMN_NAME ColumnName,
CASE EXTRA WHEN ‘AUTO_INCREMENT’ THEN ‘Y’ ELSE ” END IsIdentity,
case COLUMN_Key when ‘PRI’ then ‘Y’ else ” END PrimaryKey,
DATA_TYPE DataType,
CASE DATA_TYPE
WHEN ‘double’ THEN NUMERIC_PRECISION
WHEN ‘decimal’ THEN NUMERIC_PRECISION
WHEN ‘tinyint’ THEN NUMERIC_PRECISION
WHEN ‘int’ THEN NUMERIC_PRECISION
WHEN ‘bigint’ THEN NUMERIC_PRECISION
ELSE CASE WHEN CHARACTER_MAXIMUM_LENGTH IS NULL THEN 0 ELSE CHARACTER_MAXIMUM_LENGTH END
END ByteLength,
CASE DATA_TYPE
WHEN ‘double’ THEN NUMERIC_PRECISION
WHEN ‘decimal’ THEN NUMERIC_PRECISION
WHEN ‘tinyint’ THEN NUMERIC_PRECISION
WHEN ‘int’ THEN NUMERIC_PRECISION
WHEN ‘bigint’ THEN NUMERIC_PRECISION
ELSE CASE WHEN CHARACTER_MAXIMUM_LENGTH IS NULL THEN 0 ELSE CHARACTER_MAXIMUM_LENGTH END
END Length,
IFNULL(numeric_scale,”) IsNumeric,
CASE is_nullable WHEN ‘YES’ THEN ‘Y’ ELSE ” END AllowNull,
column_default DefaultValue,
COLUMN_COMMENT Description
FROM INFORMATION_SCHEMA.COLUMNS
WHERE table_name = ‘tableName ‘

3、增加DTcms4/5 MySql版DAL生成

生成到新目录:DTcms.DAL.MySql,2个模板,同时支持DTcms4和DTcms5两个版本。

4、发布V20181001国庆版

5、下一步计划整理在线版的帮助文档

图文和视频汇总在一起,并增加开发实例视频教程

#0001:Done is better that perfect,比完美更好的是完成

Done is better that perfect并非Facebook CEO原创,但因为他说出来而被大家所知。中文直译就是标题所说的:比完美更好的是完成,但我觉得翻译为:比完美更重要的是完成,更好些。

我开发软件的经历

在我过往软件从业经历中不仅做过基于原有知名产品的二次开发,也有真正从零开始的产品开发。这里所说的产品包括:网站、有人机交互的应用软件、纯后台的程序、功能插件、APP等。有那么几年,一直觉得自己做得东西太小、或者不成熟、或者不强大,不敢给真正需要的人、企业去用,从而躺在自己的硬盘里或者只给唯一少数用户带来利益。

记得2013年,我和TonySiko、Jack曾经就是否要做自己的产品这个问题而犹豫不决,最大的困难来自思维局限,认为一定要做堪比市面上已经成百上千人开发多年的成熟产品,其实后来想开了,就从小的开始:小插件、小应用、小程序、小系统,解决用户的小问题、小公司的问题、小范围的问题等。

2014年起,开始为邻居的珠宝公司优化运营流程,开发针对电商的库存管理运营平台,并持续3年才完成覆盖线上电商和线下实体批发业务的业务系统。从2015年起,陆续推出多个FlowPortal插件、DTcms插件,并在淘宝销售,2017年推出基于针对DTcms的代码生成器,同期也将以前写网站的经验和积累,弄成建站平台。2018年把自己看到的很多企业运营中常见的问题归类,并提出自己的解决方案,围绕这些主题开发一些小软件产品,是我目前的定位和方向,毕竟第一是只有我一个人开发,第二做帮用户解决问题的方案中,软件只是一部分。软件背后的逻辑、嵌入其内的流程、简洁的用户体验界面、基于系统所存储的数据(Data)而带给用户的信息(Information),才是最有价值的地方。

为何不要上来追求完美

用过Windows的人都知道要打补丁,也知道这个Windows从95、98、Me、2000、xp、7、10、2013、2012、2016、2019,从未停止升级。

既然这么大软件公司,都是在一个固定期限段追求完成,然后后继再来完善、完美。那么对于小软件公司、个人开发者,又有什么财力能支撑我们上来就追求完美。相信扎克伯格也深谙此道。

不管你的的目标有多么的高远,引领市场、行业No1的前提都是你必须活着,远景对倒闭的企业和组织没有任何意义。

饼可以画,但是生存盈利还是当前最重要的。尽管不完美,功能还只是实现基本功能,用户操作也不是很方便,质量稳定性也不是很好,可维护性可调试性需求也没有考虑,但是解决了客户棘手的问题,更多功能需求、更好用户体验以及售后维护等等都可以在推出产品之后通过用户反馈、市场验证来逐步的完善。这也是我们经常说的软件和互联网都需要有迭代过程的。

看到这里,你是否也认同,应该先完成“无中生有”,不应该上来就“长达成人”后才出来见人。相信有很多有想法、有技术的年轻人,因为这个顾虑,慢慢熬成了中年油腻男,最后连想的勇气都没有了。

立即开始,先做出来

如果,你也和我一样:使用大型、传统软件多年;在企业里熟知各环节的业务流程,并对痛点有感触,有流程优化和自己解决的能力,不妨停止恐惧,即刻开始。不要被下面这个复杂的软件开发常规流程所吓倒。

从一个不完美的小功能开始,实实在在解决多数公司会遇到的问题。然后持续付出、不断完善,直至完美功能、完美产品。

我的那些已经完成但不完美产品

前几年的大家可以上我的淘宝店,以插件为主。下面几个是我将近3年的积累整理、优化所做的。

  1. 旺财C# .NET代码生成器 – 针对技术人员,也为自己的快速开发平台配套的,提高开发效率的利器,没有它,我哪有这么多精力做下面的软件。
  2. 旺财云库存 – 针对中小型企业,特别是有一物一码(一码一物)、批次、质保期、有效期要求的金银珠宝、化妆品行业。
  3. 旺财云进销存 – 针对中小型企业,没有ERP系统
  4. 旺财供应商门户 – 针对大中型企业,对接ERP,与下游供应商进行订单协同,未来扩展询价、投标、质量跟踪协作等。
  5. 旺财客户门户 – 针对大中型企业,对接ERP,利用移动互联网将库存开放给客户、经销商,方便其利用移动互联网和传统PC浏览器进行下单并跟踪、协同订单全流程。

这些产品都有核心并可实施,目前仅放出了旺财云库存和旺财云进销存的演示系统,预计很快就会将供应商平台演示系统开放出来。

从0到0.1

如本文标题所说:不求完美,只求完成。也许我这里的“完成”只是完成了从0到0.1,这些产品如能被越来越多的用户用在他们的日常工作当中,提高效率、节约成本、创造价值,那我将会更加持续付出精力,来完善、完美它们。

也许你会问,你为什么选择做这些产品呢?后面的系列,我会陆续分享我的想法。

#0000:Talk is cheap,Show me the code.

做IT的,会写代码的都应该听说过这句名言,中文翻译:能说算不上什么,有本事就把你的代码给我看看。这句话是Linux 的创始人 Linus Torvalds 在 2000-08-25 给linux-kernel 邮件列表的一封邮件提到的。

作为一个程序员,如果不能产出高质量的代码,这的确有失本分。

那么怎么评价代码是否好?

那么又有没有办法让自己的代码派上更大的用场呢?

吴军《硅谷来信》里面提到了一个“工程师的5个等级”概念,我非常认同,我觉得这个工程师可以泛指为各行各业的工程师,不能仅仅看做IT工程师或程序员。

书中提到:能从第五级和第四级这种仅把“工作”做好,跳跃到“能独立设计和实现产品”,外加“在市场上获得成功”,得到客户认可,或者市场回报后,继续“设计并实现别人无法做出的产品”实现从第三级到第二级飞跃,持续努力和聚焦,进而能有机会进入第一级开创一个产业”。

简单一幅图,就给我们指明的方向。而令我倍感庆幸地是,这些年的经历,的确是这么个路径。

那么我写这篇文字的意图,想必大家也能猜到一二,我是想即日起开启新的分享:我过去几年在业余时间投入大量精力,经历了To B 产品打造和试水阶段,正打算将产品推向市场,帮助更多企业、帮助更多人,提高效率、增加收益、降低内耗,进而为中国社会高效运转和蓬勃发展,贡献自己的一份力量。

这个系列的分享每篇文章都会有一个编号,按照惯例从0开始,第一篇编号:#0000,如果能有幸写满一万篇,那此生也功德圆满了。

 

将旺财珠宝库存管理系统的前端ZUI升级到1.8.1

ZUI是一套开源的HTML5跨屏框架,是基于 Bootstrap 深度定制开源前端实践方案,帮助中国人快速构建现代跨屏应用。从2014年开始用于旺财珠宝库存管理系统的开发(历程#1),经历了电商零售版和实体批发版的2个版本的开发,较好地完成了客户在用户体验方面的要求,再次感谢这个位于青岛的开发团队,持续改善着这套UI,虽有VUE、React等后期前端之秀,但我独爱Bootstrap、钟爱ZUI。

旺财珠宝库存管理系统于2016年就基本稳定,适用于黄金、银等珠宝电商和实体批发企业使用。可喜的是我于2017年去申请了软件著作权,如果您或您周围的朋友有这方面需求,欢迎与我联系。由于时间仓库,目前没有搭建演示系统,但年内计划投入服务器托管,开放软件试用。需要强调的是:软件虽已成型,但可根据各企业需求定制开发。

由于系统一直使用的还是v1.5.0 – 2016-09,趁着周末升级到v1.8.1 – 2018-01-18。同时修复了一些已知的Bug。

还是那句话:看着别人积累出各种产品,终于知道其中的不易,不过既然已经上路,就继续坚持,持续投入,日积月累,必有成效。

 

devbridge/jQuery-Autocomplete 1.2.21升级到

旺财系列库存管理系统、WMS、进销存、订单系统、下单系统、客户门户等软件中使用的下拉自动完成组件是:devbridge/jQuery-Autocomplete,官网地址:https://github.com/devbridge/jQuery-Autocomplete/

目前用的v1.2.21是2015年6月的版本,今天升级到v1.4.7,更新日期2017年12月。

旺财2018的618小福利

今天端午节,我的旺财C#.NET代码生成器发布了V20180618版本,同时即日起公开DTcms4.旺财代码生成器免费版.20170926,有需要的朋友加我微信获取。

DTcms5版本可在淘宝购买:https://item.taobao.com/item.htm?spm=a230r.1.14.13.51f476cb2Kotft&id=545213785654&ns=1&abbucket=14#detail

在Global.asax中获取Session的注意事项

几年前给朋友珠宝公司开发过一套旺财珠宝库存管理系统,用得还是web Form老技术,但是更多的走Ashx+Ajax,但前端可是HTML5+jQuery+BootStrap等新技术,所以不论功能还是用户体验,都能很完美的满足用户要求(用户才不管你用的是什么技术,先进的和古老的都必须解决他的问题,然后还需要好用)。近期特别反馈说有些页面比较慢,我觉得用了几年了,数据库就近2个G了,可能是数据库查询的问题,也可能是程序执行的问题,也可能用户网络问题。数据库可以在服务器上用Sql Server Profiler进行查询分析,但页面上还得做点跟踪。于是就用Global.asax来实现,本来很方便的,但为了获取当前登录用户,需要在Global.asax中获取Session,花了点时间才搞定,记录下来分享一下。

本来想在Application_BeginRequest或者Session_Start里面获取的,可怎么也获取不到,于是翻看MSDN了解Global.asax的事件及执行顺序,在Application_AcquireRequestState中才获取到。

    protected DateTime StartDateTime;

    protected void Application_BeginRequest(object sender, EventArgs e)
    {
        //开始执行时间
        StartDateTime = DateTime.Now;
    }
    protected BaseUserInfo CurrentUserInfo;
    protected void Application_AcquireRequestState(object sender, EventArgs e)
    {
        if (Utilities.UserIsLogOn())
        {
            CurrentUserInfo = Utilities.GetUserInfo();
        }
    }

    protected void Application_EndRequest(object sender, EventArgs e)
    {
        DateTime endDateTime = DateTime.Now;
        TimeSpan ts = endDateTime - StartDateTime;
        //5秒以上的慢页面进行记录
        if (ts.TotalMilliseconds >= 5000)
        {
            StringBuilder sb = new StringBuilder();
            sb.Append("时间:" + endDateTime.ToString("yyyy-MM-dd hh:mm:ss fff") + ",当前请求URL:" + HttpContext.Current.Request.Url + ",请求的参数为:" + HttpContext.Current.Request.QueryString + ",页面加载的时间:" + ts.TotalMilliseconds + " 毫秒");
            if (CurrentUserInfo != null)
            {
                sb.Append(",用户:" + CurrentUserInfo.UserName);
            }
            FileUtil.WriteMessage(sb.ToString(), BaseSystemInfo.StartupPath + "//Log//Slow/" + DateTime.Now.ToString(BaseSystemInfo.DateFormat) + ".txt");
        }

    }

通过上述代码就可方便的获取哪些页面比较慢,何时、何人、何参数、何地(IP)发生的。

2018-05-11 03:33:18 947:[当前请求URL:Modules/WMS/ItemMaster/ItemMasterPlan.aspx;请求的参数为:;页面加载的时间:8151.3672 毫秒]
2018-05-11 04:09:25 181:[当前请求URL:Modules/WholesaleWMS/tools/WholesaleBPStatement.ashx?action=RefreshStatement;请求的参数为:action=RefreshStatement;页面加载的时间:19720.7031 毫秒]
2018-05-11 05:18:10 486:[当前请求URL:Modules/WMS/OutboundOrderLine/OutboundOrderLineListSummary.aspx;请求的参数为:;页面加载的时间:16742.1875 毫秒]

2018-05-12 10:33:59 305:[当前请求URL:Modules/WMS/PurchaseDemand/PurchaseDemandAdmin.aspx;请求的参数为:;页面加载的时间:9375.9765 毫秒]
2018-05-12 10:49:19 497:[当前请求URL:Modules/WMS/ItemMaster/ItemMasterPlan.aspx;请求的参数为:;页面加载的时间:5278.3203 毫秒]
2018-05-12 01:24:36 673:[当前请求URL:Modules/WMS/InboundOrderLine/InboundOrderLineListReport.aspx;请求的参数为:;页面加载的时间:11416.0156 毫秒]
2018-05-12 01:24:42 045:[当前请求URL:Modules/WMS/InboundOrderLine/InboundOrderLineListReport.aspx;请求的参数为:;页面加载的时间:6209.9609 毫秒]
2018-05-12 01:24:42 611:[当前请求URL:Modules/WMS/InboundOrderLine/InboundOrderLineListReport.aspx;请求的参数为:;页面加载的时间:10142.5781 毫秒]
2018-05-12 04:39:35 251:[当前请求URL:Modules/WMS/OutboundOrderLine/OutboundOrderLineListSummary.aspx;请求的参数为:;页面加载的时间:16623.0469 毫秒]
2018-05-12 04:51:31 401:[当前请求URL:Modules/WMS/OutboundOrderLine/OutboundOrderLineListSummary.aspx;请求的参数为:;页面加载的时间:16648.4375 毫秒]
2018-05-12 04:57:15 362:[当前请求URL:Modules/WMS/OutboundOrderLine/OutboundOrderLineListSummary.aspx;请求的参数为:;页面加载的时间:16552.7343 毫秒]

最后附上MSDN上对Global.asax的解释:

按执行顺序来解释一下Global.asax.cs中相应的事件处理方法的含义

  1. Application_BeginRequest:BeginRequest是在收到Request时第一个触发的事件,这个方法自然就是第一个执行的了。
  2. Application_AuthenticateRequest:当安全模块已经建立了当前用户的标识后执行。
  3. Application_AuthorizeRequest:当安全模块已经验证了当前用户的授权时执行。
  4. Application_ResolveRequestCache:当ASP.NET完成授权事件以使缓存模块从缓存中为请求提供服务时发生,从而跳过处理程序(页面或者是WebService)的执行。这样做可以改善网站的性能,这个事件还可以用来判断正文是不是从Cache中得到的。
  5. Application_AcquireRequestState:当ASP.NET获取当前请求所关联的当前状态(如Session)时执行(真是拗口啊,msdn上就这样写的,我自己想不出什么好句子了)。
  6. Application_PreRequestHandlerExecute:当ASP.Net即将把请求发送到处理程序对象(页面或者是WebService)之前执行。这个时候,Session就可以用了。
  7. Application_PostRequestHandlerExecute:当处理程序对象(页面或者是WebService)工作完成之后执行。
  8. Application_ReleaseRequestState:在ASP.NET执行完所有请求处理程序后执行。ReleaseRequestState事件将使当前状态数据被保存。
  9. Application_UpdateRequestCache:在ASP.NET执行完处理程序后,为了后续的请求而更新响应缓存时执行。
  10. Application_EndRequest:同上,EndRequest是在响应Request时最后一个触发的事件,这个方法自然就是最后一个执行的了。

再附上两个无顺序的,随时都可能执行的

  1. Application_PreSendRequestHeaders:向客户端发送Http标头之前执行。
  2. Application_PreSendRequestContent:向客户端发送Http正文之前执行。

C#开发代码规范中PascalCase和camelCase的两个有用的方法类

#region 代码规范风格化
        /// <summary>
        /// 转换为Pascal风格-每一个单词的首字母大写
        /// </summary>
        /// <param name="fieldName">字段名</param>
        /// <param name="fieldDelimiter">分隔符</param>
        /// <returns></returns>
        public static string ConvertToPascal(string fieldName, string fieldDelimiter)
        {
            string result = string.Empty;
            if (fieldName.Contains(fieldDelimiter))
            {
                //全部小写
                string[] array = fieldName.ToLower().Split(fieldDelimiter.ToCharArray());
                foreach (var t in array)
                {
                    //首字母大写
                    result += t.Substring(0, 1).ToUpper() + t.Substring(1);
                }
            }
            else if (string.IsNullOrWhiteSpace(fieldName))
            {
                result = fieldName;
            }
            else if (fieldName.Length == 1)
            {
                result = fieldName.ToUpper();
            }
            else
            {
                result = fieldName.Substring(0, 1).ToUpper() + fieldName.Substring(1);
            }
            return result;
        }
        /// <summary>
        /// 转换为Camel风格-第一个单词小写,其后每个单词首字母大写
        /// </summary>
        /// <param name="fieldName">字段名</param>
        /// <param name="fieldDelimiter">分隔符</param>
        /// <returns></returns>
        public static string ConvertToCamel(string fieldName, string fieldDelimiter)
        {
            //先Pascal
            string result = ConvertToPascal(fieldName, fieldDelimiter);
            //然后首字母小写
            if (result.Length == 1)
            {
                result = result.ToLower();
            }
            else
            {
                result = result.Substring(0, 1).ToLower() + result.Substring(1);
            }
            
            return result;
        }
        #endregion

近期为统一Oracle数据库下大写表名和字段,以及下划线_分隔符的特点,升级了旺财C#.NET代码生成器,将规范化的代码写了2个方法用于Camel和Pascal风格化,用于有表字段分隔符的场景。

骆驼拼写法,英文名CamelCase。分为两种:

第一个词的首字母小写,后面每个词的首字母大写,叫做“小骆驼拼写法”(lowerCamelCase);

第一个词的首字母,以及后面每个词的首字母都大写,叫做“大骆驼拼写法”(UpperCamelCase),又称“帕斯卡拼写法”(PascalCase)

两者核心差别:PascalCase第一个单词的首字母大写,而CamelCase第一个单词的首字母小写。

还是连接池的问题,终于搞定了

上个月中旬提到过被Web.config中数据库连接池 Max Pool Size的问题折腾了,但是增加到200个最大连接池,还是会报错:

System.InvalidOperationException: Timeout expired.  The timeout period elapsed prior to obtaining a connection from the pool.   This may have occurred because all pooled connections were in use and max pool size was reached.

本打算借助微软的免费工具Debug Diagnostic Tool v2 Update 2,搞了半天不太会用。为了省事,借助RedGate的免费14天试用的ANTS Memory Profiler free trial,终于找到问题所在。原来是连接泄露了,在connection连接后未及时使用dispose()或close()进行关闭。

在升级改进吉日嘎拉DotNet数据访问层DotNet.Utilities时,本来继承了IDisposable接口,后改为IDbHelper,但并未启用手动关闭连接。原来的IDisposable接口的主要用途是释放非托管资源。当不再使用托管对象时,垃圾回收器会自动释放分配给该对象的内存。

至此,数据库连接池的报错终于完全修复。