Web应用程序系统的多用户权限控制设计及实现-登录模块【4】
通过前三个模块的介绍,把web权限系统开发所需要的基本类,Css文件,EasyUI框架等准备好后,就可以着手开始系统的编码了。
登陆模块是权限处理系统的关键,根据输入用户的的信息,可自动从数据库中加载该用户可以访问的页面,匹配出可以操作的模块。
由于登录模块是系统的基本模块,因此没有单独放在一个域里面。登录的控制器在项目默认的Controllers文件夹下。登录对应的视图在项目默认的Views文件夹下。
1.1视图
登录视图中比较重要的是通过.NET MVC的Ajax异步方式提交用户名和密码到后台服务。
提交格式如下:@using (Ajax.BeginForm("Login", "Login",
new AjaxOptions
{
OnBegin = "loginBefore",
OnSuccess = "showMessage"
}))
在提交数据前做必要的数据格式校验,提交成功后,通过Login.js文件中的函数跳转到首页。登录模块的详细视图内容如下:

1 @{ 2 Layout = null; 3 } 4 5 <!DOCTYPE html> 6 7 <html> 8 <head> 9 <meta name="viewport" content="width=device-width" /> 10 <title>用户登录</title> 11 <script src="~/Scripts/jquery-1.7.1.js"></script> 12 <link href="~/Content/css/login.css" rel="stylesheet" /> 13 <link href="~/Content/easyui143/themes/bootstrap/easyui.css" rel="stylesheet" /> 14 <style type="text/css"> 15 .passwordtxt { 16 height: 35px; 17 width: 117px; 18 line-height: 27px; 19 border: none; 20 border: none; 21 font-size: 14px; 22 text-indent: 10px; 23 background: url(../../Content/images/passwordtxt.png); 24 vertical-align: middle; 25 color: black; 26 margin-left: 5px; 27 } 28 29 30 .inputicon { 31 margin-left: -410px; 32 vertical-align: middle; 33 } 34 35 .btn, .yzm { 36 cursor: pointer; 37 } 38 39 40 .txtfocus { 41 height: 42px; 42 width: 262px; 43 line-height: 27px; 44 border: none; 45 border: none; 46 font-size: 14px; 47 text-indent: 10px; 48 background: url(../../Content/images/inputing.png) no-repeat; 49 vertical-align: middle; 50 color: black; 51 } 52 53 54 .passwordfocustxt { 55 height: 35px; 56 width: 117px; 57 line-height: 27px; 58 border: none; 59 border: none; 60 font-size: 14px; 61 text-indent: 10px; 62 background: url(../../Content/images/checkcodeinputing.png); 63 vertical-align: middle; 64 color: black; 65 margin-left: 5px; 66 } 67 </style> 68 <script src="~/Scripts/jquery.unobtrusive-ajax.min.js"></script> 69 70 </head> 71 <body> 72 <div class="all"> 73 <div class="loginContent"> 74 <div class="loginContent-inner"> 75 <h1 class="loginTitle"></h1> 76 <div class="main"> 77 <div class="erro"> 78 </div> 79 <ul class="loginList"> 80 @using (Ajax.BeginForm("Login", "Login", 81 new AjaxOptions 82 { 83 OnBegin = "loginBefore", 84 OnSuccess = "showMessage" 85 })) 86 { 87 <li> 88 <span>用户名</span> 89 <input type="text" tabindex="1" class="txt" name="userName" id="UserName" style="padding-left: 35px;" /> 90 <input type="submit" name="LoginBut" class="btn" value="登录" style="border-width: 0px;" onclick=" return Login();" /> 91 <img src="/Content/Images/user1.png" class="inputicon" id="userNameImg" /> 92 </li> 93 94 <li> 95 <span>密 码</span> 96 <input name="password" id="Password" type="password" tabindex="2" class="txt" style="padding-left: 35px;" /> 97 <input type="button" onclick="javascript: Cancel();" class="submit" value="重置" style="border: 0px; cursor: pointer;" /> 98 <img src="/Content/Images/password1.png" class="inputicon" id="passwordImg" /> 99 </li> 100 <li> 101 <span>验证码</span> 102 <input type="text" tabindex="3" name="checkCode" id="txtCheckCode" class="passwordtxt" /> 103 <img src="/ValidateCode/ValidateCode" id="CheckImage" 104 onclick="ChangeCode()" width="69" height="27" class="yzm" /> 105 <a href="javascript:ChangeCode();" style="color: #00689d; text-decoration: underline;">换一张</a> 106 </li> 107 } 108 </ul> 109 </div> 110 <div class="loginFooter"> 111 支持FireFox、IE8及其以上版本的浏览器 112 <br /> 113 技术支持:######<span></span> 114 </div> 115 @if (ViewData["AA"] == null) 116 { 117 <div> 118 </div> 119 } 120 </div> 121 </div> 122 </div> 123 124 <script type="text/javascript" src="~/Scripts/CustomJs/login.js"> 125 </script> 126 127 </body> 128 </html>
1.2 JS文件
登录模块所用的JS文件在项目的根目录Scripts目录下。它主要是提交登录信息,根据服务器返回的结果判断登录信息是否有效。详细的JS代码如下:

