31、三层架构、AJAX+FormsAuthentication实现登陆

时间:2023-03-10 03:32:30
31、三层架构、AJAX+FormsAuthentication实现登陆

三层架构

前段时间公司要求修改一个网站,打开后我疯了,一层没有都是调用的DB接口,遍地的SQL语句,非常杂乱。

什么是三层架构?

三层架构是将整个项目划分为三个层次:表现层、业务逻辑层、数据访问层。目的为了高内聚低耦合思想。

三层结构

表现层(UI):接受用户请求,数据的返回呈现。

业务逻辑层(BLL ):用来处理业务逻辑,处理用户提交的数据。

数据访问层(DAL):用来与数据库进行交互,处理增、删、改、差。

实体类对象(Model):用来存储实体类。

三层关系

UI表现层:接收用户输入的数据,并传递给业务逻辑层。

BLL业务逻辑层:把接收到的数据传递给数据访问层,并返回结果给UI层。

DAL数据访问层:负责与数据库进行交互,并将结果返回给BLL层。

什么时候需要分层?

当项目过大的时候可以分层开发,这样可以每个人负责的不同,共同进行开发。如果一个项目非常小的话,独立开发可以不分层。

操作步骤:

1、创建结构

新增三个类库分别是:BLL、DAL、Model,并建立一个UI层(winform 或 webform 或 MVC项目)设置启动项为UI层。

31、三层架构、AJAX+FormsAuthentication实现登陆

这里随个人喜欢可以再加一个Common层,负责处理其他常用方法。

31、三层架构、AJAX+FormsAuthentication实现登陆

2、引用关系

DAL要用到Model,BLL要用到DAL、CL和Model,UI要用到BLL、Model和CL。相互引用即可。

31、三层架构、AJAX+FormsAuthentication实现登陆

实现登陆

登陆是所有系统的入口,相信大家一般都用session,本例使用FormsAuthentication微软提供的身份认证,存储在cookie中。

假设有如下管理员表:

31、三层架构、AJAX+FormsAuthentication实现登陆

mif_id:唯一标识,lever等级,usernmae,psw账号密码,trueName姓名,createTime创建日期,isLock是否锁定,Power权限。

编写实体类

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text; namespace Model
{
/// <summary>
/// 管理员实体类
/// </summary>
public class ManagerInfo
{
/// <summary>
/// 标识ID
/// </summary>
public int mif_id { get; set; }
/// <summary>
/// 暂时没用
/// </summary>
public int mif_pid { get; set; }
/// <summary>
/// 管理员等级
/// </summary>
public int mif_lever { get; set; }
/// <summary>
/// 账号
/// </summary>
public string mif_userName { get; set; }
/// <summary>
/// 密码
/// </summary>
public string mif_psw { get; set; }
/// <summary>
/// 姓名
/// </summary>
public string mif_trueName { get; set; }
/// <summary>
/// 创建时间
/// </summary>
public DateTime mif_createTime { get; set; }
/// <summary>
/// 是否锁定
/// </summary>
public bool mif_isLock { get; set; }
/// <summary>
/// 权限
/// </summary>
public string mif_power { get; set; }
}
}

ManagerInfo.cs

习惯性编写完Model重新生成下Model层,检查是否有错误。

编写数据访问层

引入sqlHelper文件(上一文章中找),修改UI中的web.config添加数据库连接字符串,并在sqlHelper中修改相关名称,注意引入System.configuration.dll。

编写操作ManagerInfo的数据访问类

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Model;
using System.Data.SqlClient; namespace DAL
{
/// <summary>
/// 管理员数据访问类
/// </summary>
public class ManagerInfoDal
{
/// <summary>
/// 通过账号查找
/// </summary>
/// <param name="userName"></param>
/// <returns></returns>
public static ManagerInfo row(string userName)
{
string sql = "select mif_id,mif_lever,mif_userName,mif_psw,mif_trueName,mif_createTime,mif_isLock from managerInfo where mif_userName = @uid";
var v = new ManagerInfo();
using (var dr = SQLHelper.ExecuteReader(sql, new SqlParameter("@uid", userName)))
{
if (dr.Read())
{
v.mif_id = Convert.ToInt32(dr["mif_id"]);
v.mif_lever = Convert.ToInt32(dr["mif_lever"]);
v.mif_userName = dr["mif_userName"].ToString();
v.mif_psw = dr["mif_psw"].ToString();
v.mif_trueName = dr["mif_trueName"].ToString();
v.mif_createTime = Convert.ToDateTime(dr["mif_createTime"]);
v.mif_isLock = Convert.ToBoolean(dr["mif_isLock"]);
}
}
return v;
}
}
}

