从零开始编写自己的C#框架(16)——Web层后端父类
本章节讲述的各个类是后端系统的核心之一,涉及到系统安全验证、操作日志记录、页面与按键权限控制、后端页面功能封装等内容,希望学习本系列的朋友认真查看新增的类与函数,这对以后使用本框架进行开发时非常重要。
1、父类(基类)的实现
在开发后端首页与相关功能页面前,我们首先要实现的是所有页面的基类(父类),将常用的功能都预先实现出来,而后面的相关UI类则直接继承它,这样就能简单的自动实现了相关页面功能,不用再每个页面去编写某些按键功能或其他一些功能,如果有特殊的需要,再重写对应的功能类就可以了,对于常用功能,由于之前的逻辑层与数据层已使用模板生成好了,所以直接调用,这样的话比如实现一个列表页面的一些功能(如下图),只需要简单的在页面控件使用指定名称,那么一些实现代码就不用再编写了,这些控件自动拥有对应的功能,比如刷新、自动排序、保存排序(直接修改下图中排序列的输入框后点击保存排序就可以了,这个功能不用编写任何一个代码,只需要将按键放到下图位置,然后使用指定名称就可以了)等功能。这样操作将使我们后面的开发工作更加轻松。而对于列表的话,也只需要调用逻辑层函数直接绑定(bll.BindGrid(this, Grid1, Grid1.PageIndex + 1, Grid1.PageSize, InquiryCondition(), _order);)就可以实现列表、分页、翻页、排序等功能。当然列表点击审核的√与×就会同步更改数据库对应记录的字段与图标,也只需要在列表控件对应函数复制进简单的几行代码就可以实现,这些会在后面相应章节中具体讲述。
先上父类与接口代码
1 using System; 2 using System.Collections.Generic; 3 using System.Web.UI; 4 using DotNet.Utilities; 5 using FineUI; 6 using Solution.Logic.Managers; 7 using Solution.Logic.Managers.Application; 8 9 namespace Solution.Web.Managers.WebManage.Application 10 { 11 /// <summary> 12 /// Web层页面父类 13 /// 本基类封装了各种常用函数,c减少重复代码的编写 14 /// </summary> 15 public abstract class PageBase : System.Web.UI.Page, IPageBase 16 { 17 #region 定义对象 18 //逻辑层接口对象 19 protected ILogicBase bll = null; 20 //定义列表对象 21 private FineUI.Grid grid = null; 22 //页面排序容器 23 List<string> sortList = null; 24 #endregion 25 26 #region 初始化函数 27 protected override void OnInit(EventArgs e) 28 { 29 base.OnInit(e); 30 31 //检测用户是否超时退出 32 OnlineUsersBll.GetInstence().IsTimeOut(); 33 34 if (!IsPostBack) 35 { 36 //检测当前页面是否有访问权限 37 MenuInfoBll.GetInstence().CheckPagePower(this); 38 39 #region 设置页面按键权限 40 //定义按键控件 41 Control btnControl = null; 42 try 43 { 44 //找到页面放置按键控件的位置 45 ControlCollection controls = MenuInfoBll.GetInstence().GetToolBarControls(this.Controls); 46 //逐个读取出来 47 for (int i = 0; i < controls.Count; i++) 48 { 49 //取出控件 50 btnControl = controls[i]; 51 //判断是否除了刷新、查询和关闭以外的按键 52 if (btnControl.ID != "ButtonRefresh" && btnControl.ID != "ButtonSearch" && btnControl.ID != "ButtonClose" && btnControl.ID != "btnReset") 53 { 54 //是的话检查该按键当前用户是否有控件权限,没有的话则禁用该按键 55 ((FineUI.Button)btnControl).Enabled = MenuInfoBll.GetInstence().CheckControlPower(this, btnControl.ID); 56 } 57 } 58 } 59 catch (Exception) { } 60 #endregion 61 62 //记录用户当前所在的页面位置 63 CommonBll.UserRecord(this); 64 } 65 66 //运行UI页面初始化函数,子类继承后需要重写本函数,以提供给本初始化函数调用 67 Init(); 68 } 69 #endregion 70 71 #region 接口函数,用于UI页面初始化,给逻辑层对象、列表等对象赋值 72 73 /// <summary> 74 /// 接口函数,用于UI页面初始化,给逻辑层对象、列表等对象赋值 75 /// </summary> 76 public abstract void Init(); 77 78 #endregion 79 80 #region 页面各种按键事件 81 82 /// <summary> 83 /// 刷新按钮事件 84 /// </summary> 85 /// <param name="sender"></param> 86 /// <param name="e"></param> 87 protected void ButtonRefresh_Click(object sender, EventArgs e) 88 { 89 FineUI.PageContext.RegisterStartupScript("window.location.reload()"); 90 } 91 92 /// <summary> 93 /// 关闭 94 /// </summary> 95 /// <param name="sender"></param> 96 /// <param name="e"></param> 97 protected void ButtonClose_Click(object sender, EventArgs e) 98 { 99 PageContext.RegisterStartupScript(ActiveWindow.GetHideRefreshReference()); 100 101 } 102 /// <summary> 103 /// 查询 104 /// </summary> 105 /// <param name="sender"></param> 106 /// <param name="e"></param> 107 protected void ButtonSearch_Click(object sender, EventArgs e) 108 { 109 LoadData(); 110 } 111 112 /// <summary> 113 /// 新增 114 /// </summary> 115 /// <param name="sender"></param> 116 /// <param name="e"></param> 117 protected void ButtonAdd_Click(object sender, EventArgs e) 118 { 119 Add(); 120 } 121 122 /// <summary> 123 /// 编辑 124 /// </summary> 125 /// <param name="sender"></param> 126 /// <param name="e"></param> 127 protected void ButtonEdit_Click(object sender, EventArgs e) 128 { 129 Edit(); 130 } 131 132 /// <summary> 133 /// 删除 134 /// </summary> 135 /// <param name="sender"></param> 136 /// <param name="e"></param> 137 protected void ButtonDelete_Click(object sender, EventArgs e) 138 { 139 //执行删除操作,返回删除结果 140 string result = Delete(); 141 142 if (string.IsNullOrEmpty(result)) 143 return; 144 //弹出提示框 145 FineUI.Alert.ShowInParent(result, FineUI.MessageBoxIcon.Information); 146 147 //重新加载页面表格 148 LoadData(); 149 } 150 151 152 /// <summary> 153 /// 保存数据 154 /// </summary> 155 /// <param name="sender"></param> 156 /// <param name="e"></param> 157 protected void ButtonSave_Click(object sender, EventArgs e) 158 { 159 //执行保存操作,返回保存结果 160 string result = Save(); 161 162 if (string.IsNullOrEmpty(result)) 163 { 164 PageContext.RegisterStartupScript(ActiveWindow.GetHidePostBackReference()); 165 FineUI.Alert.ShowInParent("保存成功", FineUI.MessageBoxIcon.Information); 166 } 167 else 168 { 169 //by july,部分页面保存后,必须刷新原页面的,把返回的值用 "{url}" + 跳转地址的方式传过来 170 if (StringHelper.Left(result, 5) == "{url}") 171 { 172 string url = result.Trim().Substring(6); 173 FineUI.Alert.ShowInParent("保存成功", "", FineUI.MessageBoxIcon.Information, "self.location='" + url + "'"); 174 } 175 else 176 { 177 FineUI.Alert.ShowInParent(result, FineUI.MessageBoxIcon.Information); 178 } 179 } 180 } 181 182 /// <summary>保存排序</summary> 183 /// <param name="sender"></param> 184 /// <param name="e"></param> 185 protected void ButtonSaveSort_Click(object sender, EventArgs e) 186 { 187 SaveSort(); 188 } 189 190 /// <summary>自动排序</summary> 191 /// <param name="sender"></param> 192 /// <param name="e"></param> 193 protected void ButtonSaveAutoSort_Click(object sender, EventArgs e) 194 { 195 //默认使用多级分类 196 SaveAutoSort(); 197 } 198 199 /// <summary> 200 /// 排序 201 /// </summary> 202 /// <param name="sender"></param> 203 /// <param name="e"></param> 204 protected void Grid1_Sort(object sender, FineUI.GridSortEventArgs e) 205 { 206 //生成排序关键字 207 Sort(e); 208 //刷新列表 209 LoadData(); 210 } 211 212 /// <summary> 213 /// 分页 214 /// </summary> 215 /// <param name="sender"></param> 216 /// <param name="e"></param> 217 protected void Grid1_PageIndexChange(object sender, FineUI.GridPageEventArgs e) 218 { 219 if (grid != null) 220 { 221 grid.PageIndex = e.NewPageIndex; 222 223 LoadData(); 224 } 225 } 226 227 /// <summary> 228 /// 关闭子窗口事件 229 /// </summary> 230 /// <param name="sender"></param> 231 /// <param name="e"></param> 232 protected virtual void Window1_Close(object sender, WindowCloseEventArgs e) 233 { 234 LoadData(); 235 } 236 237 /// <summary> 238 /// 关闭子窗口事件 239 /// </summary> 240 /// <param name="sender"></param> 241 /// <param name="e"></param> 242 protected virtual void Window2_Close(object sender, WindowCloseEventArgs e) 243 { 244 LoadData(); 245 } 246 247 /// <summary> 248 /// 关闭子窗口事件 249 /// </summary> 250 /// <param name="sender"></param> 251 /// <param name="e"></param> 252 protected virtual void Window3_Close(object sender, WindowCloseEventArgs e) 253 { 254 LoadData(); 255 } 256 #endregion 257 258 #region 虚函数,主要给页面各种按键事件调用,子类需要使用到相关功能时必须将它实现 259 260 /// <summary> 261 /// 加载事件 262 /// </summary> 263 public virtual void LoadData() { } 264 265 /// <summary> 266 /// 添加记录 267 /// </summary> 268 public virtual void Add() { } 269 270 /// <summary> 271 /// 修改记录 272 /// </summary> 273 public virtual void Edit() { } 274 275 /// <summary> 276 /// 删除记录 277 /// </summary> 278 /// <returns>返回删除结果</returns> 279 public virtual string Delete() 280 { 281 return null; 282 } 283 284 /// <summary> 285 /// 保存数据 286 /// </summary> 287 /// <returns>返回保存结果</returns> 288 public virtual string Save() 289 { 290 return ""; 291 } 292 293 /// <summary> 294 /// 保存排序 295 /// </summary> 296 /// <returns>返回保存结果</returns> 297 public virtual void SaveSort() 298 { 299 //保存排序 300 if (grid != null && bll != null) 301 { 302 //更新排序 303 if (bll.UpdateSort(this, grid, "tbxOrderID")) 304 { 305 //重新加载列表 306 LoadData(); 307 308 Alert.ShowInParent("操作成功", "保存排序成功", "window.location.reload();"); 309 } 310 else 311 { 312 Alert.ShowInParent("操作成失败", "保存排序失败", "window.location.reload();"); 313 } 314 } 315 } 316 317 /// <summary> 318 /// 保存自动排序 319 /// </summary> 320 public virtual void SaveAutoSort() 321 { 322 if (bll == null) 323 { 324 Alert.ShowInParent("保存失败", "逻辑层对象为null,请联系开发人员给当前页面的逻辑层对象赋值"); 325 return; 326 } 327 328 if (bll.UpdateAutoSort(this, "", true)) 329 { 330 Alert.ShowInParent("保存成功", "保存自动排序成功", "window.location.reload();"); 331 } 332 else 333 { 334 Alert.ShowInParent("保存失败", "保存自动排序失败", "window.location.reload();"); 335 } 336 } 337 338 /// <summary> 339 /// 生成排序关键字 340 /// </summary> 341 /// <param name="e"></param> 342 public virtual void Sort(FineUI.GridSortEventArgs e) 343 { 344 //处理排序 345 sortList = null; 346 sortList = new List<string>(); 347 //排序列字段名称 348 string sortName = ""; 349 350 if (e.SortField.Length > 0) 351 { 352 //判断是升序还是降序 353 if (e.SortDirection != null && e.SortDirection.ToUpper() == "DESC") 354 { 355 sortList.Add(e.SortField + " desc"); 356 } 357 else 358 { 359 sortList.Add(e.SortField + " asc"); 360 } 361 sortName = e.SortField; 362 } 363 else 364 { 365 //使用默认排序——主键列降序排序 366 sortList.Add("Id desc"); 367 sortName = "Id"; 368 } 369 370 //利用反射的方式给页面控件赋值 371 //查找指定名称控件 372 var control = Page.FindControl("SortColumn"); 373 if (control != null) 374 { 375 //判断是否是TextBox类型 376 var type = control.GetType(); 377 if (type.FullName == "System.Web.UI.WebControls.TextBox") 378 { 379 //存储排序列字段名称 380 ((TextBox)control).Text = sortName; 381 } 382 } 383 } 384 385 #endregion 386 387 } 388 }
1 namespace Solution.Web.Managers.WebManage.Application 2 { 3 /// <summary> 4 /// UI页面接口类——主要用于列表(Grid)页 5 /// </summary> 6 public interface IPageBase 7 { 8 #region 用于UI页面初始化,给逻辑层对象、列表等对象赋值,主要是列表(Grid)页面使用 9 10 void Init(); 11 12 #endregion 13 } 14 }
有朋友可能会有疑问,为什么本类要用abstract修饰成抽象类,而实现类中只有接口Init()函数是抽象函数,这样设置主要是强制要求Init()函数在子类中必须实现,因为在开发过程中,不这样强制的话,一些页面开发时很容易忘记去给父类的相关对象赋值,那么父类中的一些功能在调用时就无法正常运行。
代码中的OnInit()函数,在页面启动的时候会调用到,主要用于检查用户登陆情况,用户是否有当前页面的访问权限和页面按键的使用权限,记录用户访问记录,以及运行UI页面初始化函数,方便父类相关函数功能的调用(比如保存排序、分页等功能)。
另外,预先定义好了页面各种常用按键事件,只要在页面里放置这些按键,就可以自动调用这些事件功能,部分按键功能已实现的就不用再编写代码,未实现的则直接重写对应的虚函数,就可以达到想要的效果。
而虚函数,大多都没有实现其功能的代码,可能有朋友会说,为什么不用接口呢?呃......在这里再重新说明一下,定义成接口的话就必须要去实现,而对于多数页面来说,并不一定要用到这个功能,那么这些页面代码看起来就很罗嗦,充斥大量无用代码,可读性就会非常的差,而用虚方法只是放在那里就可以了,需要使用时才去重写,这样操作会比较灵活,页面代码看起来也非常整洁干净。
由于下面不少功能都是重新沟思后编写的,所以可能会存在一些不合理的地方,且未经过运行测试,以后会根据情况进行增删。
2、逻辑层添加了接口,且抽象类实现这个接口并增加对应的抽象函数
为了方便上面父类的调用,减少重复代码的编写,在逻辑层增加了接口类ILogicBase,而原来的逻辑层抽象类LogicBase(所有模板都必须继承的类)也实现了该接口
1 using System.Collections.Generic; 2 using System.Web.UI; 3 using Solution.DataAccess.DbHelper; 4 5 namespace Solution.Logic.Managers.Application 6 { 7 public interface ILogicBase 8 { 9 #region 绑定表格 10 11 void BindGrid(FineUI.Grid grid, int pageIndex = 0, int pageSize = 0, 12 List<ConditionFun.SqlqueryCondition> wheres = null, List<string> orders = null); 13 14 void BindGrid(FineUI.Grid grid, int parentValue, 15 List<ConditionFun.SqlqueryCondition> wheres = null, List<string> orders = null, string parentId = "ParentId"); 16 17 void BindGrid(FineUI.Grid grid, int parentValue, List<string> orders = null, 18 string parentId = "ParentId"); 19 20 #endregion 21 22 #region 删除记录 23 24 void Delete(Page page, int id); 25 26 void Delete(Page page, int[] id); 27 28 #endregion 29 30 #region 保存排序 31 32 bool UpdateSort(Page page, FineUI.Grid grid1, string tbxOrderId, string orderIdName = "OrderId"); 33 34 #endregion 35 36 #region 自动排序 37 38 bool UpdateAutoSort(Page page, string strWhere = "", bool isExistsMoreLv = false, int pid = 0, 39 string fieldName = "OrderId", string fieldParentId = "ParentId"); 40 41 #endregion 42 } 43 }
1 using System; 2 using System.Collections.Generic; 3 using System.Linq.Expressions; 4 using System.Web.UI; 5 using FineUI; 6 using Solution.DataAccess.DbHelper; 7 using Solution.Logic.Managers.Application; 8 9 namespace Solution.Logic.Managers 10 { 11 /// <summary> 12 /// 逻辑层抽象类 13 /// </summary> 14 public abstract class LogicBase : ILogicBase 15 { 16 #region 清空缓存 17 /// <summary>清空缓存</summary> 18 public virtual void DelCache() 19 { 20 21 } 22 #endregion 23 24 #region 全表缓存加载条件 25 /// <summary> 26 /// 全表缓存加载条件——对于有些表并不用所有记录都加载到缓存当中,这个时候就可以重写本函数来实现每次加载时只加载指定的记录到缓存中 27 /// </summary> 28 /// <typeparam name="T"></typeparam> 29 /// <returns></returns> 30 public virtual Expression<Func<T, bool>> GetExpression<T>() 31 { 32 return null; 33 } 34 #endregion 35 36 #region 绑定表格 37 public abstract void BindGrid(FineUI.Grid grid, int pageIndex = 0, int pageSize = 0, List<ConditionFun.SqlqueryCondition> wheres = null, List<string> orders = null); 38 39 public abstract void BindGrid(FineUI.Grid grid, int parentValue, List<ConditionFun.SqlqueryCondition> wheres = null, List<string> orders = null, 40 string parentId = "ParentId"); 41 42 public abstract void BindGrid(FineUI.Grid grid, int parentValue, List<string> orders = null, string parentId = "ParentId"); 43 #endregion 44 45 #region 删除记录 46 public abstract void Delete(Page page, int id); 47 public abstract void Delete(Page page, int[] id); 48 #endregion 49 50 #region 保存排序 51 public abstract bool UpdateSort(Page page, Grid grid1, string tbxOrderId, string orderIdName = "OrderId"); 52 #endregion 53 54 #region 自动排序 55 public abstract bool UpdateAutoSort(Page page, string strWhere = "", bool isExistsMoreLv = false, int pid = 0, 56 string fieldName = "OrderId", string fieldParentId = "ParentId"); 57 #endregion 58 } 59 }
原来的模板根据需要也对应做出了相应调整
我们在第1点时不是强制要求实现Init()函数吗,这里就是主要为PageBase类(父类)的逻辑层接口赋值用的。
比如我们要实现一个信息列表的功能,由于我们的逻辑层都继承了ILogicBase类,所以我们只要在Init()函数中给PageBase类的protected ILogicBase bll = null;这个定义重新赋值就可以了,那么在执行下面代码时就会通过这个接口的调用来执行对应类的功能
在Init()初始化函数中,给逻辑层对象赋值例子
1 using System; 2 using Solution.Logic.Managers; 3 using Solution.Web.Managers.WebManage.Application; 4 5 namespace Solution.Web.Managers 6 { 7 public partial class WebForm1 : PageBase 8 { 9 protected void Page_Load(object sender, EventArgs e) 10 { 11 12 } 13 14 15 #region 接口函数,用于UI页面初始化,给逻辑层对象、列表等对象赋值 16 /// <summary> 17 /// 接口函数,用于UI页面初始化,给逻辑层对象、列表等对象赋值 18 /// </summary> 19 public override void Init() 20 { 21 //给逻辑层对象赋值 22 bll = InformationBll.GetInstence(); 23 //给表格赋值 24 grid = Grid1; 25 } 26 #endregion 27 } 28 }
PageBase类执行逻辑层程序代码——实际上这些代码可以再次进行封装成一个公共函数,不过都是用模板生成的,有变动不用一个个改就懒得再重构了
1 /// <summary> 2 /// 保存排序 3 /// </summary> 4 /// <returns>返回保存结果</returns> 5 public virtual void SaveSort() 6 { 7 //保存排序 8 if (grid != null && bll != null) 9 { 10 //更新排序 11 if (bll.UpdateSort(this, grid, "tbxOrderID")) 12 { 13 //重新加载列表 14 LoadData(); 15 16 Alert.ShowInParent("操作成功", "保存排序成功", "window.location.reload();"); 17 } 18 else 19 { 20 Alert.ShowInParent("操作成失败", "保存排序失败", "window.location.reload();"); 21 } 22 } 23 } 24 25 /// <summary> 26 /// 保存自动排序 27 /// </summary> 28 public virtual void SaveAutoSort() 29 { 30 if (bll == null) 31 { 32 Alert.ShowInParent("保存失败", "逻辑层对象为null,请联系开发人员给当前页面的逻辑层对象赋值"); 33 return; 34 } 35 36 if (bll.UpdateAutoSort(this, "", true)) 37 { 38 Alert.ShowInParent("保存成功", "保存自动排序成功", "window.location.reload();"); 39 } 40 else 41 { 42 Alert.ShowInParent("保存失败", "保存自动排序失败", "window.location.reload();"); 43 } 44 }
3、MenuInfoBll逻辑类
它是后端所有页面权限、页面控件权限,以及页面访问链接加密处理的功能类
它会将后端所有菜单与页面记录全部加载到缓存当中,优化后端检查权限时的性能;
用户对后端页面的所有访问,都会经过CheckPagePower函数的处理,只要在系统中绑定了菜单与页面,后端页面开发时,就不必去考虑页面权限,即不用对每个页面的权限进行赋值,CheckPagePower函数会自动检查并判断用户是否有访问权限并做出相应处理;
后端所有页面的加密解密处理函数也在这里,所有访问链接都需要加上对应的加密函数,不然该页面无法正常访问,这种功能可以使我们的后端在管理相关业务时,用户不能通过复制页面链接后修改链接参数中的Id访问,以达到查看当前用户查看无权限访问的信息,防止业务信息被非常查看(当然也不是不能破解,这种算法将加大其难度),另外该链接只有当前浏览器上有效,通过复制发送给他人马上失效。
1 using System; 2 using System.Collections; 3 using System.Collections.Generic; 4 using System.Linq; 5 using System.Text; 6 using System.Web; 7 using System.Web.UI; 8 using DotNet.Utilities; 9 using Solution.DataAccess.DataModel; 10 11 namespace Solution.Logic.Managers 12 { 13 /// <summary> 14 /// MenuInfoBll逻辑类 15 /// </summary> 16 public partial class MenuInfoBll : LogicBase 17 { 18 /*********************************************************************** 19 * 自定义函数 * 20 ***********************************************************************/ 21 22 #region 自定义函数 23 24 private const string const_CacheKey_Model = "Cache_MenuInfo_AllModel"; 25 26 #region 获取MenuInfo全表内容并放到缓存中 27 28 /// <summary> 29 /// 取得MenuInfo全表内容——使用菜单地址做为KEY 30 /// </summary> 31 /// <returns>返回Hashtable</returns> 32 public Hashtable GetHashtable() 33 { 34 //读取记录 35 object obj = CacheHelper.GetCache(const_CacheKey_Model); 36 //如果记录不存在,则重新加载 37 if (obj == null) 38 { 39 //初始化全局菜单内容缓存 40 var ht = new Hashtable(); 41 //获取菜单表全部内容 42 var all = MenuInfo.All(); 43 //遍历读取 44 foreach (var model in all) 45 { 46 //创建菜单实体 47 var menuinfo = new MenuInfo(); 48 menuinfo.Id = model.Id; 49 menuinfo.Name = model.Name; 50 menuinfo.Url = model.Url; 51 menuinfo.ParentId = model.ParentId; 52 menuinfo.Depth = model.Depth; 53 54 try 55 { 56 //将菜单实体存入容器中 57 //使用页面地址做为KEY 58 ht.Add(menuinfo.Url, menuinfo); 59 } 60 catch (Exception) 61 { 62 } 63 } 64 65 if (ht.Count > 0) 66 { 67 CacheHelper.SetCache(const_CacheKey_Model, ht); 68 } 69 70 return ht; 71 } 72 else 73 { 74 return (Hashtable) obj; 75 } 76 } 77 78 #endregion 79 80 #region 清空缓存 81 82 /// <summary>清空缓存</summary> 83 public override void DelCache() 84 { 85 CacheHelper.RemoveOneCache(const_CacheKey_Model); 86 } 87 88 #endregion 89 90 #region 根据菜单Url地址,获取菜单相应内容 91 92 /// <summary> 93 /// 根据菜单Url地址,获取菜单相应内容 94 /// </summary> 95 /// <param name="menuInfoUrl">页面地址</param> 96 /// <returns>返回菜单实体</returns> 97 public MenuInfo GetMenuInfo(string menuInfoUrl) 98 { 99 try 100 { 101 //从全局缓存中读取菜单内容 102 //获取菜单实体 103 return (MenuInfo) (MenuInfoBll.GetInstence().GetHashtable()[menuInfoUrl]); 104 } 105 catch (Exception) 106 { 107 return new MenuInfo(); 108 } 109 } 110 111 #endregion 112 113 #region 检查页面权限 114 115 /// <summary> 116 /// 检查当前菜单或页面是否有权限访问 117 /// </summary> 118 /// <param name="menuId">菜单ID</param> 119 /// <returns>真或假</returns> 120 public bool CheckPagePower(string menuId) 121 { 122 var pagePower = OnlineUsersBll.GetInstence().GetPagePower(); 123 if (string.IsNullOrEmpty(pagePower) || menuId == "") 124 { 125 return false; 126 } 127 //检查是否有权限 128 if ( 129 pagePower.IndexOf("," + menuId + ",") >= 0) 130 { 131 return true; 132 } 133 else 134 { 135 return false; 136 } 137 } 138 139 #endregion 140 141 #region 检查当前页面控件是否有权限访问 142 143 /// <summary> 144 /// 检查当前页面控件是否有权限访问 145 /// </summary> 146 /// <param name="page">页面指针</param> 147 /// <param name="controlName">控件名称</param> 148 /// <returns>真或假</returns> 149 public bool CheckControlPower(Page page, string controlName) 150 { 151 //获取当前访问页面的URL 152 var currentPage = page.Request.Url.AbsolutePath; 153 //获取当前用户所有可以访问的页面ID 154 var menuId = GetMenuInfo(currentPage).Id; 155 //判断全局缓存中是否存储了该控件ID,否的话表示该控件没有权限 156 if (PagePowerSignPublicBll.GetInstence().GetHashtable()[controlName] == null) 157 { 158 return false; 159 } 160 else 161 { 162 var controlPower = OnlineUsersBll.GetInstence().GetControlPower(); 163 if (string.IsNullOrEmpty(controlPower)) 164 { 165 return false; 166 } 167 //获取当前控件ID 168 string ppsID = PagePowerSignPublicBll.GetInstence().GetHashtable()[controlName].ToString(); 169 170 //检查是否有权限 171 if (controlPower.IndexOf("," + menuId + "|" + ppsID + ",") >= 0) 172 { 173 return true; 174 } 175 else 176 { 177 return false; 178 } 179 180 } 181 182 } 183 184 #endregion 185 186 #region 判断当前用户是否有当前页面操作权限 187 188 /// <summary> 189 /// 判断当前用户是否有本页面的操作权限 190 /// </summary> 191 /// <returns></returns> 192 public void CheckPagePower(Page page) 193 { 194 try 195 { 196 //获取当前访问页面的名称 197 var currentPage = page.Request.Url.AbsolutePath; 198 if (currentPage.Equals("/WebManage/Main.aspx")) 199 return; 200 201 //检查是否从正确路径进入 202 ChichPageEncrypt(page); 203 204 205 //获取当前用户所有可以访问的页面ID 206 var menuId = GetMenuInfo(currentPage).Id + ""; 207 208 if (!CheckPagePower(menuId)) 209 { 210 //添加用户访问记录 211 UseLogBll.GetInstence().Save(page, "{0}没有权限访问【{1}】页面"); 212 213 page.Response.Write("您没有访问该页面的权限!"); 214 page.Response.End(); 215 return; 216 } 217 218 } 219 catch (Exception) 220 { 221 //添加用户访问记录 222 UseLogBll.GetInstence().Save(page, "{0}没有权限访问【{1}】页面"); 223 224 page.Response.Write("您没有访问该页面的权限!"); 225 page.Response.End(); 226 return; 227 } 228 } 229 230 #endregion 231 232 #region 页面访问加密--检查用户是否从正确的路径进入本页面 233 234 /// <summary> 235 /// 设置页面加密--用于检查用户是否从正确的路径进入本页面 236 /// </summary> 237 /// <param name="key">页面加密的Key</param> 238 /// <returns>加密后的字符串</returns> 239 public string SetPageEncrypt(string key) 240 { 241 //当前用户md5 242 var md5 = OnlineUsersBll.GetInstence().GetMd5(); 243 //加密:md5+Key 244 var encrypt = DotNet.Utilities.Encrypt.Md5(md5 + key); 245 //再次加密:Key + Encrypt 246 return Encrypt.Md5(key + encrypt); 247 } 248 249 /// <summary> 250 /// 检查用户是否从正确的路径进入本页面,默认KEY为ID 251 /// </summary> 252 public void CheckPageEncrypt(Page page) 253 { 254 //当前用户md5 255 var md5 = OnlineUsersBll.GetInstence().GetMd5(); 256 //Key,如果没有传递Key这个变量过来的,就读取id或ParentID做为Key使用 257 var key = HttpContext.Current.Request["Id"]; 258 if (string.IsNullOrEmpty(key)) 259 { 260 key = HttpContext.Current.Request["pid"]; 261 } 262 if (string.IsNullOrEmpty(key)) 263 { 264 key = HttpContext.Current.Request["ParentId"]; 265 } 266 if (string.IsNullOrEmpty(key)) 267 { 268 key = HttpContext.Current.Request["Key"]; 269 } 270 //上一链接传过来的加密数据 271 var keyEncrypt = HttpContext.Current.Request["KeyEncrypt"]; 272 273 //加密:md5+Key 274 var encrypt = Encrypt.Md5(md5 + key); 275 //再次加密:Key + Encrypt 276 encrypt = Encrypt.Md5(key + encrypt); 277 278 //检查是否有权限,没有权限的直接终止当前页面的运行 279 if (keyEncrypt != encrypt || string.IsNullOrEmpty(key)) 280 { 281 try 282 { 283 //添加用户访问记录 284 UseLogBll.GetInstence().Save(page, "{0}没有权限访问【{1}】页面"); 285 } 286 catch (Exception) 287 { 288 } 289 290 HttpContext.Current.Response.Write("你从错误的路径进入当前页面!"); 291 HttpContext.Current.Response.End(); 292 } 293 } 294 295 /// <summary> 296 /// 组成URL加密参数字符串 297 /// </summary> 298 /// <param name="key">页面加密的Key</param> 299 /// <returns>组成URL加密参数字符串</returns> 300 public string PageUrlEncryptString(string key) 301 { 302 return "KeyEncrypt=" + SetPageEncrypt(key) + "&Key=" + key; 303 } 304 305 /// <summary> 306 /// 组成URL加密参数字符串,使用随机生成的Key,如果页面传的参数中包含有ID这个名称的,则不能使用本函数 307 /// </summary> 308 /// <returns>组成URL加密参数字符串</returns> 309 public string PageUrlEncryptString() 310 { 311 var key = RandomHelper.GetRandomCode(null, 12); 312 return "KeyEncrypt=" + SetPageEncrypt(key) + "&Id=" + key; 313 } 314 315 /// <summary> 316 /// 组成URL加密参数字符串——返回不带Key的字符串 317 /// </summary> 318 /// <param name="key">页面加密的Key</param> 319 /// <returns>组成URL加密参数字符串——不带Key</returns> 320 public string PageUrlEncryptStringNoKey(string key) 321 { 322 return "KeyEncrypt=" + SetPageEncrypt(key); 323 } 324 325 /// <summary>和 PageBase.BtnSave_Click 对应,部分页面刷新后不关闭原页面,并要刷新的情况下使用</summary> 326 /// <param name="url">跳转的url</param> 327 /// <returns></returns> 328 public string PageSaveReturnUrlFlash(string url) 329 { 330 url = DirFileHelper.GetFilePath(HttpContext.Current.Request.Path) + "/" + url; 331 return "{url}" + url; 332 } 333 334 #endregion 335 336 #region 从页面中找到放置按键控件的位置 337 338 /// <summary> 339 /// 从页面中找到放置按键控件的位置 340 /// </summary> 341 /// <param name="controls"></param> 342 /// <returns></returns> 343 public ControlCollection GetToolBarControls(ControlCollection controls) 344 { 345 if (controls == null) 346 return null; 347 348 ControlCollection c = null; 349 try 350 { 351 for (int i = 0; i < controls.Count; i++) 352 { 353 if (controls[i].ID == "toolBar") 354 { 355 return controls[i].Controls; 356 } 357 c = GetToolBarControls(controls[i].Controls); 358 if (c != null) 359 return c; 360 } 361 } 362 catch (Exception) 363 { 364 } 365 366 return c; 367 } 368 369 #endregion 370 371 #endregion 自定义函数 372 } 373 }
看看效果:
这个是编辑页面的路径,KeyEncrypt参数值就是加密后的字串
将地址复制出来在同一个浏览器换个标签访问,修改Id参数,加密串不变时,显示没权限访问
换个浏览器使用同样的链接访问效果
4、OnlineUsersBll逻辑类
它是后端所有在线缓存、列表数据读写操作的功能类
主要功能大家自己研究吧,由于未经过运行测试,后面开发时可能会对一些函数进行比较大的修改。
1 using System; 2 using System.Collections; 3 using System.Collections.Generic; 4 using System.Web; 5 using System.Web.UI; 6 using DotNet.Utilities; 7 using FineUI; 8 using Solution.DataAccess.DataModel; 9 10 namespace Solution.Logic.Managers 11 { 12 13 /// <summary> 14 /// OnlineUsersBll逻辑类 15 /// </summary> 16 public partial class OnlineUsersBll : LogicBase 17 { 18 /*********************************************************************** 19 * 自定义函数 * 20 ***********************************************************************/ 21 #region 自定义函数 22 23 #region 加载数据库记录到缓存 24 private static readonly object LockObject = new object(); 25 /// <summary> 26 /// 加载数据库记录到缓存 27 /// </summary> 28 public void Load() 29 { 30 lock (LockObject) 31 { 32 //判断缓存中["OnlineUsers"]是否存在,不存在则尝试加载数据库中的在线表 33 if (CacheHelper.GetCache("OnlineUsers") == null) 34 { 35 //将当前用户信息添加到Hashtable中 36 var onlineUsers = new Hashtable(); 37 38 //不存在则尝试加载数据库中的在线表到缓存中 39 var list = GetList(); 40 if (list != null && list.Count > 0) 41 { 42 foreach (var model in list) 43 { 44 try 45 { 46 onlineUsers.Add(model.UserHashKey, model); 47 } 48 catch 49 { 50 } 51 } 52 53 //将在线列表(Hashtable)添中进系统缓存中 54 CacheHelper.SetCache("OnlineUsers", onlineUsers); 55 } 56 } 57 } 58 } 59 #endregion 60 61 #region 获取在线用户表内容 62 /// <summary> 63 /// 根据字段名称,获取当前用户在线表中的内容 64 /// </summary> 65 /// <param name="colName">字段名<para/> 66 /// 可选参数<para/> 67 /// UserId : 当前用户的ID编号<para/> 68 /// LoginDate : 登录时间<para/> 69 /// OnlineTime : 在线时长<para/> 70 /// LoginIp : 当前用户IP<para/> 71 /// LoginName : 当前用户登陆名<para/> 72 /// CName : 当前用户中文名<para/> 73 /// Sex : 当前用户的性别<para/> 74 /// BranchId : 部门自动ID<para/> 75 /// BranchCode : 部门编码<para/> 76 /// BranchName : 部门名称<para/> 77 /// PositionInfoId : 职位ID<para/> 78 /// PositionInfoName : 职位名称<para/> 79 /// </param> 80 /// <returns></returns> 81 public object GetUserOnlineInfo(string colName) 82 { 83 return GetUserOnlineInfo(null, colName); 84 } 85 /// <summary> 86 /// 根据字段名称,获取指定用户在线表中的内容 87 /// </summary> 88 /// <param name="userHashKey">用户在线列表的Key</param> 89 /// <param name="colName">字段名<para/> 90 /// userId : 当前用户的ID编号<para/> 91 /// LoginDate : 登录时间<para/> 92 /// OnlineTime : 在线时长<para/> 93 /// LoginIp : 当前用户IP<para/> 94 /// LoginName : 当前用户登陆名<para/> 95 /// CName : 当前用户中文名<para/> 96 /// Sex : 当前用户的性别<para/> 97 /// BranchId : 部门自动ID<para/> 98 /// BranchCode : 部门编码<para/> 99 /// BranchName : 部门名称<para/> 100 /// PositionInfoId : 职位ID<para/> 101 /// PositionInfoName : 职位名称<para/> 102 /// </param> 103 /// <returns></returns> 104 public object GetUserOnlineInfo(string userHashKey, string colName) 105 { 106 //由于GetUserHashKey有可能从 107 108 try 109 { 110 if (colName == "") 111 { 112 return null; 113 } 114 115 if (userHashKey == null) 116 { 117 userHashKey = GetUserHashKey() +""; 118 } 119 120 //如果不存在在线表则退出 121 if (HttpRuntime.Cache["OnlineUsers"] == null || userHashKey == "") 122 return null; 123 124 //返回指定字段的内容 125 var model = (DataAccess.Model.OnlineUsers)((Hashtable) CacheHelper.GetCache("OnlineUsers"))[userHashKey]; 126 127 return GetFieldValue(model, colName); 128 } 129 catch (Exception e) 130 { 131 CommonBll.WriteLog("", e); 132 } 133 134 return null; 135 } 136 #endregion 137 138 #region 更新在线用户信息 139 /// <summary> 140 /// 更新在线用户信息 141 /// </summary> 142 /// <param name="userId">用户ID,即在线用户表中的KEY</param> 143 /// <param name="colName">要更新的字段名称</param> 144 /// <param name="value">将要赋的值</param> 145 public void UpdateUserOnlineInfo(string userId, string colName, object value) 146 { 147 try 148 { 149 ((Dictionary<String, object>)((Hashtable)CacheHelper.GetCache("OnlineUsers"))[userId])[colName] = value; 150 } 151 catch (Exception) { } 152 } 153 #endregion 154 155 #region 获取在线人数 156 /// <summary> 157 /// 获取在线人数 158 /// </summary> 159 /// <returns></returns> 160 public int GetUserOnlineCount() 161 { 162 return ((Hashtable)CacheHelper.GetCache("OnlineUsers")).Count; 163 } 164 #endregion 165 166 #region 更新在线人数 167 /// <summary> 168 /// 更新在线人数,将不在线人员删除 169 /// </summary> 170 public void UpdateUserOnlineCount() 171 { 172 //获取在线列表 173 var onlineUsers = (Hashtable)CacheHelper.GetCache("OnlineUsers"); 174 175 //如果不存在在线表则退出 176 if (onlineUsers == null) 177 return; 178 179 //初始化下线用户KEY存储数组 180 var users = new string[onlineUsers.Count]; 181 int i = 0; 182 183 //循环读取在线信息 184 foreach (DictionaryEntry de in onlineUsers) 185 { 186 //判断该用户是否在线,不在线的话则将它暂停存储起来 187 if (CacheHelper.GetCache("OnlineUsers_" + de.Key) == null) 188 { 189 users[i] = de.Key + ""; 190 i++; 191 } 192 } 193 194 //移除在线数据 195 for (int j = 0; j < users.Length; j++) 196 { 197 if (users[j] == null) 198 break; 199 200 //添加用户下线记录 201 LoginLogBll.GetInstence().Save(users[j], "用户【{0}】退出系统!在线时间【{1}】"); 202 //将HashTable里存储的前一登陆帐户移除 203 Delete(null, x => x.UserHashKey == users[j]); 204 //移除在线用户 205 RemoveUser(users[j]); 206 } 207 } 208 #endregion 209 210 #region 删除在线缓存中的用户 211 /// <summary> 212 /// 删除在线缓存中的用户 213 /// </summary> 214 /// <param name="userHashKey"></param> 215 public void RemoveUser(string userHashKey) 216 { 217 //获取在线列表 218 var onlineUsers = (Hashtable)CacheHelper.GetCache("OnlineUsers"); 219 220 //如果不存在在线表则退出 221 if (onlineUsers == null) 222 return; 223 224 //移除在线用户 225 onlineUsers.Remove(userHashKey); 226 } 227 #endregion 228 229 #region 判断用户是否被强迫离线 230 /// <summary> 231 /// 判断用户是否被强迫离线[true是;false否](由于用户长时间没有操作系统自动剔除用户节省资源,管理人员剔除下线) 232 /// </summary> 233 public bool IsOffline(Page page) 234 { 235 try 236 { 237 //获取当前用户Id 238 var userinfoId = GetManagerId(); 239 //获取用户Key 240 var userHashKey = GetUserHashKey(); 241 242 //判断当前用户是否已经被系统清出 243 if (userinfoId == 0 || HttpRuntime.Cache.Get("OnlineUsers_" + userHashKey) == null) 244 { 245 //通知用户 246 FineUI.Alert.Show("您太久没有操作已退出系统,请重新登录!", "检测通知", MessageBoxIcon.Information, "window.location.href='/WebManage/Login.aspx';"); 247 return true; 248 } 249 else 250 { 251 //判断在线用户的Md5与当前用户存储的Md5是否一致 252 if (GenerateMd5() != CookieHelper.GetCookieValue(OnlineUsersTable.Md5)) 253 { 254 CommonBll.WriteLog("当前帐号已经下线,用户Id【" + userinfoId + "】"); 255 256 //通知用户 257 FineUI.Alert.Show("您的账号已经在另一处登录,当前账号已经下线!", "检测通知", MessageBoxIcon.Information, "window.location.href='/WebManage/Login.aspx';"); 258 return true; 259 } 260 } 261 return false; 262 } 263 catch (Exception ex) 264 { 265 CommonBll.WriteLog("Logic.Systems.Manager.CheckIsOffline出现异常", ex); 266 267 FineUI.Alert.Show("系统已经开始更新维护,请稍后重新登录!", "检测通知", MessageBoxIcon.Information, "window.location.href='/WebManage/Login.aspx';"); 268 return true; 269 } 270 271 } 272 #endregion 273 274 #region 判断用户是否超时退出 275 276 /// <summary> 277 /// 判断用户是否超时退出(退出情况:1.系统更新,2.用户自动退出) 278 /// </summary> 279 public void IsTimeOut() 280 { 281 if (HttpContext.Current.Session == null || GetUserHashKey() == null) 282 { 283 try 284 { 285 //不存在则表示Session失效了,重新从Cookies中加载 286 var userHashKey = CookieHelper.GetCookieValue(OnlineUsersTable.UserHashKey); 287 var md5 = CookieHelper.GetCookieValue(OnlineUsersTable.Md5); 288 289 //判断Cookies是否存在,存在则查询在线列表,重新获取用户信息 290 if (userHashKey.Length > 0 && md5.Length == 32) 291 { 292 //读取当前用户在线实体 293 var model = GetModelForCache(x => x.UserHashKey == userHashKey); 294 //当前用户存在在线列表中 295 if (model != null) 296 { 297 //计算用户md5值 298 var key = GenerateMd5(model); 299 300 //判断用户的md5值是否正确 301 if (md5 == key) 302 { 303 //将UserHashKey存储到缓存中 304 HttpContext.Current.Session[OnlineUsersTable.UserHashKey] = userHashKey; 305 //获取用户权限并存储到用户Session里 306 PositionBll.GetInstence().SetUserPower(model.Position_Id); 307 //更新用户当前SessionId到在线表中 308 //UpdateUserOnlineInfo(model.Id + "", OnlineUsersTable.SessionId, HttpContext.Current.Session.SessionID); 309 310 //加载用户相关信息完毕,退出超时检查 311 return; 312 } 313 } 314 } 315 } 316 catch (Exception e) 317 { 318 //出现异常,保存出错日志信息 319 CommonBll.WriteLog("", e); 320 } 321 322 323 //用户不存在,直接退出 324 FineUI.Alert.Show("当前用户登录已经过时或系统已更新,请重新登录!", "检测通知", MessageBoxIcon.Information, "window.location.href='/WebManage/Login.aspx';"); 325 HttpContext.Current.Response.End(); 326 } 327 } 328 #endregion 329 330 #region 生成加密串——用户加密密钥计算 331 /// <summary> 332 /// 生成加密串——用户加密密钥计算 333 /// </summary> 334 /// <param name="model">在线实体</param> 335 /// <returns></returns> 336 public string GenerateMd5(DataAccess.Model.OnlineUsers model) 337 { 338 if (model == null) 339 { 340 return RandomHelper.GetRndKey(); 341 } 342 else 343 { 344 //Md5(密钥+登陆帐号+密码+IP+密钥.Substring(6,8)) 345 return Encrypt.Md5(model.UserKey + model.Manager_LoginName + model.Manager_LoginPass + 346 IpHelper.GetUserIp() + model.UserKey.Substring(6, 8)); 347 } 348 } 349 350 /// <summary> 351 /// 生成加密串——用户加密密钥计算,直接读取当前用户实体 352 /// </summary> 353 /// <returns></returns> 354 public string GenerateMd5() 355 { 356 //读取当前用户实体 357 var model = GetModelForCache(GetManagerId()); 358 return GenerateMd5(model); 359 } 360 #endregion 361 362 #region 获取用户ID 363 /// <summary> 364 /// 从Session中读取用户ID,如果Session为Null时,返回0 365 /// </summary> 366 /// <returns></returns> 367 public int GetManagerId() 368 { 369 return ConvertHelper.Cint0(GetUserOnlineInfo(OnlineUsersTable.Manager_Id)); 370 } 371 #endregion 372 373 #region 获取用户中文名称 374 /// <summary> 375 /// 从Session中读取用户中文名称,如果Session为Null时,返回"" 376 /// </summary> 377 /// <returns></returns> 378 public string GetManagerCName() 379 { 380 return GetUserOnlineInfo(OnlineUsersTable.Manager_CName) + ""; 381 } 382 #endregion 383 384 #region 获取用户UserHashKey 385 /// <summary> 386 /// 获取用户UserHashKey 387 /// </summary> 388 /// <returns></returns> 389 public object GetUserHashKey() 390 { 391 //读取Session中存储的UserHashKey值 392 var userHashKey = HttpContext.Current.Session[OnlineUsersTable.UserHashKey]; 393 //如果为null 394 if (userHashKey == null) 395 { 396 //为null则表示用户Session过期了,所以要检查用户登陆,避免用户权限问题 397 IsTimeOut(); 398 399 //从Cookies中读取 400 userHashKey = CookieHelper.GetCookieValue(OnlineUsersTable.UserHashKey); 401 //不为null时,重新存储到Session中 402 if (userHashKey != null) 403 { 404 HttpContext.Current.Session[OnlineUsersTable.UserHashKey] = userHashKey; 405 } 406 } 407 return userHashKey; 408 } 409 #endregion 410 411 #region 获取用户加密串——用户加密密钥Md5值 412 /// <summary> 413 /// 获取用户加密串——用户加密密钥Md5值 414 /// </summary> 415 /// <returns></returns> 416 public object GetMd5() 417 { 418 //读取Session中存储的Md5值 419 var md5 = HttpContext.Current.Session[OnlineUsersTable.Md5]; 420 //如果为null 421 if (md5 == null) 422 { 423 //为null则表示用户Session过期了,所以要检查用户登陆,避免用户权限问题 424 IsTimeOut(); 425 426 //从Cookies中读取 427 md5 = CookieHelper.GetCookieValue(OnlineUsersTable.Md5); 428 //不为null时,重新存储到Session中 429 if (md5 != null) 430 { 431 HttpContext.Current.Session[OnlineUsersTable.Md5] = md5; 432 } 433 } 434 return md5; 435 } 436 #endregion 437 438 #region 获取用户页面操作权限 439 /// <summary> 440 /// 获取用户页面操作权限 441 /// </summary> 442 /// <returns></returns> 443 public string GetPagePower() 444 { 445 //读取Session中存储的PagePower值 446 var pagePower = HttpContext.Current.Session[PositionTable.PagePower]; 447 //如果为null 448 if (pagePower == null) 449 { 450 //获取用户权限并存储到用户Session里 451 PositionBll.GetInstence().SetUserPower(GetUserOnlineInfo(OnlineUsersTable.Position_Id) + ""); 452 } 453 pagePower = HttpContext.Current.Session[PositionTable.PagePower]; 454 return pagePower + ""; 455 } 456 #endregion 457 458 #region 获取用户页面控件(按键)操作权限 459 /// <summary> 460 /// 获取用户页面控件(按键)操作权限 461 /// </summary> 462 /// <returns></returns> 463 public string GetControlPower() 464 { 465 //读取Session中存储的ControlPower值 466 var controlPower = HttpContext.Current.Session[PositionTable.ControlPower]; 467 //如果为null 468 if (controlPower == null) 469 { 470 //获取用户权限并存储到用户Session里 471 PositionBll.GetInstence().SetUserPower(GetUserOnlineInfo(OnlineUsersTable.Position_Id) + ""); 472 } 473 controlPower = HttpContext.Current.Session[PositionTable.ControlPower]; 474 return controlPower + ""; 475 } 476 #endregion 477 478 #endregion 自定义函数 479 } 480 }
点击下载:
由于框架不是非常成熟,很多朋友不是用来学习而是直接用到项目中,但不熟悉框架引起不少小问题,所以停止提供下载,有需要学习的可以到群共享里下,不便之处敬请谅解。
由于本解决方案开发时没有经过调试,所以存在一些小BUG,这些会在下一章节发布的代码中解决,大家先自行熟悉一下代码与结构就可以了
版权声明:
本文由AllEmpty原创并发布于博客园,欢迎转载,未经本人同意必须保留此段声明,且在文章页面明显位置给出原文链接,否则保留追究法律责任的权利。如有问题,可以通过1654937@qq.com 联系我,非常感谢。
发表本编内容,只要主为了和大家共同学习共同进步,有兴趣的朋友可以加加Q群:327360708 ,大家一起探讨。
更多内容,敬请观注博客:http://www.cnblogs.com/EmptyFS/