三层架构
前段时间公司要求修改一个网站,打开后我疯了,一层没有都是调用的DB接口,遍地的SQL语句,非常杂乱。
什么是三层架构?
三层架构是将整个项目划分为三个层次:表现层、业务逻辑层、数据访问层。目的为了高内聚低耦合思想。
三层结构
表现层(UI):接受用户请求,数据的返回呈现。
业务逻辑层(BLL ):用来处理业务逻辑,处理用户提交的数据。
数据访问层(DAL):用来与数据库进行交互,处理增、删、改、差。
实体类对象(Model):用来存储实体类。
三层关系
UI表现层:接收用户输入的数据,并传递给业务逻辑层。
BLL业务逻辑层:把接收到的数据传递给数据访问层,并返回结果给UI层。
DAL数据访问层:负责与数据库进行交互,并将结果返回给BLL层。
什么时候需要分层?
当项目过大的时候可以分层开发,这样可以每个人负责的不同,共同进行开发。如果一个项目非常小的话,独立开发可以不分层。
操作步骤:
1、创建结构
新增三个类库分别是:BLL、DAL、Model,并建立一个UI层(winform 或 webform 或 MVC项目)设置启动项为UI层。
这里随个人喜欢可以再加一个Common层,负责处理其他常用方法。
2、引用关系
DAL要用到Model,BLL要用到DAL、CL和Model,UI要用到BLL、Model和CL。相互引用即可。
实现登陆
登陆是所有系统的入口,相信大家一般都用session,本例使用FormsAuthentication微软提供的身份认证,存储在cookie中。
假设有如下管理员表:
mif_id:唯一标识,lever等级,usernmae,psw账号密码,trueName姓名,createTime创建日期,isLock是否锁定,Power权限。
编写实体类
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 6 namespace Model 7 { 8 /// <summary> 9 /// 管理员实体类 10 /// </summary> 11 public class ManagerInfo 12 { 13 /// <summary> 14 /// 标识ID 15 /// </summary> 16 public int mif_id { get; set; } 17 /// <summary> 18 /// 暂时没用 19 /// </summary> 20 public int mif_pid { get; set; } 21 /// <summary> 22 /// 管理员等级 23 /// </summary> 24 public int mif_lever { get; set; } 25 /// <summary> 26 /// 账号 27 /// </summary> 28 public string mif_userName { get; set; } 29 /// <summary> 30 /// 密码 31 /// </summary> 32 public string mif_psw { get; set; } 33 /// <summary> 34 /// 姓名 35 /// </summary> 36 public string mif_trueName { get; set; } 37 /// <summary> 38 /// 创建时间 39 /// </summary> 40 public DateTime mif_createTime { get; set; } 41 /// <summary> 42 /// 是否锁定 43 /// </summary> 44 public bool mif_isLock { get; set; } 45 /// <summary> 46 /// 权限 47 /// </summary> 48 public string mif_power { get; set; } 49 } 50 }
ManagerInfo.cs
习惯性编写完Model重新生成下Model层,检查是否有错误。
编写数据访问层
引入sqlHelper文件(上一文章中找),修改UI中的web.config添加数据库连接字符串,并在sqlHelper中修改相关名称,注意引入System.configuration.dll。
编写操作ManagerInfo的数据访问类
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using Model; 6 using System.Data.SqlClient; 7 8 namespace DAL 9 { 10 /// <summary> 11 /// 管理员数据访问类 12 /// </summary> 13 public class ManagerInfoDal 14 { 15 /// <summary> 16 /// 通过账号查找 17 /// </summary> 18 /// <param name="userName"></param> 19 /// <returns></returns> 20 public static ManagerInfo row(string userName) 21 { 22 string sql = "select mif_id,mif_lever,mif_userName,mif_psw,mif_trueName,mif_createTime,mif_isLock from managerInfo where mif_userName = @uid"; 23 var v = new ManagerInfo(); 24 using (var dr = SQLHelper.ExecuteReader(sql, new SqlParameter("@uid", userName))) 25 { 26 if (dr.Read()) 27 { 28 v.mif_id = Convert.ToInt32(dr["mif_id"]); 29 v.mif_lever = Convert.ToInt32(dr["mif_lever"]); 30 v.mif_userName = dr["mif_userName"].ToString(); 31 v.mif_psw = dr["mif_psw"].ToString(); 32 v.mif_trueName = dr["mif_trueName"].ToString(); 33 v.mif_createTime = Convert.ToDateTime(dr["mif_createTime"]); 34 v.mif_isLock = Convert.ToBoolean(dr["mif_isLock"]); 35 } 36 } 37 return v; 38 } 39 } 40 }
ManagerInfoDal.cs
编写业务逻辑层
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using DAL; 6 using System.Web.Security; 7 using System.Web; 8 using CL; 9 using Model; 10 11 namespace BLL 12 { 13 /// <summary> 14 /// 管理员 15 /// </summary> 16 public class ManagerInfoBll 17 { 18 /// <summary> 19 /// 登陆 20 /// </summary> 21 /// <param name="userName"></param> 22 /// <param name="passWord"></param> 23 /// <param name="remember">是否记住</param> 24 /// <returns></returns> 25 public static string login(string userName, string passWord, string remember) 26 { 27 var v = ManagerInfoDal.row(userName); 28 if (v.mif_id > 0) 29 { 30 if (v.mif_isLock == false) 31 { 32 if (v.mif_psw.Equals(createMD5.getMD5(passWord))) 33 { 34 var expires = DateTime.Now.AddMinutes(20); 35 if (remember.Equals("on")) 36 { 37 expires = DateTime.Now.AddDays(8); 38 } 39 //将登陆的用户存储在Ticket中 40 var ticket = new FormsAuthenticationTicket(0, userName, DateTime.Now, expires, true, userName); 41 //使用Encrypt方法加密Ticket,并存储在cookie中 42 var cookie = new HttpCookie(FormsAuthentication.FormsCookieName, FormsAuthentication.Encrypt(ticket)); 43 //防止浏览器攻击窃取、伪造cookie信息 44 cookie.HttpOnly = true; 45 cookie.Expires = expires; 46 HttpContext.Current.Response.Cookies.Add(cookie); 47 return "0"; 48 } 49 return "1"; 50 } 51 return "2"; 52 } 53 return "3"; 54 } 55 public static ManagerInfo row(string userName) 56 { 57 return ManagerInfoDal.row(userName); 58 } 59 /// <summary> 60 /// 查询权限(通过数字查询名字) 61 /// </summary> 62 /// <param name="userRole">权限数字</param> 63 /// <returns></returns> 64 public static string getRole(int userRole) 65 { 66 switch (userRole) 67 { 68 case 0: 69 return "编辑"; 70 case 1: 71 return "管理员"; 72 case 2: 73 return "系统"; 74 default: 75 return "暂无"; 76 } 77 } 78 } 79 }
ManagerInfoBll.cs
管理员等级这里建议写成枚举,本人较懒。
FormsAuthenticationTicket这个东西比传统的session和cookie的好处就是 可以任意目录设置他的访问权限,如果没有登陆直接跳出到登陆页面。而不用每个页面一开始都认证一番~
还有 cookie.HttpOnly 在使用cookie时这一项最好设置一下,否则可能会客户端模拟cookie进行攻击。
编写通用层
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Security.Cryptography; 5 using System.Text; 6 7 namespace CL 8 { 9 public class createMD5 10 { 11 public static string getMD5(string str) 12 { 13 var md5 = MD5.Create(); 14 var buffer = Encoding.Default.GetBytes(str); 15 var mdbuffer = md5.ComputeHash(buffer); 16 StringBuilder result = new StringBuilder(); 17 for (int i = 0; i < mdbuffer.Length; i++) 18 { 19 result.Append(mdbuffer[i].ToString("x2")); 20 } 21 return result.ToString(); 22 } 23 } 24 }
createMD5
生成MD5的代码,可不用。
1 using System; 2 using System.Collections.Generic; 3 using System.Drawing; 4 using System.Drawing.Drawing2D; 5 using System.Drawing.Imaging; 6 using System.IO; 7 using System.Linq; 8 using System.Text; 9 using System.Web; 10 11 namespace CL 12 { 13 public static class createVlidate 14 { 15 /// <summary> 16 /// 字符 17 /// </summary> 18 /// <param name="len">几位</param> 19 /// <returns></returns> 20 public static string validation(int cd) 21 { 22 var ran = new Random(); 23 int num, tem; 24 string rtuStr = ""; 25 for (int i = 0; i < cd; i++) 26 { 27 num = ran.Next(); 28 if (i % 2 == 1) 29 tem = num % 10 + ‘0‘; //数字 30 else 31 tem = num % 26 + ‘A‘; //字母 32 rtuStr += Convert.ToChar(tem).ToString(); 33 } 34 //写入cookie 35 HttpCookie cookie = new HttpCookie("check"); 36 cookie.Value = rtuStr.ToLower(); 37 HttpContext.Current.Response.Cookies.Add(cookie); 38 return rtuStr; 39 } 40 41 /// <summary> 42 /// 生成图像 43 /// </summary> 44 /// <param name="check">字符</param> 45 public static byte[] drawImg(string check) 46 { 47 Bitmap img = new Bitmap(90, 34); 48 var ht = Graphics.FromImage(img); 49 ht.Clear(Color.White); 50 ht.DrawLine(new Pen(Color.SpringGreen), 1, 1, 90, 34); 51 Font font = new Font("微软雅黑", 20, FontStyle.Bold); 52 var jianbian = new LinearGradientBrush(new Rectangle(0, 0, img.Width, img.Height), Color.Teal, Color.Snow, 2f, true); 53 ht.DrawString(check, font, jianbian, 0, 0); 54 ht.DrawRectangle(new Pen(Color.Aqua), 0, 0, img.Width - 1, img.Height - 1); 55 MemoryStream ms = new MemoryStream(); 56 img.Save(ms, ImageFormat.Jpeg); 57 ht.Dispose(); 58 img.Dispose(); 59 return ms.ToArray(); 60 } 61 } 62 }
createValidate.cs
生成验证码的,可自己写。
编写展现层
首先需要在web.config中设置authentication节点为Forms验证模式,然后就可以在目录中任意设置访问级别了。
<authentication mode="Forms"> <forms loginUrl="~/manage/index.html" timeout="10080" defaultUrl="~/manage/Net/" /> </authentication>
登陆页面代码
1 <!DOCTYPE html> 2 <html xmlns="http://www.w3.org/1999/xhtml"> 3 <head> 4 <meta charset="utf-8" /> 5 <meta http-equiv="X-UA-Compatible" content="IE=edge,Chrome=1" /> 6 <meta http-equiv="X-UA-Compatible" content="IE=9" /> 7 <meta name="viewport" content="width=device-width, initial-scale=1"> 8 <title>汉之殇管理系统</title> 9 <link href="css/bootstrap.css" rel="stylesheet" /> 10 <link href="css/admin.css" rel="stylesheet" /> 11 <!--[if lt IE 9]> 12 <script src="http://apps.bdimg.com/libs/html5shiv/3.7/html5shiv.min.js"></script> 13 <script src="http://apps.bdimg.com/libs/respond.js/1.4.2/respond.min.js"></script> 14 <![endif]--> 15 </head> 16 <body class="login"> 17 <nav class="navbar navbar-default navbar-static-top"> 18 <div class="container"> 19 <div class="navbar-header"> 20 <a href="login.html" class="navbar-brand">汉之殇管理系统</a> 21 </div> 22 </div> 23 </nav> 24 <div class="container"> 25 <div class="panel panel-default"> 26 <div class="panel-body"> 27 <div id="ts"></div> 28 <h4 class="page-header">登陆</h4> 29 <div class="form-group has-feedback"> 30 <label class="sr-only" for="userName">账号</label> 31 <input type="text" id="userName" class="form-control" placeholder="账号" maxlength="50" autofocus /> 32 <span class="glyphicon glyphicon-user form-control-feedback" aria-hidden="true"></span> 33 </div> 34 <div class="form-group has-feedback"> 35 <label class="sr-only" for="passWord">密码</label> 36 <input type="password" id="passWord" class="form-control" maxlength="50" placeholder="密码" /> 37 <span class="glyphicon glyphicon-lock form-control-feedback" aria-hidden="true"></span> 38 </div> 39 <div class="form-group has-feedback"> 40 <label class="sr-only" for="validateCode">验证码</label> 41 <input type="text" id="validateCode" class="form-control validateCode" placeholder="验证码" maxlength="4" /> 42 <img src="checkLogin/ValidateCode.ashx" id="img" onclick="changeCode()" class="validateImg"> 43 <a href="javascript:changeCode()">看不清,换一张</a> 44 </div> 45 <div class="form-group"> 46 <input type="checkbox" id="remember" checked="checked" /> <span class="form-control-static">记住我 </span> 47 <button id="submit" type="button" class="btn btn-primary col-xs-offset-4" style="width:40%">登录</button> 48 </div> 49 </div> 50 </div> 51 </div> 52 <nav class="navbar navbar-default navbar-fixed-bottom"> 53 <div class="container"> 54 <div class="navbar-header"> 55 <p class="navbar-text">© 2015 汉之殇 版权所有</p> 56 </div> 57 </div> 58 </nav> 59 <script src="js/jquery-1.7.2.min.js"></script> 60 <script src="js/status.js"></script> 61 <script src="js/login.js"></script> 62 </body> 63 </html>
index.html
验证码
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Web; 5 using CL; 6 7 namespace UI.manage.checkLogin 8 { 9 /// <summary> 10 /// ValidateCode 的摘要说明 11 /// </summary> 12 public class ValidateCode : IHttpHandler 13 { 14 public void ProcessRequest(HttpContext context) 15 { 16 context.Response.ContentType = "image/jpeg"; 17 18 var check = createVlidate.validation(4); 19 byte[] buffer = createVlidate.drawImg(check); 20 21 context.Response.BinaryWrite(buffer); 22 } 23 24 public bool IsReusable 25 { 26 get 27 { 28 return false; 29 } 30 } 31 } 32 }
ValidateCode.ashx
登陆用的JS
1 $(function () { 2 $.get("checkLogin/Validate.ashx?rand=" + Math.random(0, 1), function (data) { 3 if (data == "0") { 4 location.href="/manage/Net/" 5 } 6 }); 7 8 if (top.location != self.location) { 9 top.location = self.location; 10 } 11 12 var lj = window.location.toString(); 13 if (lj.lastIndexOf("?") != -1) { 14 status("info", "请先登录!"); 15 $("#userName").focus(); 16 } 17 18 $(".login").height(document.documentElement.clientHeight); 19 document.onkeydown = function (e) { 20 var ev = document.all ? window.event : e; 21 if (ev.keyCode == 13) { 22 $("#submit").click(); 23 } 24 } 25 }); 26 $("#submit").click(function () { 27 //$("#submit").attr("disabled", "true"); 28 var userName = $("#userName").val(); 29 var passWord = $("#passWord").val(); 30 var validateCode = $("#validateCode").val(); 31 var remember = $("#remember:checked").val(); 32 if (userName != "") { 33 if (passWord != "") { 34 if (validateCode != "") { 35 if (validateCode.toLowerCase() == getCode()) { 36 $.post("checkLogin/Validate.ashx", { userName: userName, passWord: passWord, remember: remember }, function (data) { 37 changeCode(); 38 if (data == "0") { 39 location.href = "Net/"; 40 } else if (data == "1") { 41 status("no", "登陆失败,密码错误!"); 42 $("#passWord").focus(); 43 return false; 44 } else if (data == "2") { 45 status("no", "登陆失败,账号已被禁止登陆!"); 46 $("#userName").focus(); 47 return false; 48 } else { 49 status("no", "登陆失败,账号不存在!"); 50 $("#userName").focus(); 51 return false; 52 } 53 }); 54 return false; 55 } 56 status("no", "验证码不正确!"); 57 $("#validateCode").focus(); 58 return false; 59 } 60 status("info", "请输入验证码!"); 61 $("#validateCode").focus(); 62 return false; 63 } 64 status("info", "请输入您的密码!"); 65 $("#passWord").focus(); 66 return false; 67 } 68 status("info", "请输入您的账号!"); 69 $("#userName").focus(); 70 return false; 71 }); 72 function changeCode() { 73 $("#img").attr("src", $("#img").attr("src") + "?"); 74 } 75 function getCode() { 76 var cookies = document.cookie.split(";"); 77 for (var i = 0; i < cookies.length; i++) { 78 var validate = cookies[i].split("="); 79 if (validate[0].replace(/(^\s*)|(\s*$)/g, "") == "check") { 80 return validate[1].replace(/(^\s*)|(\s*$)/g, ""); 81 } 82 } 83 }
login.js
验证的一般处理程序
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Web; 5 6 namespace UI.manage.checkLogin 7 { 8 /// <summary> 9 /// Validate 的摘要说明 10 /// </summary> 11 public class Validate : IHttpHandler 12 { 13 14 public void ProcessRequest(HttpContext context) 15 { 16 var userName = context.Request["userName"]; 17 var passWord = context.Request["passWord"]; 18 var remember = context.Request["remember"] == null ? "" : context.Request["remember"]; 19 if (!string.IsNullOrEmpty(userName) && !string.IsNullOrEmpty(passWord)) 20 { 21 var result = BLL.ManagerInfoBll.login(userName, passWord, remember); 22 context.Response.Write(result); 23 } 24 else 25 { 26 if (context.Request.IsAuthenticated) 27 { 28 context.Response.Write("0"); 29 } 30 } 31 } 32 33 public bool IsReusable 34 { 35 get 36 { 37 return false; 38 } 39 } 40 } 41 }
Validate.ashx
最后附上效果图如下:
登陆后使用 Page.User.Identity.Name 获取用户标识。
如下:
获取信息及退出登陆如下:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Web; 5 using System.Web.Security; 6 using System.Web.UI; 7 using System.Web.UI.WebControls; 8 using BLL; 9 using Model; 10 11 namespace UI.manage.Net 12 { 13 public partial class _default : System.Web.UI.Page 14 { 15 protected void Page_Load(object sender, EventArgs e) 16 { 17 if (!IsPostBack) 18 { 19 var userName = Page.User.Identity.Name; 20 int roleId = ManagerInfoBll.row(userName).mif_lever; 21 22 this.userId.InnerText = userName; 23 this.userRole.InnerText = ManagerInfoBll.getRole(roleId); 24 } 25 26 var logout = Request["logout"]; 27 if (!string.IsNullOrEmpty(logout)) 28 { 29 FormsAuthentication.SignOut(); 30 Response.Redirect("../index.html"); 31 } 32 } 33 } 34 }
default.aspx.cs
最后 在禁止匿名访问的目录下 新增一个web.config 内容如下
<?xml version="1.0"?> <configuration> <system.web> <authorization> <deny users="?"/> </authorization> </system.web> </configuration>
这样,当记住凭证后直接访问登陆或者该目录都可以直接跳转,如果点击退出或过期后,则自动跳出到登陆页面中。至此大功告成~