ManagerInfoDal.cs

编写业务逻辑层

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DAL;
using System.Web.Security;
using System.Web;
using CL;
using Model; namespace BLL
{
/// <summary>
/// 管理员
/// </summary>
public class ManagerInfoBll
{
/// <summary>
/// 登陆
/// </summary>
/// <param name="userName"></param>
/// <param name="passWord"></param>
/// <param name="remember">是否记住</param>
/// <returns></returns>
public static string login(string userName, string passWord, string remember)
{
var v = ManagerInfoDal.row(userName);
if (v.mif_id > )
{
if (v.mif_isLock == false)
{
if (v.mif_psw.Equals(createMD5.getMD5(passWord)))
{
var expires = DateTime.Now.AddMinutes();
if (remember.Equals("on"))
{
expires = DateTime.Now.AddDays();
}
//将登陆的用户存储在Ticket中
var ticket = new FormsAuthenticationTicket(, userName, DateTime.Now, expires, true, userName);
//使用Encrypt方法加密Ticket,并存储在cookie中
var cookie = new HttpCookie(FormsAuthentication.FormsCookieName, FormsAuthentication.Encrypt(ticket));
//防止浏览器攻击窃取、伪造cookie信息
cookie.HttpOnly = true;
cookie.Expires = expires;
HttpContext.Current.Response.Cookies.Add(cookie);
return "";
}
return "";
}
return "";
}
return "";
}
public static ManagerInfo row(string userName)
{
return ManagerInfoDal.row(userName);
}
/// <summary>
/// 查询权限(通过数字查询名字)
/// </summary>
/// <param name="userRole">权限数字</param>
/// <returns></returns>
public static string getRole(int userRole)
{
switch (userRole)
{
case :
return "编辑";
case :
return "管理员";
case :
return "系统";
default:
return "暂无";
}
}
}
}

ManagerInfoBll.cs

管理员等级这里建议写成枚举,本人较懒。

FormsAuthenticationTicket这个东西比传统的session和cookie的好处就是 可以任意目录设置他的访问权限,如果没有登陆直接跳出到登陆页面。而不用每个页面一开始都认证一番~

还有 cookie.HttpOnly 在使用cookie时这一项最好设置一下,否则可能会客户端模拟cookie进行攻击。

编写通用层

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text; namespace CL
{
public class createMD5
{
public static string getMD5(string str)
{
var md5 = MD5.Create();
var buffer = Encoding.Default.GetBytes(str);
var mdbuffer = md5.ComputeHash(buffer);
StringBuilder result = new StringBuilder();
for (int i = ; i < mdbuffer.Length; i++)
{
result.Append(mdbuffer[i].ToString("x2"));
}
return result.ToString();
}
}
}

createMD5

生成MD5的代码,可不用。

 using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Text;
using System.Web; namespace CL
{
public static class createVlidate
{
/// <summary>
/// 字符
/// </summary>
/// <param name="len">几位</param>
/// <returns></returns>
public static string validation(int cd)
{
var ran = new Random();
int num, tem;
string rtuStr = "";
for (int i = ; i < cd; i++)
{
num = ran.Next();
if (i % == )
tem = num % + ''; //数字
else
tem = num % + 'A'; //字母
rtuStr += Convert.ToChar(tem).ToString();
}
//写入cookie
HttpCookie cookie = new HttpCookie("check");
cookie.Value = rtuStr.ToLower();
HttpContext.Current.Response.Cookies.Add(cookie);
return rtuStr;
} /// <summary>
/// 生成图像
/// </summary>
/// <param name="check">字符</param>
public static byte[] drawImg(string check)
{
Bitmap img = new Bitmap(, );
var ht = Graphics.FromImage(img);
ht.Clear(Color.White);
ht.DrawLine(new Pen(Color.SpringGreen), , , , );
Font font = new Font("微软雅黑", , FontStyle.Bold);
var jianbian = new LinearGradientBrush(new Rectangle(, , img.Width, img.Height), Color.Teal, Color.Snow, 2f, true);
ht.DrawString(check, font, jianbian, , );
ht.DrawRectangle(new Pen(Color.Aqua), , , img.Width - , img.Height - );
MemoryStream ms = new MemoryStream();
img.Save(ms, ImageFormat.Jpeg);
ht.Dispose();
img.Dispose();
return ms.ToArray();
}
}
}

