Asp.Net Core 2.0 项目实战(11) 基于OnActionExecuting全局过滤器,页面操作权限过滤控制到按钮级
Asp.Net Core 2.0 项目实战(1) NCMVC开源下载了
Asp.Net Core 2.0 项目实战(2)NCMVC一个基于Net Core2.0搭建的角色权限管理开发框架
Asp.Net Core 2.0 项目实战(3)NCMVC角色权限管理前端UI预览及下载
Asp.Net Core 2.0 项目实战(4)ADO.NET操作数据库封装、 EF Core操作及实例
Asp.Net Core 2.0 项目实战(5)Memcached踩坑,基于EnyimMemcachedCore整理MemcachedHelper帮助类。
Asp.Net Core 2.0 项目实战(6)Redis配置、封装帮助类RedisHelper及使用实例
Asp.Net Core 2.0 项目实战(7)MD5加密、AES&DES对称加解密
Asp.Net Core 2.0 项目实战(8)Core下缓存操作、序列化操作、JSON操作等Helper集合类
Asp.Net Core 2.0 项目实战(9) 日志记录,基于Nlog或Microsoft.Extensions.Logging的实现及调用实例
Asp.Net Core 2.0 项目实战(10) 基于cookie登录授权认证并实现前台会员、后台管理员同时登录
Asp.Net Core 2.0 项目实战(11) 基于OnActionExecuting全局过滤器,页面操作权限过滤控制到按钮级
1.权限管理
权限管理的基本定义:百度百科。
基于《Asp.Net Core 2.0 项目实战(10) 基于cookie登录授权认证并实现前台会员、后台管理员同时登录》我们做过了登录认证,登录是权限的最基础的认证,没有登录就没有接下来的各种操作权限管理,以及数据权限管理(暂不探讨),这里我们把登录当作全局权限,进入系统后再根据不同的角色或者人员,固定基本功能的展示,当不同的角色要对功能操作时,就需要验证操作权限,如:查看/添加/修改/删除,也就是我们常说的控制到按钮级。下面让我们一步一步来操作实现一下,本篇提供一种权限过滤思路,欢迎讨论指正,全局过滤代码基类已经实现,相关控制页面还在紧急编码中,时间少任务重,希望大家多体谅。
内容略长:请耐心浏览。
2.约定大于配置
约定优于配置,也称作按约定编程,是一种软件设计范式,旨在减少软件开发人员需做决定的数量,获得简单的好处,而又不失灵活性。与之对应的就是mvc下控制器和视图的关系。
本质是说,开发人员仅需规定应用中不符约定的部分。例如,如果模型中有个名为Sale的类,那么数据库中对应的表就会默认命名为sales。只有在偏离这一约定时,例如将该表命名为”products_sold”,才需写有关这个名字的配置。
为了方便项目快速构建,数据库我们这里先使用dtcms 5.0的数据库相关表navigation。EF Core生成Model备用。
a) 首先约定后台Controller和Action命名约定,以及属性Attribute类定义
##菜单约定##
1.nav_name尽量使用controller
2.所有英文小写
3.最后一级url不能为空
##方法定义约定##
1.属性全nav_name,action_type
2.属性只有nav_name,判断Action和参数是否为空
3.属性只有action_type,控制器名做nav_name
4.根据控制器+Action判断
5.不是标准方法必须加属性nav_name
6.控制器标准,保存Action方法不标准,需要传标准参数
b) 定义操作枚举Enum
using System;
using System.Collections.Generic;
using System.Text; namespace NC.Common
{
public class JHEnums
{ /// <summary>
/// 统一管理操作枚举
/// </summary>
public enum ActionEnum
{
/// <summary>
/// 所有
/// </summary>
All,
/// <summary>
/// 显示
/// </summary>
Show,
/// <summary>
/// 查看
/// </summary>
View,
/// <summary>
/// 添加
/// </summary>
Add,
/// <summary>
/// 修改
/// </summary>
Edit,
/// <summary>
/// 删除
/// </summary>
Delete,
/// <summary>
/// 审核
/// </summary>
Audit,
/// <summary>
/// 回复
/// </summary>
Reply,
/// <summary>
/// 确认
/// </summary>
Confirm,
/// <summary>
/// 取消
/// </summary>
Cancel,
/// <summary>
/// 作废
/// </summary>
Invalid,
/// <summary>
/// 生成
/// </summary>
Build,
/// <summary>
/// 安装
/// </summary>
Instal,
/// <summary>
/// 卸载
/// </summary>
UnLoad,
/// <summary>
/// 登录
/// </summary>
Login,
/// <summary>
/// 备份
/// </summary>
Back,
/// <summary>
/// 还原
/// </summary>
Restore,
/// <summary>
/// 替换
/// </summary>
Replace,
/// <summary>
/// 复制
/// </summary>
Copy
}
}
JHEnums
c) 获取操作权限
#region 操作权限菜单
/// <summary>
/// 获取操作权限
/// </summary>
/// <returns>Dictionary</returns>
public static Dictionary<string, string> ActionType()
{
Dictionary<string, string> dic = new Dictionary<string, string>();
dic.Add("Show", "显示");
dic.Add("View", "查看");
dic.Add("Add", "添加");
dic.Add("Edit", "修改");
dic.Add("Delete", "删除");
dic.Add("Audit", "审核");
dic.Add("Reply", "回复");
dic.Add("Confirm", "确认");
dic.Add("Cancel", "取消");
dic.Add("Invalid", "作废");
dic.Add("Build", "生成");
dic.Add("Instal", "安装");
dic.Add("Unload", "卸载");
dic.Add("Back", "备份");
dic.Add("Restore", "还原");
dic.Add("Replace", "替换");
return dic;
}
#endregion
Utils.ActionType()
d) Action属性类定义
using Microsoft.AspNetCore.Mvc.Filters;
using System; namespace NC.Lib
{
/// <summary>
/// nav_name
/// </summary>
public class NavAttr : Attribute, IFilterMetadata
{
public NavAttr() { }
public NavAttr(string navName, string actionType)
{
this.NavName = navName;
this.ActionType = actionType;
}
public string NavName { set; get; }//菜单名称
public string ActionType { set; get; } //操作类型
}
}
3. 全局过滤实现
3.1 首先定义一个基Controller
定义好基AdminBase控制器后,所有的后台域Controller都继承此类
3.2 首先验证用户是否登录
Session相关参考3.5 Session操作
/// <summary>
/// 判断管理员是否已经登录
/// </summary>
public bool IsAdminLogin()
{
var bSession = HttpContext.Session.Get(AdminAuthorizeAttribute.AdminAuthenticationScheme);
if (bSession == null)
{
return false;
}
siteAdminInfo = ByteConvertHelper.Bytes2Object<JhManager>(bSession);
//如果Session为Null
if (siteAdminInfo != null)
{
return true;
}
else
{
//检查Cookies
var cookieAdmin = HttpContext.AuthenticateAsync(AdminAuthorizeAttribute.AdminAuthenticationScheme);
cookieAdmin.Wait();
var adminname = cookieAdmin.Result.Principal.Claims.FirstOrDefault(x => x.Type == "AdminName")?.Value;
var adminpwd = cookieAdmin.Result.Principal.Claims.FirstOrDefault(x => x.Type == "AdminPwd")?.Value; if (adminname != "" && adminpwd != "")
{
JhManager model = dblEf.JhManager.Where(m => m.UserName == adminname && m.Password == adminpwd).FirstOrDefault();
if (model != null)
{
HttpContext.Session.Set(AdminAuthorizeAttribute.AdminAuthenticationScheme, ByteConvertHelper.Object2Bytes(model));//存储session
bSession = HttpContext.Session.Get(AdminAuthorizeAttribute.AdminAuthenticationScheme);
siteAdminInfo = ByteConvertHelper.Bytes2Object<JhManager>(bSession);
return true;
}
}
}
return false;
}
3.3 OnActionExecuting重载方法实现过滤
首先验证登录,然后判断需要过滤的area,判断是否有跳过属性(SkipAdminAuthorizeAttribute,做登录的时候定义过),最后判断菜单和按钮的权限。
这里需要注意的是,OnActionExecuting和AdminAuthorizeAttribute. OnAuthorization的执行顺序,有的网友博客看到的是OnActionExcuting先执行,我这里测试的是先验证属性OnAuthorization,再执行OnActionExecuting;可能附加条件不同,这里不做过多探讨,调试的时候大家可试试。
/// <summary>
/// 创建过滤器:***全局过滤器*** 过滤除登录登出等操作权限验证
/// </summary>
/// <param name="context"></param>
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
//1.验证是否登录
//2.验证菜单权限
//3.验证按钮权限
//在action执行之前 //判断是否加有SkipAdmin标签
var skipAuthorize = filterContext.ActionDescriptor.FilterDescriptors.Where(a => a.Filter is SkipAdminAuthorizeAttribute).Any();
if (!skipAuthorize)
{
//是否系统管理文件夹里文件,Areas》ad_min
var isPermission = false;
//获取controller和action
var route = filterContext.RouteData.Values; string strArea = route["area"].ToString();//获取区域的名字,ad_min区域下的都需要权限验证
if (strArea != null && strArea.Equals("ad_min"))
{
isPermission = true;
}
//需要验证权限
if (isPermission) {
var currController = route["controller"].ToString();
var curraction = route["action"].ToString();
var exceptCtr = UtilConf.Configuration["Site:exceptCtr"].Replace(",", ",");//防止中文逗号
var exceptAction = UtilConf.Configuration["Site:exceptAction"].Replace(",", ",");//防止中文逗号
//判断是否有例外控制器或Action校验是否例外,跳过验证
if (!exceptCtr.Contains(currController.ToLower()) && !exceptAction.Contains(curraction.ToLower()))
{
//验证是否登录
if (!IsAdminLogin())
{
string msg = string.Format("未登录或登录超时,请重新登录!");
filterContext.Result = new RedirectResult("~/ad_min/login?msg=" + WebUtility.UrlEncode(msg));
return;
}
//验证菜单权限
//验证按钮权限
//自定义方法属性
try
{
//获取属性
NavAttr actionAttr = filterContext.ActionDescriptor.FilterDescriptors.Where(a => a.Filter is NavAttr).Select(a => a.Filter).FirstOrDefault() as NavAttr;
string strNavName = string.Empty;
string strActionType = string.Empty;
if (actionAttr == null)
{
actionAttr = filterContext.ActionDescriptor.FilterDescriptors.GetType().GetCustomAttributes<NavAttr>().FirstOrDefault() as NavAttr;
}
if (actionAttr != null)
{
strNavName = actionAttr.NavName;
strActionType = actionAttr.ActionType;
}
//获取参数,由于action在mvc中属于关键词,所以使用act当作操作方式参数
string paramAction = "";
//paramAction = Request.Query["action"].ToString();
if (string.IsNullOrEmpty(paramAction))
{
if (route["act"] != null)
{
paramAction = route["act"].ToString();
}
}
if (siteAdminInfo.RoleType != )//超管拥有所有权限
{
if (!ChkPermission(siteAdminInfo.RoleId, currController, curraction, strNavName, strActionType, paramAction))
{
TempData["Permission"] = "您没有管理该页面的权限,请联系管理员!";
filterContext.Result = new RedirectResult("~/ad_min/Home/Index");
return;
//返回固定错误json
}
else
{
TempData["Permission"] = null;
}
}
}
catch (System.Exception ex)
{
throw ex;
}
}
}
}
}
页面权限验证,首先获取到页面的Controller和Action以及Action上面是否包含相关属性NavAttr,校验数据库中是否包含对此属性的定义。
/// <summary>
/// 判断页面
/// </summary>
/// <param name="role_id">角色id</param>
/// <param name="currController">当前控制器</param>
/// <param name="currAction">当前</param>
/// <param name="navName">方法上的属性</param>
/// <param name="actionType">操作类型</param>
/// <param name="paramAction">当为操作方法是传递的参数</param>
/// <returns>没有权限返回false</returns>
public bool ChkPermission(int? role_id, string currController, string currAction, string navName, string actionType, string paramAction)
{
//1.未配置页面,在方法上加属性/ad_min/Settings/SysConfigSave
//2.控制器+Action /admin/sys_config/index,/admin/sys_config/add,/admin/sys_config/edit
//3.先判断已配置页面/admin/settings/sys_config
bool result = true;
var url = HttpContext.Request.Path.Value;
if (url.Contains("/ad_min/home/index"))//后台首页不验证
{
return result;
}
DataTable dt = chkPermission(role_id);
var action_type = actionType;
//属性不为空
if (!string.IsNullOrEmpty(navName) && !string.IsNullOrEmpty(actionType))//属性全
{
DataRow[] dr = dt.Select("nav_name='" + navName + "' and action_type='" + action_type + "'");
result = (dr.Count() > );
}
else if (!string.IsNullOrEmpty(navName) && string.IsNullOrEmpty(actionType))//属性只有nav_name
{
action_type = getActionType(currAction, paramAction);
DataRow[] dr = dt.Select("nav_name='" + navName + "' and action_type='" + action_type + "'");
result = (dr.Count() > );
}
else if (string.IsNullOrEmpty(currController) && !string.IsNullOrEmpty(actionType))//控制器名:nav_name,属性只有action_type
{
DataRow[] dr = dt.Select("nav_name='" + currController + "' and action_type='" + action_type + "'");
result = (dr.Count() > );
}
else
{
//约定大于配置
//控制器名:nav_name
//Action:action_type
if (!string.IsNullOrEmpty(currController) && !string.IsNullOrEmpty(currAction))
{
//控制器+action
if (currAction.ToLower() == "index")//首页为展示
{
currAction = "View";
}
DataRow[] dr = dt.Select("nav_name='" + currController + "' and (action_type='" + currAction + "')");
result = (dr.Count() > ); }
//属性全空,控制器+Action验证不通过,参数不空
if (!result && !string.IsNullOrEmpty(currController) && !string.IsNullOrEmpty(paramAction))//(控制器)+参数判断
{
//参数可为Edit,Add,Del...
DataRow[] dr = dt.Select("nav_name='" + currController + "' and action_type='" + paramAction + "'");
result = (dr.Count() > );
}
if (!result)//控制器+Action验证未通过
{
//配置页面处理
DataTable dtNav = GetNavCacheList("link_url='" + url + "'");//根据菜单URL,从缓存中检索调用ID
if (dtNav.Rows.Count > )
{
DataRow drNav = dtNav.Rows[];
string nav_name = drNav["name"].ToString();//nav_name
action_type = getActionType(currAction, paramAction); DataRow[] dr = dt.Select("nav_name='" + nav_name + "' and action_type='" + action_type + "'");
result = (dr.Count() > );
}
}
} return result;
}
/// <summary>
/// 判断是否有权限
/// </summary>
private DataTable chkPermission(int? role_id)
{
DataTable dt = CacheHelper.Get("permisson" + role_id) as DataTable;
if (dt == null)
{
string strSql = "SELECT mrv.nav_name,mrv.action_type FROM manager_role mr LEFT JOIN manager_role_value mrv ON mr.id=mrv.role_id WHERE mr.id=@role_id";
DbParameters p = new DbParameters();
p.Add("@role_id", role_id);
dt = Dbl.JHCMS.CreateSqlDataTable(strSql, p);
CacheHelper.Set("permisson" + role_id, dt);
}
return dt;
}
/// <summary>
/// 1.验证action是否标准约定
/// 2.根据action=''参数获取操作类型
/// </summary>
private string getActionType(string currAction, string paramAction)
{
if (currAction.ToLower().Contains("index") || currAction.ToLower().Contains("list"))//首先判断是否首页/列表等展示
{
return "View";
}
if (currAction.ToLower().Contains("save"))//如果包含保存save关键字,默认返回add
{
return string.IsNullOrEmpty(paramAction) ? "Add" : paramAction;
}
else if (currAction.ToLower().Contains("edit") || currAction.ToLower().Contains("update"))
{
return string.IsNullOrEmpty(paramAction) ? "Edit" : paramAction;
}
else if (currAction.ToLower().Contains("del"))
{
return string.IsNullOrEmpty(paramAction) ? "Delete" : paramAction;
}
//判断Action
if (!string.IsNullOrEmpty(currAction))
{
if (Utils.ActionType().ContainsKey(currAction))//首字母要大写,约定
return currAction;
}
return string.IsNullOrEmpty(paramAction) ? "View" : paramAction;
}
3.4 Controller中的约定
1.NavAttr属性全部定义
/// <summary>
/// 更新字典排序
/// </summary>
[NavAttr(NavName = "sys_navigation", ActionType = "Edit")]
public JsonResult UpdateNav(string id, string nav)
{}
2.NavAttr属性之定义NavName(对应数据库中的name)
[NavAttr(NavName = "sys_navigation"]
public JsonResult UpdateNav_Edit(string id, string nav)
{}
3.未定义Action属性,必须传递一个参数以确定操作类型
//(控制器)+参数判断 public class sys_navigationController : AdminBase
{
public JsonResult UpdateNav_Edit(string id, string nav)
{}
}
3.5 Session相关操作
Session使用需要先在startup.cs中进行配置注入,找到方法ConfigureServices注入Session
Configure中启用
在控制器中的操作,存储:
JhManager bUser = getUserInfoByNameAndPwd(AdminName, adminpwd, true);
HttpContext.Session.Set(AdminAuthorizeAttribute.AdminAuthenticationScheme, ByteConvertHelper.Object2Bytes(bUser));//存储session
读取:
var bSession =
HttpContext.Session.Get(AdminAuthorizeAttribute.AdminAuthenticationScheme);
if (bSession == null)
{
return false;
}
bUser= ByteConvertHelper.Bytes2Object<JhManager>(bSession);
ByteConvertHelper是byte转换帮助类
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Text; namespace NC.Common
{
/// <summary>
/// byte转换操作类,主要用于Session存储
/// </summary>
public class ByteConvertHelper
{
/// <summary>
/// 将对象转换为byte数组
/// </summary>
/// <param name="obj">被转换对象</param>
/// <returns>转换后byte数组</returns>
public static byte[] Object2Bytes(object obj)
{
byte[] serializedResult = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(obj));
return serializedResult;
} /// <summary>
/// 将byte数组转换成对象
/// </summary>
/// <param name="buff">被转换byte数组</param>
/// <returns>转换完成后的对象</returns>
public static object Bytes2Object(byte[] buff)
{
return JsonConvert.DeserializeObject<object>(Encoding.UTF8.GetString(buff));
} /// <summary>
/// 将byte数组转换成对象
/// </summary>
/// <param name="buff">被转换byte数组</param>
/// <returns>转换完成后的对象</returns>
public static T Bytes2Object<T>(byte[] buff)
{
return JsonConvert.DeserializeObject<T>(Encoding.UTF8.GetString(buff));
}
}
}
4.总结
实战项目还在一点点开发中,碰到很多坑点,时间也很有限。工作越来越忙,总是抽时间兼顾学习联系,很累。NET技术更新换代很快,公司里还在沿用比较老的技术,可能大多数公司都是这样,程序不得不学新技术,企业不得不用成熟的技术。
虽然不知道会做到哪一步,碰到的问题积累的点,在这里先记录下来,备查。项目如果成型或能够运行起来看到效果,到时候开源出来。有时候毕竟代码片段或者写博的时候有些地方不容易连贯起来,现在让我们先一起学习吧。
Asp.Net Core 2.0 项目实战(11) 基于OnActionExecuting全局过滤器,页面操作权限过滤控制到按钮级的更多相关文章
- net core体系-web应用程序-4asp.net core2.0 项目实战(1)-13基于OnActionExecuting全局过滤器,页面操作权限过滤控制到按钮级
1.权限管理 权限管理的基本定义:百度百科. 基于<Asp.Net Core 2.0 项目实战(10) 基于cookie登录授权认证并实现前台会员.后台管理员同时登录>我们做过了登录认证, ...
- Asp.Net Core 2.0 项目实战(10) 基于cookie登录授权认证并实现前台会员、后台管理员同时登录
1.登录的实现 登录功能实现起来有哪些常用的方式,大家首先想到的肯定是cookie或session或cookie+session,当然还有其他模式,今天主要探讨一下在Asp.net core 2.0下 ...
- Asp.Net Core 2.0 项目实战(9) 日志记录,基于Nlog或Microsoft.Extensions.Logging的实现及调用实例
本文目录 1. Net下日志记录 2. NLog的使用 2.1 添加nuget引用NLog.Web.AspNetCore 2.2 配置文件设置 2.3 依赖配置及调用 ...
- Asp.Net Core 2.0 项目实战(8)Core下缓存操作、序列化操作、JSON操作等Helper集合类
本文目录 1. 前沿 2.CacheHelper基于Microsoft.Extensions.Caching.Memory封装 3.XmlHelper快速操作xml文档 4.Serializatio ...
- Asp.Net Core 2.0 项目实战(7)MD5加密、AES&DES对称加解密
本文目录 1. 摘要 2. MD5加密封装 3. AES的加密.解密 4. DES加密/解密 5. 总结 1. 摘要 C#中常用的一些加密和解密方案,如:md5加密.RSA加密与解密和DES加密等, ...
- Asp.Net Core 2.0 项目实战(6)Redis配置、封装帮助类RedisHelper及使用实例
本文目录 1. 摘要 2. Redis配置 3. RedisHelper 4.使用实例 5. 总结 1. 摘要 由于內存存取速度远高于磁盘读取的特性,为了程序效率提高性能,通常会把常用的不常变动的数 ...
- Asp.Net Core 2.0 项目实战(4)ADO.NET操作数据库封装、 EF Core操作及实例
Asp.Net Core 2.0 项目实战(1) NCMVC开源下载了 Asp.Net Core 2.0 项目实战(2)NCMVC一个基于Net Core2.0搭建的角色权限管理开发框架 Asp.Ne ...
- Asp.Net Core 2.0 项目实战(1) NCMVC开源下载了
Asp.Net Core 2.0 项目实战(1) NCMVC开源下载了 Asp.Net Core 2.0 项目实战(2)NCMVC一个基于Net Core2.0搭建的角色权限管理开发框架 Asp.Ne ...
- Asp.Net Core 2.0 项目实战(2)NCMVC一个基于Net Core2.0搭建的角色权限管理开发框架
Asp.Net Core 2.0 项目实战(1) NCMVC开源下载了 Asp.Net Core 2.0 项目实战(2)NCMVC一个基于Net Core2.0搭建的角色权限管理开发框架 Asp.Ne ...
随机推荐
- uploadify上传文件(2)--基础语法
隔了好久,因为最近搬家,离开从小生活的城市,来到杭州.找工作.找房子等诸多事宜耽误了这篇文章许久.今天难得闲暇在旅馆中完成uploadify上传文件系列的第二篇--uploadify使用的基础语法. ...
- mysql数据库相关基本术语和概念
1.DDL:Data Definition Language,即数据定义语言,定义数据库涉及的各种对象,定义数据的完整性约束.保密限制等约束. 2.DML:Data Manipulation Lang ...
- VxWorks:添加自己组件到Tornado
项目要求将cpci的驱动做成Tornado组件,尝试了一下! Folder FOLDER_CPCI { //上层组件设置 NAME cpci componen ...
- mysql学习笔记03 mysql数据类型
数值型:整数型 小数型字符串型时间和日期类型 数值型①整数型1 2 3 4 81bin表示1位,1Byte表示一个字节1B=8b.1汉字=2字节(1 word = 2 byte)1字节=8位(1 by ...
- Flex读取txt文件中的内容报错
Flex读取txt文件中的内容 1.具体错误如下 2.错误原因 读取文件不存在 var file:File = new File(File.applicationDirectory.nativePat ...
- WebService之CXF注解之四(测试类)
TeacherTest.java: /** * @Title:TeacherTest.java * @Package:com.test.service * @Description: * @autho ...
- java特征
java的核心是面向对象,与之相对的是面向过程的编程,在对整个java编程没有足够的理解和运用的情况下恐怕没办法很好的理解这两个概念. 在我的初步理解中,写一个程序就例如做一件事情,面向过程的思想或许 ...
- Day25 前端自学日记——入坑记
一 学习契机 今年是走出校门的第一个年头,进入了一家还算不错的公司,领着一份还算不错的薪水,在外人眼中,似乎这样已经不错了,只要我努力好好做,前程一片光明.可事实真是这样吗?两份实习经历都指向我应该从 ...
- TensorFlow MNIST初级学习
MNIST MNIST 是一个入门级计算机视觉数据集,包含了很多手写数字图片,如图所示: 数据集中包含了图片和对应的标注,在 TensorFlow 中提供了这个数据集,我们可以用如下方法进行导入: f ...
- 【转】Java中hashCode的作用
以下是关于HashCode的官方文档定义: hashcode方法返回该对象的哈希码值.支持该方法是为哈希表提供一些优点,例如,java.util.Hashtable 提供的哈希表. hashCode ...