程序员的自我救赎---1.4.2: 核心框架讲解(BLL&Tool)
《核心框架讲解》
之前想用一篇文章讲完核心框架的三四个程序集,后来写着写着就发现一篇文章写不完,这才想了一下用最少要用三篇。
上一篇讲了一下DAL,其实也没讲特别透,主要讲了一下DA部分的使用。这一篇文章主要来讲一下FacadeBase和常用工具。
其实在之前讲《事务的使用》 的时候已经着重介绍过FacadeBase,不复杂。
using System; using Winner.Framework.Core.DataAccess; using Winner.Framework.Core.Interface; using Winner.Framework.Utils; namespace Winner.Framework.Core.Facade { /// <summary> /// 通用三层架构的业务处理层(BLL)基类 /// </summary> public class FacadeBase : IDisposable, IPromptInfo { private PromptInfo _promptInfo; /// <summary> /// 错误详情 /// </summary> public PromptInfo PromptInfo { get { if (this._promptInfo == null) { this._promptInfo = new PromptInfo(); } return this._promptInfo; } } /// <summary> /// 分页控件 /// </summary> public IChangePage ChangePage; #region 错误详情上报 /// <summary> /// 错误详情上报 /// </summary> /// <param name="result">错误详情</param> protected void Alert(PromptInfo result) { this._promptInfo = result; } /// <summary> /// 错误详情上报 /// </summary> /// <param name="restulType">错误类型</param> protected void Alert(ResultType restulType) { this.PromptInfo.Alert(restulType); } /// <summary> /// 错误详情上报 /// </summary> /// <param name="msg">错误信息</param> protected void Alert(string msg) { this.PromptInfo.Alert(msg); } /// <summary> /// 错误详情上报 /// </summary> /// <param name="restulType">错误类型</param> /// <param name="result">错误详情</param> protected void Alert(ResultType restulType, PromptInfo result) { this.PromptInfo.Alert(restulType, result); } /// <summary> /// 错误详情上报 /// </summary> /// <param name="restulType">错误类型</param> /// <param name="msg">错误信息</param> protected void Alert(ResultType restulType, string msg) { this.PromptInfo.Alert(restulType, msg); } /// <summary> /// 错误详情上报 /// </summary> /// <param name="msg">错误信息</param> /// <param name="result">上级错误信息</param> protected void Alert(string msg, PromptInfo result) { this.PromptInfo.Alert(msg, result); } /// <summary> /// 错误详情上报 /// </summary> /// <param name="restulType">错误类型</param> /// <param name="msg">错误信息</param> /// <param name="result">错误详情</param> protected void Alert(ResultType restulType, string msg, PromptInfo result) { this.PromptInfo.Alert(restulType, msg, result); } #endregion #region 事务 /// <summary> /// 事物对象 /// </summary> public Transaction Transaction { get; private set; } /// <summary> /// 开启事物 /// </summary> protected void BeginTransaction() { if (this.Transaction == null) { this.Transaction = new Winner.Framework.Core.DataAccess.Transaction(); } this.Transaction.BeginTransaction(); } /// <summary> /// 提示事物 /// </summary> protected void Commit() { this.Transaction.Commit(); } /// <summary> /// 强制回滚事物 /// </summary> protected void RealRollback() { this.Transaction.RealRollback(); } /// <summary> /// 事物串联 /// </summary> /// <param name="transaction">事物对象</param> public void ReferenceTransactionFrom(Transaction transaction) { this.Transaction = transaction; } /// <summary> /// 回滚事物 /// </summary> protected void Rollback() { this.Transaction.Rollback(); } #endregion #region 对象销毁 /// <summary> /// 销毁对象 /// </summary> public virtual void Dispose() { } #endregion } }
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using Winner.Framework.Utils.Model; namespace Winner.Framework.Utils { /// <summary> /// 错误详情 /// </summary> [DebuggerDisplay("ResultType:{Message}")] public class PromptInfo { #region Property private string _customMessageStack; private string _messageStack; /// <summary> /// 当前自定义错误信息 /// </summary> public string CustomMessage { get; private set; } /// <summary> /// 当前自定义错误信息堆栈 /// </summary> public string CustomMessageStack { get { if (string.IsNullOrEmpty(this._customMessageStack)) { this.LoadResultStack(this, 1); } return this._customMessageStack; } } /// <summary> /// 内部错误信息 /// </summary> public PromptInfo InnerPromptInfo { get; private set; } /// <summary> /// 当前错误信息 /// </summary> public string Message { get; private set; } /// <summary> /// 当前错误信息堆栈 /// </summary> public string MessageStack { get { if (string.IsNullOrEmpty(this._messageStack)) { this.LoadResultStack(this, 1); } return this._messageStack; } } /// <summary> /// 当前错误类型 /// </summary> public ResultType ResultType { get; set; } /// <summary> /// 当前错误类型编号 /// </summary> public int Code { get { return (int)ResultType; } } #endregion /// <summary> /// 错误详情上报 /// </summary> /// <param name="restulType">错误类型</param> public void Alert(ResultType restulType) { this.ResultType = restulType; this.Message = string.Format("{{错误代码:{0} {1}}}", (int)restulType, restulType); } /// <summary> /// 错误详情上报 /// </summary> /// <param name="message">错误信息</param> public void Alert(string message) { this.CustomMessage = message; ResultType = ResultType.未指定; this.Message = message + @" " + string.Format("{{错误代码:{0} {1}}}", (int)ResultType, ResultType); } /// <summary> /// 错误详情上报 /// </summary> /// <param name="restulType">错误类型</param> /// <param name="innerPromptInfo">内部错误</param> public void Alert(ResultType restulType, PromptInfo innerPromptInfo) { if (innerPromptInfo == this) return; this.InnerPromptInfo = innerPromptInfo; this.Alert(restulType); } /// <summary> /// 错误详情上报 /// </summary> /// <param name="restulType">错误类型</param> /// <param name="message">错误信息</param> public void Alert(ResultType restulType, string message) { this.CustomMessage = message; this.ResultType = restulType; this.Message = message + @" " + string.Format("{{错误代码:{0} {1}}}", (int)ResultType, ResultType); } /// <summary> /// 错误详情上报 /// </summary> /// <param name="message">错误信息</param> /// <param name="innerPromptInfo">内部错误</param> public void Alert(string message, PromptInfo innerPromptInfo) { if (innerPromptInfo == this) return; this.InnerPromptInfo = innerPromptInfo; this.Alert(message); } /// <summary> /// 错误详情上报 /// </summary> /// <param name="restulType">错误类型</param> /// <param name="message">错误信息</param> /// <param name="innerPromptInfo">内部错误</param> public void Alert(ResultType restulType, string message, PromptInfo innerPromptInfo) { if (innerPromptInfo != this) { this.InnerPromptInfo = innerPromptInfo; } this.Alert(restulType, message); } private void LoadResultStack(PromptInfo promptInfo, int index) { if (index == 1) { this._customMessageStack = string.Empty; this._messageStack = string.Empty; } string str1 = this._customMessageStack; if (!string.IsNullOrEmpty(promptInfo.CustomMessage)) { this._customMessageStack = string.Concat(new object[] { str1, index > 1 ? @";" : string.Empty, index, ".", promptInfo.CustomMessage }); } string str2 = this._messageStack; if (!string.IsNullOrEmpty(promptInfo.Message)) { this._messageStack = string.Concat(new object[] { str2, index > 1 ? @";" : string.Empty, index, ".", promptInfo.Message }); } if (promptInfo.InnerPromptInfo != null) { this.LoadResultStack(promptInfo.InnerPromptInfo, ++index); } } /// <summary> /// 转换为FuncResult对象 /// </summary> /// <returns></returns> public FuncResult AsFuncResult() { return FuncResult.FailResult(this.MessageStack, Code); } } }
FacadeBase依赖DataAccess ,FacadeBase重点做的事情除了事务以外,就是错误详情上报,因为我们主旨是让
业务流程返回True / False。 我见过有的为了上报错误详情,使用ref 、out 来实现,其实完全没毕业,这个功能太常用
常用的功能就应该封装起来。
这里说个额外的话题,其实上一篇中我讲到DA的SQL拼接的时候,其实那些功能很好用,但是后来由于使用“参数化”了,
这就使得程序员要去自己写“like”,”%”等等这样的关键字,有时间我还想安排人封装一下,参数化也要封装这些操作。开发起来
才简单。
说回正题,关于FacadeBase,“事务”已经讲过来,Alert 基本一看就懂,所以这里不做更细致的分析,看一下在项目中的应用。
而在前端,调用Facade的时候,就只需要有取Facade 的 PromptInfo.Message 即可。 如下图:
关于Facade就介绍到这里,下面写一下今天的重点:Winner.Framework.Utils 和 Winner.Framework.Encrypt;
从字面上很好理解,一个是负责加密的,一个是工具集。 我们还是由浅入深的方式来讲一下先讲Utils吧,因为比较简单
这个我不每个都做讲解。一张图贴明白:
目前就这些,作为一个工具集,其实网上有很多 Comon.dll下载,会内置很多工具,这里我简单介绍一下,我们Winner的工具集
Configuration: 读取Web.Config 的配置,常规我们用于读取数据库连接字符串,和AppSetting的配置。(不支持配置文件写入)
Json: 做Json的序列化与反序列化。这里引用的是系统框架 System.Web.Script.Serialization做的没有用Newtonsoft.json。
Listener: 监听,可以用户执行监听等待,本质为一个监听服务。
Log : 引用log4net 写的日子记录工具。
Map : 映射工具类。
Model: 常用的实体,如:基本Name,Value键值对封装。如: 常规的返回对象: 是否成功,错误信息,错误码。这样的常用实体。
ModelVerify:实体验证,本身MVC的控制层可以使用系统自带的验证,但是如果放到业务层这不可用,这里封装后任意地方可使用。
Network: 网络请求帮助类,内置如:上传文件,封装获取网址等网络请求中需要的操作方法。
Photo:图片处理。
Prompt:上报错误详情,上面已经讲到过了。
Reflection: 获取成员特性(包括集合)。
Sign:签名验证,如第三方Web服务调用中常用于防篡改。
Text: 内置如:字符串格式化操作,安全码生成,后面单独讲该类操作。
Track:Stopwatch 测试方法,执行过程中的时间,常用语检查程序性能。
这里单讲一下Text:
Text 里面包含的工具很多简单的 格式化时间类型,就不讲了重点讲一下安全转换 和 安全验证码:
安全转换 : 正常情况下我们常见的将 String类型 转换成 int 类型一般都是用 Convert.ToInt 以及 int.Parse两种方式,
使用Convert遇见如汉字这样的有可能会导致程序异常,而用int.Parse也一样,这时候最安全的做法是TryParse。而
TryParse一旦用就要写if判断。有时候也不那么好用,所以Winner封装了安全转换。
/// <summary> /// 转换为ToInt32 /// </summary> /// <param name="defaultValue">默认值</param> /// <returns></returns> public int ToInt32(int defaultValue = 0) { try { if (!Value.HasValue()) { return defaultValue; } return Convert.ToInt32(Value); } catch (Exception) { return defaultValue; } }
类似这种封装还有很多。另外一个就是 生成安全码,我们知道身份证是带验证功能的,也就是说数字编号处理本身的顺序编号以外,
要防止别人生成相关编号,来注入的情况下,就需要生成带验证功能的编号。Winner也做了相关封装:
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Winner.Framework.Utils.Text { /// <summary> /// 加权码 /// </summary> public class SafeCode { /// <summary> /// 45位加权因子(用Excle打乱顺序) /// </summary> private static int[] Factor = new[] { 5, 44, 43, 28, 34, 19, 31, 15, 36, 41, 11, 35, 3, 4, 24, 42, 26, 40, 20, 33, 17, 14, 10, 2, 23, 18, 45, 27, 1, 25, 8, 37, 29, 16, 22, 7, 9, 21, 13, 30, 32, 38, 39, 6, 12 }; /// <summary> /// 加权 /// </summary> /// <param name="code">需要加权编码</param> /// <returns></returns> public static long Weighted(long code) { string codeString = code.ToString(); if (codeString.Length <= 1 || codeString.Length > Factor.Length) return 0; int sum = 0; for (int i = 0; i < codeString.Length; i++) { sum += int.Parse(codeString[i].ToString()) * Factor[i]; } codeString += sum % codeString.Length; return long.Parse(codeString); } /// <summary> /// 验证 /// </summary> /// <param name="code">需要验证的数字</param> /// <returns></returns> public static bool Validate(long code) { string codeString = code.ToString(); if (codeString.Length <= 1 || codeString.Length > Factor.Length) return false; string original = codeString.Substring(0, codeString.Length - 1); return code == Weighted(long.Parse(original)); } } }
这个编号本身自带校验功能,类似的功能还很多,我不一一列举。当然关于工具类,Winner其实还是有很多可以改进的地方,
要囊括所有的攻击的话,Winner的工具类基本还是偏少但是够用,有时间Winner还可以继续扩展一下功能类。就比如之前我
网上看到的苏飞程序集 就有很多功能我们没有。
====================华丽的分割线=================
接下来说一下Winner.Framework.Encrypt,字面意思好理解,就“加密类”常规的加密类网上有很多什么des,aes。
Winner框架中也有封装:
最常见的Sha1,Md5,RSA,这都没什么稀奇的。这里重点介绍一下SafePassword.cs 和 Hardware.cs
首先介绍一下SafePassword:
我们知道大部分的网站用户密码是用MD5做的密文,登录时对比密文是否一致。但是随着这几年MD5的
撞库,以及专门有人以大硬盘大数据收集Md5,做匹配所以导致MD5做密码并不那么安全。
我们Winner里面用于用户的密码加密则用的是Hardware这一套,首先Hardware 不是一套单纯的加密算法,而是融入用户的ID主键编号。
所以,如果有人直接进入数据库,插入一条用户数据从其他账户复制一个密码过来,密文一样,但是登录不了,因为每个用户的主键id
不一样,则对应的密文不一样,必须有算法才能加密,不然的话及时连接上数据库也没有用。这是其一。
其二,密码是自带密文校验功能的。就像前面所说的如身份证最后一位就是用来校验身份证号码的真实性,
其三,密文内部有版本号。如果密码被破解,我们随时可升级密码,并且站内可兼容多版本密文识别。 这是SafePassword。
再来我们说一下Hardware:
Hardware叫“硬加密”,这里设计的也很有意思,通常用于做数据库连接字符串的加密,本身这个没什么特别。加密一下,程序中去解密。
然后我们读取了服务器CPU的序列号做为加密种子,也就是说如果能拿到这个项目的所有文件,首先看到数据库连接字符串是加密的,
即便有解密程序集,是解不开的。需要项目所在的部署服务器的cpu序列号作为key才能解密,而程序集中是读取的本机cpu序列号。
即便拿到整个密文,也破解了我们的数据库连接。
这里有人会说,那要是远程攻破服务器呢? 确实我们之前也吃过类似的亏,后期我们的服务器数据库服务器现在只开放数据库端口,而远程
必须要走内网,以Oralc举例,等于我们数据库服务器只开放了1521端口,本质上来说我们连1521的端口都没必要向外网开放。只是公司的业务
特殊,有操作通过程序员单独操作线上数据库的需求,所以对外开放了1521。
而应用服务器,只开放了80端口,所有服务器 都只能通过内网远程,不提供外网。(当然还有一台神秘的服务器是开放外网,作为跳板连接内网的。)
这里再说回Hardware,这就意味着我们所有的项目在部署的时候都需要单独通过加密工具群生成加密字符串,并且配置在程序中,而不是每个项目复制粘贴
就可以部署。
========================华丽的分割线====================
关于BLL & Tool就写到这里,有兴趣与我们交流Winner的可以加我们的QQ群261083244 或者扫描左侧二维码加群。