createValidate.cs

生成验证码的,可自己写。

编写展现层

首先需要在web.config中设置authentication节点为Forms验证模式,然后就可以在目录中任意设置访问级别了。

<authentication mode="Forms">
<forms loginUrl="~/manage/index.html" timeout="" defaultUrl="~/manage/Net/" />
</authentication>

登陆页面代码

 <!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,Chrome=1" />
<meta http-equiv="X-UA-Compatible" content="IE=9" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>汉之殇管理系统</title>
<link href="css/bootstrap.css" rel="stylesheet" />
<link href="css/admin.css" rel="stylesheet" />
<!--[if lt IE 9]>
<script src="http://apps.bdimg.com/libs/html5shiv/3.7/html5shiv.min.js"></script>
<script src="http://apps.bdimg.com/libs/respond.js/1.4.2/respond.min.js"></script>
<![endif]-->
</head>
<body class="login">
<nav class="navbar navbar-default navbar-static-top">
<div class="container">
<div class="navbar-header">
<a href="login.html" class="navbar-brand">汉之殇管理系统</a>
</div>
</div>
</nav>
<div class="container">
<div class="panel panel-default">
<div class="panel-body">
<div id="ts"></div>
<h4 class="page-header">登陆</h4>
<div class="form-group has-feedback">
<label class="sr-only" for="userName">账号</label>
<input type="text" id="userName" class="form-control" placeholder="账号" maxlength="50" autofocus />
<span class="glyphicon glyphicon-user form-control-feedback" aria-hidden="true"></span>
</div>
<div class="form-group has-feedback">
<label class="sr-only" for="passWord">密码</label>
<input type="password" id="passWord" class="form-control" maxlength="50" placeholder="密码" />
<span class="glyphicon glyphicon-lock form-control-feedback" aria-hidden="true"></span>
</div>
<div class="form-group has-feedback">
<label class="sr-only" for="validateCode">验证码</label>
<input type="text" id="validateCode" class="form-control validateCode" placeholder="验证码" maxlength="4" />
<img src="checkLogin/ValidateCode.ashx" id="img" onclick="changeCode()" class="validateImg">
<a href="javascript:changeCode()">看不清,换一张</a>
</div>
<div class="form-group">
<input type="checkbox" id="remember" checked="checked" /> <span class="form-control-static">记住我 </span>
<button id="submit" type="button" class="btn btn-primary col-xs-offset-4" style="width:40%">登录</button>
</div>
</div>
</div>
</div>
<nav class="navbar navbar-default navbar-fixed-bottom">
<div class="container">
<div class="navbar-header">
<p class="navbar-text">&copy; 2015 汉之殇 版权所有</p>
</div>
</div>
</nav>
<script src="js/jquery-1.7.2.min.js"></script>
<script src="js/status.js"></script>
<script src="js/login.js"></script>
</body>
</html>

index.html

验证码

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using CL; namespace UI.manage.checkLogin
{
/// <summary>
/// ValidateCode 的摘要说明
/// </summary>
public class ValidateCode : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
context.Response.ContentType = "image/jpeg"; var check = createVlidate.validation();
byte[] buffer = createVlidate.drawImg(check); context.Response.BinaryWrite(buffer);
} public bool IsReusable
{
get
{
return false;
}
}
}
}

ValidateCode.ashx

