Java Web 开发进阶案例之人事管理系统的完整实现
概述
详细
一、概述
本系统的主要任务是实现人事管理系统的系统化和自动化管理, 主要包括招聘入
职、到期离职和员工调动信息管理等功能。具体完成的功能如下:
员工信息管理:员工信息查询、员工入职登记,员工信息更改、删除等
员工调动管理:员工调动登记、员工调动记录查询,员工调动信息更改、删除等
银行账户管理:
公司银行账户管理:公司银行账户登记和公司银行账户信息更改。
员工银行账户管理:员工银行账户登记、员工银行账户更改和删除等
公司信息管理:
职位类别管理:职位类别添加、职位类别信息更改、删除等
部门信息管理:部门类别添加、部门信息更改、删除等
系统管理:
登录密码修改模块
管理员管理模块:管理员添加、管理员信息更改和管理员删除等
系统帮助:关于使用该系统的一些帮助文档信息。
二、 开发平台与环境
本系统是 基于 B/S 平台的系统,利用 jsp + servlet+ javabeans 技术,基于 MVC 三层模式,
开发工具使用 MyEclipse 8.5 集成开发环境,数据库管理工具 SQL Server 2005 等。
三、系统的分析、设计与实现
1 系统分析:
(1) 角色分析:由于该系统属于典型的 MIS 管理系统,系统主要使用对象是管理员,分
为两类普通管理员和系统管理员;普通管理员只具有一般的查看、查询和检索权限;
系统管理员除了普通管理员所具有的权限外,还有高级权限:对所有信息做出更改,
删除等权限功能。
(2) 用例分析:根据用例分析所得到如下系统用例图:
2 系统设计:
2.1 数据库设计:
根据系统的分析与设计,如下设计数据库的数据表和数据结构:
2.1.1 逻辑表设计:
管理员表: id,登录名,登录密码,管理员类型号
管理员类型表: id,管理员类型号,管理员类型
员工基本信息表: id,员工编号,姓名,性别,出生日期,职位,部门,工资,入职时
间,身份证号
职位类别表: id,岗位编号,岗位名称,岗位工资
部门信息表: id,部门编号,部门名称,部门职能,部门领导
职位设置表: id,部门编号,岗位编号)
员工调动表: id,职工编号,原职位,调任职位,原部门编号,调任部门编号,性质,备
注
公司基本信息表: id,公司名称,公司性质,法人代表,公司地址,电话,邮箱:
公司银行账户表: id,银行账号,银行账户类型,开户行,备注:
员工银行账户表: id,员工编号,银行账号,银行账户,开户行,备注:
2.1.2 数据字典设计:
数据字典:
管理员表(admins)
字段 |
类型 |
备注 |
id |
Int |
主键,自增 |
aName |
Nvarchar(20) |
管理员登录名 |
aPwd |
Nvarchar(20) |
管理员登陆密码 |
aTypeId |
Int |
管理员类型号 |
管理员类型表(admintype)
字段 |
类型 |
备注 |
id |
Int |
主键,自增 |
aTypeId |
Nvarchar(20) |
管理员类型 |
aTypeName |
Nvarchar(20) |
管理员类型名称 |
员工基本信息表(staff)
字段 |
类型 |
备注 |
id |
Int |
主键,自增 |
sId |
Nvarchar(14) |
员工编号 |
sName |
Nvarchar(20) |
员工姓名 |
sSex |
Nvarchar(2) |
员工性别 |
sBirthday |
Datetime |
出生日期 |
sPost |
Nvarchar(20) |
职位,岗位 |
sDepartment |
Nvarchar(20) |
部门 |
sSalary |
Int |
工资 |
sEntry |
Datetime |
入职时间 |
sIdentityId |
Nvarchar(20) |
身份证号 |
职位类别表(postcategory)
字段 |
类型 |
备注 |
id |
Int |
主键,自增 |
pId |
Nvarchar(20) |
职位编号 |
pName |
Nvarchar(20) |
职位名称 |
pSalary |
int |
职位工资 |
部门信息表(department)
字段 |
类型 |
备注 |
id |
Int |
主键,自增 |
dId |
Nvarchar(20) |
部门编号 |
dName |
Nvarchar(20) |
部门名称 |
dFunction |
Text |
部门职能 |
dLeader |
Nvarchar(20) |
部门领导(经理) |
职位设置表(postsetting)
字段 |
类型 |
备注 |
id |
Int |
主键,自增 |
pId |
Nvarchar(20) |
职位编号 |
dId |
Nvarchar(20) |
部门编号 |
员工调动表(staffchange)
字段 |
类型 |
备注 |
id |
Int |
主键,自增 |
sId |
Nvarchar(10) |
员工编号 |
pIdOld |
Nvarchar(20) |
原职位编号 |
pIdNew |
Nvarchar(20) |
调任职位编号 |
dIdOld |
Nvarchar(20) |
原部门编号 |
dIdNew |
Nvarchar(20) |
调任部门编号 |
sNature |
Nvarchar(20) |
性质(长期或短期) |
sRemark |
Text |
备注 |
公司基本信息表(company)
字段 |
类型 |
备注 |
id |
Int |
主键 |
cName |
Nvarchar(50) |
公司名称 |
cNature |
Nvarchar(20) |
公司性质 |
cLegalPerson |
Nvarchar(20) |
法人代表 |
cAddress |
Nvarchar(100) |
公司地址 |
cTel |
Nvarchar(20) |
电话 |
cEmail |
Nvarchar(40) |
邮箱 |
公司银行账户表(companybank)
字段 |
类型 |
备注 |
id |
Int |
主键 |
bId |
Nvarchar(30) |
银行账号 |
cBankType |
Nvarchar(20) |
银行账户类型 |
cBankName |
Nvarchar(60) |
开户行 |
cRemark |
Text |
备注 |
员工银行账户表(staffbank)
字段 |
类型 |
备注 |
id |
Int |
主键,自增 |
sId |
Nvarchar(20) |
员工编号 |
bId |
Nvarchar(30) |
银行账号 |
cBankType |
Nvarchar(20) |
银行账户类型 |
cBankName |
Nvarchar(60) |
开户行 |
cRemark |
Text |
备注 |
2.2 系统设计思想
(1)系统界面设计
由于该系统是个典型的 MIS 管理系统,本系统采用框架页面设计模式。常用
的头部和尾巴设计成单独模块,提高开发效率,提高模块重用性,方便系统升级。
(2)系统架构模式
本系统采用的是基于 Web 的 Java EE MVC 三层架构模式。
三层架构模式的原理图如下:
而将 MVC 三层模式应用于 Java EE 中,就是基于 JSP+Servlet+javabeans,其中, JSP 是视图,
servlet 是控制器,而 javabeans 则是模型。原理图如下:
3 系统实现:
(1)公用模块:
Commons Dbutils jar 包:该包是 apache 开发的一个用来常用操作的工具包。
字节编码过滤功能:该模块是使用过滤器技术对所有 servlet 编码设置进行过滤,主要用来解
决 jsp 中汉字问题。核心代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | public class CharacterEncodingFilter implements Filter { private FilterConfig config ; private String encoding= "ISO-8859-1" ; public void destroy() { // TODO Auto-generated method stub config = null ; } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { // TODO Auto-generated method stub request.setCharacterEncoding(encoding); chain.doFilter(request, response); } public void init(FilterConfig config) throws ServletException { // TODO Auto-generated method stub this .config = config; String s = config.getInitParameter( "encoding" ); if (s!= null ){ encoding = s; } } } |
Tomcat 数据库连接池技术:本系统采用 tomcat 数据池技术,便于数据库的配置和访问。 具
体配置是在 tomcat 安装目录下的 conf 文件下的 context.xml 文件中添加如下代码:
1 2 3 4 5 6 7 8 9 10 11 | < Resource name="jdbc/permanageds" auth="Container" type="javax.sql.DataSource" maxActive = "100" maxIdle="30" maxWait="10000" username="sa" password="caizhiming" driverClassName="com.microsoft.sqlserver.jdbc.SQLServerDriver" url="jdbc:sqlserver://localhost:1433;DatabaseName=permanage" /> |
SQL 数据库操作工具类:该类主要用来处理与数据库相关的操作,核心代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | public class SqlDbUtil { private String dataSourceName; private DataSource ds; public SqlDbUtil() { // TODO Auto-generated constructor stub } public SqlDbUtil(String dataSourceName){ this .dataSourceName = dataSourceName; } public void setDataSourceName(String dataSourceName) { this .dataSourceName = dataSourceName; } public void setDs(DataSource ds) { this .ds = ds; } //初始化数据源 public void init(){ try { Context initContext = new InitialContext(); this .ds = (DataSource)initContext.lookup(dataSourceName); initContext.close(); } catch (NamingException e) { // TODO Auto-generated catch block e.printStackTrace(); } } //更新数据库语句,结果返回int类型 public int update(String sql,String[] params){ QueryRunner qr = new QueryRunner(ds); int result = 0 ; try { if (params!= null ) result = qr.update(sql, params); else { result = qr.update(sql); } } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } return result; } //更新数据库语句,结果返回为Object类型,创建javabeans类型时用 public Object query(String sql,String[] params,ResultSetHandler rsh){ QueryRunner qr = new QueryRunner(ds); Object results = null ; try { results=qr.query(sql, params, rsh); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } return results; } } |
字符串工具类: 该类主要用类处理一些字符串相关操作等所用到的工具方法,相关核心代码
如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | public class StringUtil { //验证字符串是否为空 public static boolean validateNull(String s){ if (s== null || s.length()== 0 ) return true ; else { return false ; } } //改变空字符串 public static String changeNull(String source,String target){ if (source== null || source.length()== 0 || source.equalsIgnoreCase( "null" )) return target; else { return source; } } //过滤 html标记符 public static String filterHtml(String s){ if (s== null ) return null ; if (s.length()== 0 ) return s; s.replaceAll( "&" , "&" ); s.replaceAll( "<" , "<" ); s.replaceAll( ">" , ">" ); s.replaceAll( " " , " " ); s.replaceAll( "'" , "';" ); s.replaceAll( "\"" , "" "); s.replaceAll( "\n" , "<br/>" ); return s; } } |
(2) 系统登录模块实现: 该模块是系统的使用入口,通过该入口进入系统。 登录验证采用
Session 验证机制。
核心代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | String path =request.getContextPath(); request.setCharacterEncoding( "utf-8" ); int typeId = 1 ; String name = request.getParameter( "name" ).trim(); String pwd = request.getParameter( "pwd" ); String type = request.getParameter( "type" ); if (type.equalsIgnoreCase( "普通管理员" )) typeId = 0 ; String strCmd = "select * from admins where aName='" +name+"' and aPwd= '"+pwd+"' and aTypeId= '"+typeId+"' "; SqlDbUtil dbUtil = new SqlDbUtil( "java:/comp/env/jdbc/permanageds" ); dbUtil.init(); List list = (List)dbUtil.query(strCmd, null , new BeanListHandler(Admins. class )); if (list.size()!= 0 ){ HttpSession session = request.getSession(); Admins ad = (Admins)list.get( 0 ); session.setAttribute( "aId" , ad.getId()); session.setAttribute( "name" , name); session.setAttribute( "type" , type); response.sendRedirect(path+ "/index.jsp" ); } else { response.sendRedirect(path+ "/login.jsp" ); request.setAttribute( "msg" , "用户名或密码错误,请重新登录!" ); } |
登录界面如下:
(3) 员工信息管理模块实现
该模块主要有员工信息查询、入职登记以及修改删除等。
员工信息查询主要是通过搜索检索查询员工的信息,查询结果分页形式显示出来,
具体核心代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 | String text = "%" +request.getParameter( "search" ).trim()+ "%" ; String type = request.getParameter( "type" ).trim(); SqlDbUtil dbUtil = new SqlDbUtil( "java:/comp/env/jdbc/permanageds" ); dbUtil.init(); //每页显示记录数 int PageSize = 8 ; int StartRow = 0 ; //开始显示记录的编号 int PageNo = 0 ; //需要显示的页数 int CounterStart = 0 ; //每页页码的初始值 int CounterEnd = 0 ; //显示页码的最大值 int RecordCount = 0 ; //总记录数; int MaxPage = 0 ; //总页数 int PrevStart = 0 ; //前一页 int NextPage = 0 ; //下一页 int LastRec = 0 ; //int LastStartRecord = 0;//最后一页开始显示记录的编号 //获取需要显示的页数,由用户提交 if (request.getParameter( "page" ) == null ) { //如果为空,则表示第1 页 if (StartRow == 0 ) { PageNo = StartRow + 1 ; //设定为1 } } else { PageNo = Integer.parseInt(request.getParameter( "page" )); // 获得用户提交的页数 StartRow = (PageNo - 1 ) * PageSize; //获得开始显示的记录编号 } //因为显示页码的数量是动态变化的,假如总共有一百页,则不可能同时显示100个链 接。而是根据当前的页数显示 //一定数量的页面链接 //设置显示页码的初始值!! if (PageNo % PageSize == 0 ) { CounterStart = PageNo - (PageSize - 1 ); } else { CounterStart = PageNo - (PageNo % PageSize) + 1 ; } CounterEnd = CounterStart + (PageSize - 1 ); String strSql1 = null ; if (type.equals( "sId" )) strSql1 = "select * from staff where sId like '" +text+ "'" ; if (type.equals( "sName" )) strSql1 = "select * from staff where sName like '" +text+ "'" ; if (text.isEmpty()) strSql1 = "select * from staff " ; List list = (List) dbUtil.query(strSql1, null , new BeanListHandler(Staff. class )); RecordCount = list.size(); //取特定页数的数据 String strColumn = " * " ; String strTable = " staff " ; String strSort = " id desc " ; String PKID = "id" ; String strSql2 = "" ; String strWhere = "where " + type + " like " + "'" +text+ "'" ; String START_ID = Integer.toString((PageNo - 1 ) * PageSize + 1 ); String END_ID = Integer.toString(PageNo * PageSize); strSql2 = " SELECT " + strColumn + " FROM (SELECT ROW_NUMBER() OVER(ORDER BY " + strSort + ") AS rownum," + strColumn + " FROM " + strTable + " WITH(NOLOCK) " + strWhere + ") AS D WHERE rownum BETWEEN " + START_ID + " AND " + END_ID + " ORDER BY " + strSort; //获取总页数 MaxPage = RecordCount % PageSize; if (RecordCount % PageSize == 0 ) { MaxPage = RecordCount / PageSize; } else { MaxPage = RecordCount / PageSize + 1 ; } List list2 = (List) dbUtil.query(strSql2, null , new BeanListHandler(Staff. class )); request.setAttribute( "staff" , list2); request.setAttribute( "PageSize" , PageSize); request.setAttribute( "StartRow" , StartRow); request.setAttribute( "PageNo" , PageNo); request.setAttribute( "CounterStart" , CounterStart); request.setAttribute( "CounterEnd" , CounterEnd); request.setAttribute( "RecordCount" , RecordCount); request.setAttribute( "MaxPage" , MaxPage); request.setAttribute( "PrevStart" , PrevStart); request.setAttribute( "NextPage" , NextPage); request.setAttribute( "LastRec" , LastRec); request.getRequestDispatcher( "/staff/staffsearch.jsp" ).forward(re quest, response); |
系统主界面如下:
入职登记是登记新入职员工信息,具体核心代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | String path = request.getContextPath(); int result = 0 ; String sId = null ; String sName = request.getParameter( "sName" ).trim(); String sSex = request.getParameter( "sSex" ).trim(); String birth=request.getParameter( "birthyear" )+ "-" +request.getParameter("bir thmon"); birth+= "-" +request.getParameter( "birthday" ); String job = request.getParameter( "jobyear" )+ "-" +request.getParameter( "jobmon" ); job += "-" +request.getParameter( "jobday" ); Date birthDate = null ; Date jobDate = null ; Date sIdDate= null ; SimpleDateFormat sdf = new SimpleDateFormat( "yyyy-MM-dd" ); SimpleDateFormat sdf2= new SimpleDateFormat( "yyyyMMddHHmmss" ); try { birthDate = sdf.parse(birth); jobDate = sdf.parse(job); sId = sdf2.format( new java.util.Date()); } catch (ParseException e) { // TODO Auto-generated catch block e.printStackTrace(); } String pId = request.getParameter( "sPost" ).trim(); // 获取的是职位 对应的职位编号 String sSalary = request.getParameter( "sSalary" ).trim(); String sIdentityId = request.getParameter( "sIdentityId" ).trim(); SqlDbUtil db = new SqlDbUtil( "java:/comp/env/jdbc/permanageds" ); db.init(); String strCmd2 = "select * from postsetting where pId ='" + pId + "'" ; List list2 = (List) db.query(strCmd2, null , new BeanListHandler(Postsetting. class )); Postsetting ps = (Postsetting) list2.get( 0 ); String dId = ps.getdId(); String strCmd4 = "select * from postcategory where pId ='" + pId + "'" ; List list4 = (List) db.query(strCmd4, null , new BeanListHandler(Postcategory. class )); Postcategory pc = (Postcategory) list4.get( 0 ); String sPost =pc.getpName(); String strCmd3 = "select * from department where dId ='" + dId + "'" ; List list3 = (List) db.query(strCmd3, null , new BeanListHandler(Department. class )); Department dt = (Department) list3.get( 0 ); String sDepartment = dt.getdName(); // 获取到职位所隶属的部门名称 String strCmd = "insert into staff (sId,sName,sSex,sBirthday,sPost,sDepartment,sSalary,sEntry,sIdentityI d) values(?,?,?,?,?,?,?,?,?)"; String[] params = { sId, sName, sSex, sdf.format(birthDate), sPost,sDepartment, sSalary, sdf.format(jobDate), sIdentityId }; for ( int i= 0 ;i< 100000 ;i++){ result = db.update(strCmd, params); } if (result == 0 ) { String message = "对不起,内容更新失败,请重新更新! <br/>" ; request.getRequestDispatcher( "/company/addpostcategory.jsp" ) .forward(request, response); } else { response.sendRedirect(path + "/servlet/manageStaff" ); } |
员工信息管理主要是负责员工信息的修改和删除功能。
信息修改的核心代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | String path = request.getContextPath(); int result = 0 ; request.setCharacterEncoding( "utf-8" ); String sName = request.getParameter( "sName" ).trim(); String sSex = request.getParameter( "sSex" ).trim(); String birth=request.getParameter( "birthyear" )+ "-" +request.getParameter("bir thmon"); birth+= "-" +request.getParameter( "birthday" ); String job = request.getParameter( "jobyear" )+ "-" +request.getParameter( "jobmon" ); job += "-" +request.getParameter( "jobday" ); Date birthDate = null ; Date jobDate = null ; SimpleDateFormat sdf = new SimpleDateFormat( "yyyy-MM-dd" ); try { try { birthDate = sdf.parse(birth); jobDate = sdf.parse(job); } catch (java.text.ParseException e) { // TODO Auto-generated catch block e.printStackTrace(); } } catch (ParseException e) { // TODO Auto-generated catch block e.printStackTrace(); } String sId = request.getParameter( "sId" ).trim(); String pId = request.getParameter( "sPost" ).trim(); // 获取的是职位 对应的职位编号 String sSalary = request.getParameter( "sSalary" ).trim(); String sIdentityId = request.getParameter( "sIdentityId" ).trim(); SqlDbUtil db = new SqlDbUtil( "java:/comp/env/jdbc/permanageds" ); db.init(); String strCmd2 = "select * from postsetting where pId ='" + pId + "'" ; List list2 = (List) db.query(strCmd2, null , new BeanListHandler(Postsetting. class )); Postsetting ps = (Postsetting) list2.get( 0 ); String dId = ps.getdId(); String strCmd4 = "select * from postcategory where pId ='" + pId + "'" ; List list4 = (List) db.query(strCmd4, null , new BeanListHandler(Postcategory. class )); Postcategory pc = (Postcategory) list4.get( 0 ); String sPost =pc.getpName(); String strCmd3 = "select * from department where dId ='" + dId + "'" ; List list3 = (List) db.query(strCmd3, null , new BeanListHandler(Department. class )); Department dt = (Department) list3.get( 0 ); String sDepartment = dt.getdName(); // 获取到职位所隶属的部门名称 String strCmd="update staff set sName=?, sSex=?, sBirthday=?, sPost=?, sDepartment=?, sSalary=?, sEntry=?, sIdentityId=? where sId= '"+sId+"' "; String[] params = { sName, sSex, sdf.format(birthDate), sPost,sDepartment, sSalary, sdf.format(jobDate), sIdentityId }; result = db.update(strCmd, params); if (result == 0 ) { String message = "对不起,内容更新失败,请重新更新! <br/>" ; request.getRequestDispatcher( "/staff/addstaff.jsp" ) .forward(request, response); } else { response.sendRedirect(path + "/servlet/manageStaff" ); //request.getRequestDispatcher("/servlet/manageStaff").forward(re quest, response); } |
删除功能的核心代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 | String path = request.getContextPath(); int result = 0 ; request.setCharacterEncoding( "utf-8" ); String sId = request.getParameter( "sId" ).toString(); //String strCmd = "delete from where id='"+id+"'"; String strCmd = "delete from staff where sId='" +sId+ "'" ; SqlDbUtil dbUtil = new SqlDbUtil( "java:/comp/env/jdbc/permanageds" ); dbUtil.init(); result = dbUtil.update(strCmd, null ); if (result > 0 ) response.sendRedirect(path+ "/servlet/manageStaff" ); |
(4) 员工调动信息管理模块实现
该模块主要是实现员工调动信息的管理,主要包括员工调动信息登记,更改和删除,
相关核心代码与员工信息管理类似,详见源代码。 实现界面如下:
(5) 银行账户信息模块实现
该模块主要是实现公司银行和员工工资账户的管理,主要包括公司银行账户的创建、
查看和修改以及员工工资账户的登记、修改和删除等功能,相关核心代码与员工信
息管理类似, 在此不累赘, 详见源代码。 实现界面如下:
(6) 公司信息管理模块实现
该模块主要是实现公司相关信息的管理,主要包括公司基本信息的查看和更新、公
司部门信息管理以及公司职位类别管理, 该模块的相关核心代码实现与员工信息管
理类似, 在此不累赘, 详见源代码。
(7) 系统管理模块实现
该模块主要是实现系统管理员的管理,主要包括管理员添加、更新和删除等功能,
相关核心代码与上面的类似,在此不累赘,详见源代码。 实现界面如下:
四、项目代码目录结构图
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· 使用C#创建一个MCP客户端
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· Windows编程----内核对象竟然如此简单?