1 //显示遮罩层 2 function LoadMask(msg) { 3 var panel = $("body"); 4 if (msg == undefined) { 5 msg = "正在登陆,请稍候..."; 6 } 7 $("<div class=\"datagrid-mask\"></div>").css({ display: "block", width: panel.width(), height: panel.height() }).appendTo(panel); 8 $("<div class=\"datagrid-mask-msg\"></div>").html(msg).appendTo(panel).css({ display: "block", left: (panel.width() - $("div.datagrid-mask-msg", panel).outerWidth()) / 2, top: (panel.height() - $("div.datagrid-mask-msg", panel).outerHeight()) / 2 }); 9 }; 10 11 //隐藏遮罩层 12 function HideMask() { 13 var panel = $("body"); 14 panel.find("div.datagrid-mask-msg").remove(); 15 panel.find("div.datagrid-mask").remove(); 16 }; 17 18 //刷新验证码 19 function ChangeCode() { 20 $("#CheckImage").prop("src", "/ValidateCode/ValidateCode/" + Math.ceil(Math.random() * 1000)); 21 $("#txtCheckCode").val(""); 22 }; 23 24 //前台验证用户输入信息 25 function Login() { 26 if ($("#UserName").val().replace(/ /g, "") == "") { 27 alert("请输入帐号!"); 28 return false; 29 } 30 if ($("#Password").val().replace(/ /g, "") == "") { 31 alert("请输入密码!"); 32 return false; 33 } 34 35 if ($("#txtCheckCode").val().replace(/ /g, "") == "") { 36 alert("请输入验证码!"); 37 return false; 38 } 39 return true; 40 } 41 42 //重置,清空用户已经输入的信息 43 function Cancel() { 44 $("#UserName").val(""); 45 $("#Password").val(""); 46 $("#txtCheckCode").val(""); 47 ChangeCode(); 48 } 49 50 function loginBefore() { 51 if ($("#UserName").val().replace(/ /g, "") == "") { 52 alert("请输入帐号!"); 53 return false; 54 } 55 if ($("#Password").val().replace(/ /g, "") == "") { 56 alert("请输入密码!"); 57 return false; 58 } 59 60 if ($("#txtCheckCode").val().replace(/ /g, "") == "") { 61 alert("请输入验证码!"); 62 return false; 63 } 64 LoadMask(); 65 return true; 66 }; 67 68 //将用户信息提交到后台,经后台验证后的操作,回调函数 69 function showMessage(data) { 70 HideMask(); 71 if (data == "1") { 72 window.location.href = "/IndexHome/IndexHome"; 73 } 74 else { 75 alert(data); 76 $("#Password").val(""); 77 //刷新验证码 78 ChangeCode(); 79 } 80 } 81 82 83 $(function () { 84 85 $("#UserName").focus(function () { 86 $(this).removeClass("txt"); 87 $(this).addClass("txtfocus"); 88 $("#userNameImg").attr("src", "/Content/Images/user2.png"); 89 }).blur(function () { 90 $(this).removeClass("txtfocus"); 91 $(this).addClass("txt"); 92 $("#userNameImg").attr("src", "/Content/Images/user1.png"); 93 }); 94 95 96 $("#Password").focus(function () { 97 $(this).removeClass("txt"); 98 $(this).addClass("txtfocus"); 99 $("#passwordImg").attr("src", "/Content/Images/password2.png"); 100 }).blur(function () { 101 $(this).removeClass("txtfocus"); 102 $(this).addClass("txt"); 103 $("#passwordImg").attr("src", "/Content/Images/password1.png"); 104 }); 105 106 $("#txtCheckCode").focus(function () { 107 $(this).removeClass("passwordtxt"); 108 $(this).addClass("passwordfocustxt"); 109 }).blur(function () { 110 $(this).removeClass("passwordfocustxt"); 111 $(this).addClass("passwordtxt"); 112 }); 113 114 115 });
1.3 控制器
登录控制器是整个权限处理的核心模块,它根据用户的信息自动加载出用户可以访问的目录信息,可以访问的网页信息通过Session的方式,把信息通过SessionManage基本类进行会话管理。部分代码如下:
SessionManage.CurrentUser = null;先清空会话信息
#region 封装用户信息
var currentUser = new AccountInfo();
currentUser.OperatorId = Convert.ToString(dr["accountid"]);
currentUser.OperatorName = DBNull.Value.Equals(dr["accountname"]) ? "" : Convert.ToString(dr["accountname"]);
currentUser.AliasName = DBNull.Value.Equals(dr["aliasname"]) ? "" : Convert.ToString(dr["aliasname"]);
currentUser.Sex = DBNull.Value.Equals(dr["sex"]) ? "" : (Convert.ToInt32(dr["sex"]) == 0 ? "男" : "女");
currentUser.OperatorGroupId = DBNull.Value.Equals(dr["groupid"]) ? "" : Convert.ToString(dr["groupid"]);
currentUser.OperatorGroupName = (string.IsNullOrEmpty(groupName) ? "" : groupName.Substring(0, groupName.Length - 1));
SessionManage.CurrentUser = currentUser; //保存基本信息
#endregion
//目录列表(存储登录人员可以访问的一级目录和访问的网页)
IList<Catalog> navigationList = new List<Catalog>();
IList<Catalog> rightList = new List<Catalog>();
控制器的完整代码如下:

1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Web; 5 using System.Web.Mvc; 6 using Session; 7 using OdbcDbAccess; 8 using System.Data; 9 using Models; 10 using LogInfo; 11 12 namespace CqReportSystem 13 { 14 public class LoginController : Controller 15 { 16 /// <summary> 17 /// **************************** 18 /// 功能:登陆类 19 /// 作者:王令 20 /// 时间:2015-7-18 21 /// 邮箱:1129137758@qq.com 22 /// **************************** 23 24 public ActionResult Login() 25 { 26 SessionManage.CurrentUser = null; 27 Session.Clear(); 28 return View(); 29 } 30 31 /// <summary> 32 /// 用户登陆信息验证 33 /// </summary> 34 /// <param name="userName">账号</param> 35 /// <param name="password">密码</param> 36 /// <param name="checkCode">验证码</param> 37 /// <returns></returns> 38 [HttpPost] 39 public ContentResult Login(string userName, string password, string checkCode) 40 { 41 #region 不为空、验证码验证 42 43 if (string.IsNullOrEmpty(userName) || userName.Trim() == "") 44 { 45 return Content("请输入用户名!"); 46 } 47 if (string.IsNullOrEmpty(password) || password.Trim() == "") 48 { 49 return Content("请输入登陆密码!"); 50 } 51 if (string.IsNullOrEmpty(checkCode) || checkCode.Trim() == "") 52 { 53 return Content("请输入验证码!"); 54 } 55 if (Session["CheckCode"] == null) 56 { 57 Log.SaveLoginLog(0, "验证码过期"); 58 return Content("验证码已过期,请刷新验证码!"); 59 } 60 if (String.Compare(Session["CheckCode"].ToString(), checkCode, true) != 0) 61 { 62 Log.SaveLoginLog(0, "验证码有误"); 63 return Content("验证码有误!"); 64 } 65 #endregion 66 67 //移除验证码信息 68 Session.Remove("CheckCode"); 69 string sql = "select * from operatorinfo where accountid='" + userName.Trim() + "' and passwords='" + password.Trim() + "' "; 70 try 71 { 72 string groupName = ""; 73 #region 基本信息判断 74 //判断用户名和密码是否正确 75 DataSet dataSet = SqlHelper.ExecuteQuery(ConnectionHelper.GeSqlDbConnectionStr(), sql); 76 if (dataSet == null || dataSet.Tables.Count < 1 || dataSet.Tables[0].Rows.Count < 1) 77 { 78 Log.SaveLoginLog(0, "用户名或密码有误"); 79 return Content("用户名或密码有误!"); 80 } 81 //账号是否启用 82 DataRow dr = dataSet.Tables[0].Rows[0]; 83 if (DBNull.Value.Equals(dr["isonstaff"]) || Convert.ToInt32(dr["isonstaff"]) != 1) 84 { 85 Log.SaveLoginLog(0, "该账号未启用"); 86 return Content("该账号未启用!"); 87 } 88 //查找用户所属组 89 sql = "select groupname,state from operatorgroup where groupid in (" + Convert.ToString(dr["groupid"]) + ")"; 90 DataSet groupSet = SqlHelper.ExecuteQuery(ConnectionHelper.GeSqlDbConnectionStr(), sql); 91 if (groupSet == null || groupSet.Tables.Count < 1 || groupSet.Tables[0].Rows.Count < 1) 92 { 93 Log.SaveLoginLog(0, "未找到该用户所属用户组"); 94 return Content("未找到该用户所属用户组!"); 95 } 96 97 foreach (DataRow groupRow in groupSet.Tables[0].Rows) 98 { 99 if (DBNull.Value.Equals(groupRow["state"]) || Convert.ToInt32(groupRow["state"]) != 1) 100 { 101 Log.SaveLoginLog(0, "该用户所属用户组未被启用"); 102 return Content("该用户所属用户组未被启用!"); 103 } 104 if (!DBNull.Value.Equals(groupRow["groupname"])) 105 { 106 groupName += Convert.ToString(groupRow["groupname"]) + ","; 107 } 108 } 109 110 #endregion 111 var currentUser = new AccountInfo(); 112 #region 封装用户信息 113 currentUser.OperatorId = Convert.ToString(dr["accountid"]); 114 currentUser.OperatorName = DBNull.Value.Equals(dr["accountname"]) ? "" : Convert.ToString(dr["accountname"]); 115 currentUser.AliasName = DBNull.Value.Equals(dr["aliasname"]) ? "" : Convert.ToString(dr["aliasname"]); 116 currentUser.Sex = DBNull.Value.Equals(dr["sex"]) ? "" : (Convert.ToInt32(dr["sex"]) == 0 ? "男" : "女"); 117 currentUser.OperatorGroupId = DBNull.Value.Equals(dr["groupid"]) ? "" : Convert.ToString(dr["groupid"]); 118 currentUser.OperatorGroupName = (string.IsNullOrEmpty(groupName) ? "" : groupName.Substring(0, groupName.Length - 1)); 119 #endregion 120 SessionManage.CurrentUser = currentUser; 121 //验证通过,设置用户权限 122 if (!SetOperatorRight(currentUser.OperatorGroupId, currentUser.OperatorId)) 123 { 124 Log.SaveLoginLog(0, "权限设置失败"); 125 return Content("权限设置失败!"); 126 } 127 128 Log.SaveLoginLog(1, "登陆成功"); 129 130 return Content("1"); 131 } 132 catch (Exception ex) 133 { 134 Log.SaveLoginLog(0, ex.ToString()); 135 return Content("服务器内部错误!"); 136 } 137 } 138 139 /// <summary> 140 /// 设置用户权限 141 /// </summary> 142 /// <param name="operatorGroupId">用户组ID</param> 143 /// <param name="operatorId">用户登录账号</param> 144 /// <returns></returns> 145 private bool SetOperatorRight(string operatorGroupId, string operatorId) 146 { 147 try 148 { 149 //目录列表 150 IList<Catalog> navigationList = new List<Catalog>(); 151 IList<Catalog> rightList = new List<Catalog>(); 152 153 //获取首级导航栏信息 154 string strSql = "select distinct parentid,parentcatalogname,parentshowno from (select a.catalogid,a.parentid,b.parentid as granparendid," + 155 "a.catalogname,b.catalogname as parentcatalogname,a.showno,b.showno as parentshowno ,b.isavailable from catalog a left join " + 156 "catalog b on a.parentid=b.catalogid) as temptable where granparendid=0 and isavailable=1 and catalogid in (select categoryid from rightlist " + 157 "where operatorgroupid in (" + operatorGroupId + ")) order by parentshowno"; 158 DataSet navigationDs = SqlHelper.ExecuteQuery(ConnectionHelper.GeSqlDbConnectionStr(), strSql); 159 if (navigationDs != null && navigationDs.Tables.Count > 0 && navigationDs.Tables[0].Rows.Count > 0) 160 { 161 #region 封装导航栏到List 162 163 foreach (DataRow row in navigationDs.Tables[0].Rows) 164 { 165 int parentId = DBNull.Value.Equals(row["parentid"]) ? 0 : Convert.ToInt32(row["parentid"]); 166 string parentName = DBNull.Value.Equals(row["parentcatalogname"]) ? "" : Convert.ToString(row["parentcatalogname"]); 167 168 var item = new Catalog 169 { 170 CatalogId = parentId, 171 ParentId = 0, 172 CatalogName = parentName 173 }; 174 navigationList.Add(item); 175 } 176 177 #endregion 178 179 #region 获取二级栏目信息及页面信息 180 181 //获取二级分类信息 182 strSql = "select * from catalog where catalogid in (select categoryid from rightlist where operatorgroupid in (" + 183 operatorGroupId + ")) and isavailable=1 order by showno"; 184 DataSet catalogDs = SqlHelper.ExecuteQuery(ConnectionHelper.GeSqlDbConnectionStr(), strSql); 185 //获取页面信息 186 strSql = "select * from pageinfo where pageid in ( select pageid from rightlist where operatorgroupid in (" + 187 operatorGroupId + ")) and isavailable=1 order by catalogid,showno"; 188 DataSet pageDs = SqlHelper.ExecuteQuery(ConnectionHelper.GeSqlDbConnectionStr(), strSql); 189 190 //无栏目,则返回false 191 if (catalogDs == null || catalogDs.Tables.Count <= 0 || catalogDs.Tables[0].Rows.Count <= 0) 192 { 193 return false; 194 } 195 //无权限页面,则返回false 196 if (pageDs == null || pageDs.Tables.Count <= 0 || pageDs.Tables[0].Rows.Count <= 0) 197 { 198 return false; 199 } 200 201 #endregion 202 #region 封装用户权限 203 204 DataTable catalogTb = catalogDs.Tables[0]; 205 DataTable pageTb = pageDs.Tables[0]; 206 207 foreach (DataRow catalogDr in catalogTb.Rows) 208 { 209 int catalogId = Convert.ToInt32(catalogDr["catalogid"]); 210 int parentId = Convert.ToInt32(catalogDr["parentid"]); 211 string catalogName = Convert.ToString(catalogDr["catalogname"]); 212 string picUrl = DBNull.Value.Equals(catalogDr["picurl"]) ? "" : Convert.ToString(catalogDr["picurl"]); 213 var catalogItem = new Catalog { CatalogId = catalogId, ParentId = parentId, CatalogName = catalogName, PictureUrl = picUrl, PageList = new List<Page>() }; 214 215 //获取目录下的页面行 216 DataRow[] pageRows = pageTb.Select("catalogid=" + catalogId); 217 if (pageRows.Length > 0) 218 { 219 foreach (DataRow pageDr in pageRows) 220 { 221 int pageId = Convert.ToInt32(pageDr["pageid"]); 222 string pageName = Convert.ToString(pageDr["pagename"]); 223 string pageUrl = Convert.ToString(pageDr["pageurl"]); 224 var pageItem = new Page { PageIndex = pageId, PageName = pageName, PageUrl = pageUrl, CategoryId = catalogId }; 225 catalogItem.PageList.Add(pageItem); 226 } 227 } 228 rightList.Add(catalogItem); 229 } 230 #endregion 231 AccountInfo info = SessionManage.CurrentUser; 232 info.NavigationList = navigationList; 233 info.RightList = rightList; 234 SessionManage.CurrentUser = info; 235 return true; 236 } 237 return false; 238 } 239 catch (Exception e1) 240 { 241 Log.SaveErrorLog(e1.ToString(), "设置用户权限出错"); 242 return false; 243 } 244 } 245 } 246 }
1.4界面运行截图
1.5验证码生成控制器
验证码的处理方式也是一个控制器,它对应一个视图页面。视图页面中不需要编写代码,只是需要视图这个文件。供登录模块调用。对于视图的生成,只需要右键ValidateCode,点击添加视图就可以了。
public ActionResult ValidateCode()
{
CreateCheckCodeImage(GenerateCheckCode());
return View();
}
验证码生成的控制器代码如下:

1 using System; 2 using System.Collections.Generic; 3 using System.Drawing; 4 using System.Linq; 5 using System.Web; 6 using System.Web.Mvc; 7 8 namespace Controllers 9 { 10 /// <summary> 11 /// **************************** 12 /// 功能:验证码 13 /// 作者:王令 14 /// 时间:2015-7-01 15 /// 邮箱:1129137758@qq.com 16 /// **************************** 17 18 public class ValidateCodeController : Controller 19 { 20 public ActionResult ValidateCode() 21 { 22 CreateCheckCodeImage(GenerateCheckCode()); 23 return View(); 24 } 25 26 27 /// <summary> 28 /// 生成验证码 29 /// </summary> 30 /// <returns></returns> 31 private string GenerateCheckCode() 32 { 33 int number; 34 char code; 35 string checkCode = String.Empty; 36 System.Random random = new Random(); 37 38 bool F = true; 39 while (F) 40 { 41 number = random.Next(); 42 if (number % 2 == 0) 43 code = (char)('0' + (char)(number % 10)); 44 else 45 code = (char)('A' + (char)(number % 26)); 46 47 if (code == '0' || code == 'O' || code == '1' || code == 'I' || code == '2' || code == 'Z') 48 { 49 //排序0、O、1、L等字符 50 } 51 else 52 { 53 checkCode += code.ToString(); 54 if (checkCode.Length == 4) 55 { 56 F = false; 57 } 58 } 59 } 60 61 Session.Add("CheckCode", checkCode); 62 return checkCode; 63 } 64 65 66 67 /// <summary> 68 /// 生成验证码图片 69 /// </summary> 70 /// <param name="checkCode"></param> 71 private void CreateCheckCodeImage(string checkCode) 72 { 73 if (checkCode == null || checkCode.Trim() == String.Empty) 74 return; 75 System.Drawing.Bitmap image = new System.Drawing.Bitmap((int)Math.Ceiling((checkCode.Length * 12.5)), 22); 76 Graphics g = Graphics.FromImage(image); 77 78 try 79 { 80 //生成随机生成器 81 Random random = new Random(); 82 83 //清空图片背景色 84 g.Clear(Color.White); 85 86 //画图片的背景噪音线 87 for (int i = 0; i < 25; i++) 88 { 89 int x1 = random.Next(image.Width); 90 int x2 = random.Next(image.Width); 91 int y1 = random.Next(image.Height); 92 int y2 = random.Next(image.Height); 93 g.DrawLine(new Pen(Color.Silver), x1, y1, x2, y2); 94 } 95 // 96 Font font = new System.Drawing.Font("Arial", 12, (System.Drawing.FontStyle.Bold | System.Drawing.FontStyle.Italic)); 97 System.Drawing.Drawing2D.LinearGradientBrush brush = new System.Drawing.Drawing2D.LinearGradientBrush(new Rectangle(0, 0, image.Width, image.Height), Color.Blue, Color.DarkRed, 1.2f, true); 98 g.DrawString(checkCode, font, brush, 2, 2); 99 100 //画图片的前景噪音点 101 for (int i = 0; i < 100; i++) 102 { 103 int x = random.Next(image.Width); 104 int y = random.Next(image.Height); 105 image.SetPixel(x, y, Color.FromArgb(random.Next())); 106 } 107 108 //画图片的边框线 109 g.DrawRectangle(new Pen(Color.Silver), 0, 0, image.Width - 1, image.Height - 1); 110 System.IO.MemoryStream ms = new System.IO.MemoryStream(); 111 image.Save(ms, System.Drawing.Imaging.ImageFormat.Gif); 112 Response.ClearContent(); 113 Response.ContentType = "image/Gif"; 114 Response.BinaryWrite(ms.ToArray()); 115 } 116 117 finally 118 { 119 g.Dispose(); 120 image.Dispose(); 121 122 } 123 } 124 125 } 126 }
喜欢请赞赏一下啦^_^
微信赞赏
支付宝赞赏
作者:@wuya11
本文为作者原创,转载请注明出处:https://www.cnblogs.com/wlandwl/p/Login.html
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。
限于本人有限的知识水平,文中可能存在误解或错误(轻喷~),欢迎指出。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?