登陆用的JS

 $(function () {
$.get("checkLogin/Validate.ashx?rand=" + Math.random(0, 1), function (data) {
if (data == "0") {
location.href="/manage/Net/"
}
}); if (top.location != self.location) {
top.location = self.location;
} var lj = window.location.toString();
if (lj.lastIndexOf("?") != -1) {
status("info", "请先登录!");
$("#userName").focus();
} $(".login").height(document.documentElement.clientHeight);
document.onkeydown = function (e) {
var ev = document.all ? window.event : e;
if (ev.keyCode == 13) {
$("#submit").click();
}
}
});
$("#submit").click(function () {
//$("#submit").attr("disabled", "true");
var userName = $("#userName").val();
var passWord = $("#passWord").val();
var validateCode = $("#validateCode").val();
var remember = $("#remember:checked").val();
if (userName != "") {
if (passWord != "") {
if (validateCode != "") {
if (validateCode.toLowerCase() == getCode()) {
$.post("checkLogin/Validate.ashx", { userName: userName, passWord: passWord, remember: remember }, function (data) {
changeCode();
if (data == "0") {
location.href = "Net/";
} else if (data == "1") {
status("no", "登陆失败,密码错误!");
$("#passWord").focus();
return false;
} else if (data == "2") {
status("no", "登陆失败,账号已被禁止登陆!");
$("#userName").focus();
return false;
} else {
status("no", "登陆失败,账号不存在!");
$("#userName").focus();
return false;
}
});
return false;
}
status("no", "验证码不正确!");
$("#validateCode").focus();
return false;
}
status("info", "请输入验证码!");
$("#validateCode").focus();
return false;
}
status("info", "请输入您的密码!");
$("#passWord").focus();
return false;
}
status("info", "请输入您的账号!");
$("#userName").focus();
return false;
});
function changeCode() {
$("#img").attr("src", $("#img").attr("src") + "?");
}
function getCode() {
var cookies = document.cookie.split(";");
for (var i = 0; i < cookies.length; i++) {
var validate = cookies[i].split("=");
if (validate[0].replace(/(^\s*)|(\s*$)/g, "") == "check") {
return validate[1].replace(/(^\s*)|(\s*$)/g, "");
}
}
}

login.js

验证的一般处理程序

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Web; namespace UI.manage.checkLogin
{
/// <summary>
/// Validate 的摘要说明
/// </summary>
public class Validate : IHttpHandler
{ public void ProcessRequest(HttpContext context)
{
var userName = context.Request["userName"];
var passWord = context.Request["passWord"];
var remember = context.Request["remember"] == null ? "" : context.Request["remember"];
if (!string.IsNullOrEmpty(userName) && !string.IsNullOrEmpty(passWord))
{
var result = BLL.ManagerInfoBll.login(userName, passWord, remember);
context.Response.Write(result);
}
else
{
if (context.Request.IsAuthenticated)
{
context.Response.Write("");
}
}
} public bool IsReusable
{
get
{
return false;
}
}
}
}

Validate.ashx

最后附上效果图如下:

31、三层架构、AJAX+FormsAuthentication实现登陆

登陆后使用 Page.User.Identity.Name 获取用户标识。

如下:

31、三层架构、AJAX+FormsAuthentication实现登陆

获取信息及退出登陆如下:

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using BLL;
using Model; namespace UI.manage.Net
{
public partial class _default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
var userName = Page.User.Identity.Name;
int roleId = ManagerInfoBll.row(userName).mif_lever; this.userId.InnerText = userName;
this.userRole.InnerText = ManagerInfoBll.getRole(roleId);
} var logout = Request["logout"];
if (!string.IsNullOrEmpty(logout))
{
FormsAuthentication.SignOut();
Response.Redirect("../index.html");
}
}
}
}

default.aspx.cs

最后 在禁止匿名访问的目录下 新增一个web.config 内容如下

<?xml version="1.0"?>
<configuration>
<system.web>
<authorization>
<deny users="?"/>
</authorization>
</system.web>
</configuration>

这样,当记住凭证后直接访问登陆或者该目录都可以直接跳转,如果点击退出或过期后,则自动跳出到登陆页面中。至此大功告成~