前言

在之前已经提到过,公用类库Util已经开源,目的一是为了简化开发的工作量,毕竟有些常规的功能类库重复率还是挺高的,二是为了一起探讨学习软件开发,用的人越多问题也就会越多,解决的问题越多功能也就越完善,仓库地址:April.Util_githubApril.Util_gitee,还没关注的朋友希望可以先mark,后续会持续维护。

权限

在之前的net core WebApi——公用库April.Util公开及发布中已经介绍了初次发布的一些功能,其中包括缓存,日志,加密,统一的配置等等,具体可以再回头看下这篇介绍,而在其中有个TokenUtil,因为当时发布的时候这块儿还没有更新上,趁着周末来整理下吧。

关于webapi的权限,可以借助Identity,Jwt,但是我这里没有借助这些,只是自己做了个token的生成已经存储用户主要信息,对于权限我想大多数人已经有了一套自己的权限体系,所以这里我简单介绍下我的思路。

  1. 首先对于菜单做权限标示,请求的控制器,请求的事件
  2. 菜单信息维护后,设置角色对应多个菜单
  3. 管理员对应多个角色
  4. 在登录的时候根据账号信息获取对应管理员的角色及最终菜单,控制器,事件
  5. 处理管理员信息后自定义token,可设置token过期时间,token可以反解析(如果到期自动重新授权,我这里没有处理)
  6. 每次访问接口的时候(除公开不需校验的接口),根据请求的路径判断是否有当前控制器权限(通过中间层),进入接口后判断是否有对应权限(通过标签)

通过上述流程来做权限的校验,当然这里只是针对单应用,如果是多应用的话,这里还要考虑应用问题(如,一个授权认证工程主做身份校验,多个应用工程通用一个管理)。

首先,我们需要一个可以存储管理员的对应属性集合AdminEntity,主要存储基本信息,控制器集合,权限集合,数据集合(也就是企业部门等)。

  1. /// <summary>
  2. /// 管理员实体
  3. /// </summary>
  4. public class AdminEntity
  5. {
  6. private int _ID = -1;
  7. private string _UserName = string.Empty;
  8. private string _Avator = string.Empty;
  9. private List<string> _Controllers = new List<string>();
  10. private List<string> _Permissions = new List<string>();
  11. private int _TokenType = 0;
  12. private bool _IsSuperManager = false;
  13. private List<int> _Depts = new List<int>();
  14. private int _CurrentDept = -1;
  15. private DateTime _ExpireTime = DateTime.Now;
  16. /// <summary>
  17. /// 主键
  18. /// </summary>
  19. public int ID { get => _ID; set => _ID = value; }
  20. /// <summary>
  21. /// 用户名
  22. /// </summary>
  23. public string UserName { get => _UserName; set => _UserName = value; }
  24. /// <summary>
  25. /// 头像
  26. /// </summary>
  27. public string Avator { get => _Avator; set => _Avator = value; }
  28. /// <summary>
  29. /// 控制器集合
  30. /// </summary>
  31. public List<string> Controllers { get => _Controllers; set => _Controllers = value; }
  32. /// <summary>
  33. /// 权限集合
  34. /// </summary>
  35. public List<string> Permissions { get => _Permissions; set => _Permissions = value; }
  36. /// <summary>
  37. /// 访问方式
  38. /// </summary>
  39. public int TokenType { get => _TokenType; set => _TokenType = value; }
  40. /// <summary>
  41. /// 是否为超管
  42. /// </summary>
  43. public bool IsSuperManager { get => _IsSuperManager; set => _IsSuperManager = value; }
  44. /// <summary>
  45. /// 企业集合
  46. /// </summary>
  47. public List<int> Depts { get => _Depts; set => _Depts = value; }
  48. /// <summary>
  49. /// 当前企业
  50. /// </summary>
  51. public int CurrentDept { get => _CurrentDept; set => _CurrentDept = value; }
  52. /// <summary>
  53. /// 过期时间
  54. /// </summary>
  55. public DateTime ExpireTime { get => _ExpireTime; set => _ExpireTime = value; }
  56. }

之后我们来完成TokenUtil这块儿,首先是生成我们的token串,因为考虑到需要反解析,所以这里采用的是字符串加解密,当然这个加密串具体是什么可以自定义,目前我这里设置的是固定需要两个参数{id},{ts},目的是为了保证加密串的唯一,当然也是为了过期无感知重新授权准备的。

  1. public class TokenUtil
  2. {
  3. /// <summary>
  4. /// 设置token
  5. /// </summary>
  6. /// <returns></returns>
  7. public static string GetToken(AdminEntity user, out string expiretimstamp)
  8. {
  9. string id = user.ID.ToString();
  10. double exp = 0;
  11. switch ((AprilEnums.TokenType)user.TokenType)
  12. {
  13. case AprilEnums.TokenType.Web:
  14. exp = AprilConfig.WebExpire;
  15. break;
  16. case AprilEnums.TokenType.App:
  17. exp = AprilConfig.AppExpire;
  18. break;
  19. case AprilEnums.TokenType.MiniProgram:
  20. exp = AprilConfig.MiniProgramExpire;
  21. break;
  22. case AprilEnums.TokenType.Other:
  23. exp = AprilConfig.OtherExpire;
  24. break;
  25. }
  26. DateTime date = DateTime.Now.AddHours(exp);
  27. user.ExpireTime = date;
  28. double timestamp = DateUtil.ConvertToUnixTimestamp(date);
  29. expiretimstamp = timestamp.ToString();
  30. string token = AprilConfig.TokenSecretFormat.Replace("{id}", id).Replace("{ts}", expiretimstamp);
  31. token = EncryptUtil.EncryptDES(token, EncryptUtil.SecurityKey);
  32. //LogUtil.Debug($"用户{id}获取token:{token}");
  33. Add(token, user);
  34. //处理多点登录
  35. SetUserToken(token, user.ID);
  36. return token;
  37. }
  38. /// <summary>
  39. /// 通过token获取当前人员信息
  40. /// </summary>
  41. /// <param name="token"></param>
  42. /// <returns></returns>
  43. public static AdminEntity GetUserByToken(string token = "")
  44. {
  45. if (string.IsNullOrEmpty(token))
  46. {
  47. token = GetTokenByContent();
  48. }
  49. if (!string.IsNullOrEmpty(token))
  50. {
  51. AdminEntity admin = Get(token);
  52. if (admin != null)
  53. {
  54. //校验时间
  55. if (admin.ExpireTime > DateTime.Now)
  56. {
  57. if (AprilConfig.AllowSliding)
  58. {
  59. //延长时间
  60. admin.ExpireTime = DateTime.Now.AddMinutes(30);
  61. //更新
  62. Add(token, admin);
  63. }
  64. return admin;
  65. }
  66. else
  67. {
  68. //已经过期的就不再延长了,当然后续根据情况改进吧
  69. return null;
  70. }
  71. }
  72. }
  73. return null;
  74. }
  75. /// <summary>
  76. /// 通过用户请求信息获取Token信息
  77. /// </summary>
  78. /// <returns></returns>
  79. public static string GetTokenByContent()
  80. {
  81. string token = "";
  82. //判断header
  83. var headers = AprilConfig.HttpCurrent.Request.Headers;
  84. if (headers.ContainsKey("token"))
  85. {
  86. token = headers["token"].ToString();
  87. }
  88. if (string.IsNullOrEmpty(token))
  89. {
  90. token = CookieUtil.GetString("token");
  91. }
  92. if (string.IsNullOrEmpty(token))
  93. {
  94. AprilConfig.HttpCurrent.Request.Query.TryGetValue("token", out StringValues temptoken);
  95. if (temptoken != StringValues.Empty)
  96. {
  97. token = temptoken.ToString();
  98. }
  99. }
  100. return token;
  101. }
  102. /// <summary>
  103. /// 移除Token
  104. /// </summary>
  105. /// <param name="token"></param>
  106. public static void RemoveToken(string token = "")
  107. {
  108. if (string.IsNullOrEmpty(token))
  109. {
  110. token = GetTokenByContent();
  111. }
  112. if (!string.IsNullOrEmpty(token))
  113. {
  114. Remove(token);
  115. }
  116. }
  117. #region 多个登录
  118. /// <summary>
  119. /// 多个登录设置缓存
  120. /// </summary>
  121. /// <param name="token"></param>
  122. /// <param name="userid"></param>
  123. public static void SetUserToken(string token, int userid)
  124. {
  125. Dictionary<int, List<string>> dicusers = CacheUtil.Get<Dictionary<int, List<string>>>("UserToken");
  126. if (dicusers == null)
  127. {
  128. dicusers = new Dictionary<int, List<string>>();
  129. }
  130. List<string> listtokens = new List<string>();
  131. if (dicusers.ContainsKey(userid))
  132. {
  133. listtokens = dicusers[userid];
  134. if (listtokens.Count <= 0)
  135. {
  136. listtokens.Add(token);
  137. }
  138. else
  139. {
  140. if (!AprilConfig.AllowMuiltiLogin)
  141. {
  142. foreach (var item in listtokens)
  143. {
  144. RemoveToken(item);
  145. }
  146. listtokens.Add(token);
  147. }
  148. else
  149. {
  150. bool isAdd = true;
  151. foreach (var item in listtokens)
  152. {
  153. if (item == token)
  154. {
  155. isAdd = false;
  156. }
  157. }
  158. if (isAdd)
  159. {
  160. listtokens.Add(token);
  161. }
  162. }
  163. }
  164. }
  165. else
  166. {
  167. listtokens.Add(token);
  168. dicusers.Add(userid, listtokens);
  169. }
  170. CacheUtil.Add("UserToken", dicusers, new TimeSpan(6, 0, 0), true);
  171. }
  172. /// <summary>
  173. /// 多个登录删除缓存
  174. /// </summary>
  175. /// <param name="userid"></param>
  176. public static void RemoveUserToken(int userid)
  177. {
  178. Dictionary<int, List<string>> dicusers = CacheUtil.Get<Dictionary<int, List<string>>>("UserToken");
  179. if (dicusers != null && dicusers.Count > 0)
  180. {
  181. if (dicusers.ContainsKey(userid))
  182. {
  183. //删除所有token
  184. var listtokens = dicusers[userid];
  185. foreach (var token in listtokens)
  186. {
  187. RemoveToken(token);
  188. }
  189. dicusers.Remove(userid);
  190. }
  191. }
  192. }
  193. /// <summary>
  194. /// 多个登录获取
  195. /// </summary>
  196. /// <param name="userid"></param>
  197. /// <returns></returns>
  198. public static List<string> GetUserToken(int userid)
  199. {
  200. Dictionary<int, List<string>> dicusers = CacheUtil.Get<Dictionary<int, List<string>>>("UserToken");
  201. List<string> lists = new List<string>();
  202. if (dicusers != null && dicusers.Count > 0)
  203. {
  204. foreach (var item in dicusers)
  205. {
  206. if (item.Key == userid)
  207. {
  208. lists = dicusers[userid];
  209. break;
  210. }
  211. }
  212. }
  213. return lists;
  214. }
  215. #endregion
  216. #region 私有方法(这块儿还需要改进)
  217. private static void Add(string token,AdminEntity admin)
  218. {
  219. switch (AprilConfig.TokenCacheType)
  220. {
  221. //不推荐Cookie
  222. case AprilEnums.TokenCacheType.Cookie:
  223. CookieUtil.Add(token, admin);
  224. break;
  225. case AprilEnums.TokenCacheType.Cache:
  226. CacheUtil.Add(token, admin, new TimeSpan(0, 30, 0));
  227. break;
  228. case AprilEnums.TokenCacheType.Session:
  229. SessionUtil.Add(token, admin);
  230. break;
  231. case AprilEnums.TokenCacheType.Redis:
  232. RedisUtil.Add(token, admin);
  233. break;
  234. }
  235. }
  236. private static AdminEntity Get(string token)
  237. {
  238. AdminEntity admin = null;
  239. switch (AprilConfig.TokenCacheType)
  240. {
  241. case AprilEnums.TokenCacheType.Cookie:
  242. admin = CookieUtil.Get<AdminEntity>(token);
  243. break;
  244. case AprilEnums.TokenCacheType.Cache:
  245. admin = CacheUtil.Get<AdminEntity>(token);
  246. break;
  247. case AprilEnums.TokenCacheType.Session:
  248. admin = SessionUtil.Get<AdminEntity>(token);
  249. break;
  250. case AprilEnums.TokenCacheType.Redis:
  251. admin = RedisUtil.Get<AdminEntity>(token);
  252. break;
  253. }
  254. return admin;
  255. }
  256. private static void Remove(string token)
  257. {
  258. switch (AprilConfig.TokenCacheType)
  259. {
  260. case AprilEnums.TokenCacheType.Cookie:
  261. CookieUtil.Remove(token);
  262. break;
  263. case AprilEnums.TokenCacheType.Cache:
  264. CacheUtil.Remove(token);
  265. break;
  266. case AprilEnums.TokenCacheType.Session:
  267. SessionUtil.Remove(token);
  268. break;
  269. case AprilEnums.TokenCacheType.Redis:
  270. RedisUtil.Remove(token);
  271. break;
  272. }
  273. }
  274. #endregion
  275. }

中间层

当然这也在之前已经提到过net core Webapi基础工程搭建(七)——小试AOP及常规测试_Part 1,当时还觉得这个叫做拦截器,too young too simple,至于使用方法这里就不多说了,可以参考之前2.2版本的东西,也可以看代码仓库中的示例工程。

  1. public class AprilAuthorizationMiddleware
  2. {
  3. private readonly RequestDelegate next;
  4. public AprilAuthorizationMiddleware(RequestDelegate next)
  5. {
  6. this.next = next;
  7. }
  8. public Task Invoke(HttpContext context)
  9. {
  10. if (context.Request.Method != "OPTIONS")
  11. {
  12. string path = context.Request.Path.Value;
  13. if (!AprilConfig.AllowUrl.Contains(path))
  14. {
  15. //获取管理员信息
  16. AdminEntity admin = TokenUtil.GetUserByToken();
  17. if (admin == null)
  18. {
  19. //重新登录
  20. return ResponseUtil.HandleResponse(-2, "未登录");
  21. }
  22. if (!admin.IsSuperManager)
  23. {
  24. //格式统一为/api/Controller/Action,兼容多级如/api/Controller1/ConrolerInnerName/xxx/Action
  25. string[] strValues = System.Text.RegularExpressions.Regex.Split(path, "/");
  26. string controller = "";
  27. bool isStartApi = false;
  28. if (path.StartsWith("/api"))
  29. {
  30. isStartApi = true;
  31. }
  32. for (int i = 0; i < strValues.Length; i++)
  33. {
  34. //为空,为api,或者最后一个
  35. if (string.IsNullOrEmpty(strValues[i]) || i == strValues.Length - 1)
  36. {
  37. continue;
  38. }
  39. if (isStartApi && strValues[i] == "api")
  40. {
  41. continue;
  42. }
  43. if (!string.IsNullOrEmpty(controller))
  44. {
  45. controller += "/";
  46. }
  47. controller += strValues[i];
  48. }
  49. if (string.IsNullOrEmpty(controller))
  50. {
  51. controller = strValues[strValues.Length - 1];
  52. }
  53. if (!admin.Controllers.Contains(controller.ToLower()))
  54. {
  55. //无权访问
  56. return ResponseUtil.HandleResponse(401, "无权访问");
  57. }
  58. }
  59. }
  60. }
  61. return next.Invoke(context);
  62. }
  63. }

Ok,我们先来看下Login中的操作以及实现效果吧。

  1. [HttpPost]
  2. public async Task<ResponseDataEntity> Login(LoginFormEntity formEntity)
  3. {
  4. if (string.IsNullOrEmpty(formEntity.LoginName) || string.IsNullOrEmpty(formEntity.Password))
  5. {
  6. return ResponseUtil.Fail("请输入账号密码");
  7. }
  8. if (formEntity.LoginName == "admin")
  9. {
  10. //这里实际应该通过db获取管理员
  11. string password = EncryptUtil.MD5Encrypt(formEntity.Password, AprilConfig.SecurityKey);
  12. if (password == "B092956160CB0018")
  13. {
  14. //获取管理员相关权限,同样是db获取,这里只做展示
  15. AdminEntity admin = new AdminEntity
  16. {
  17. UserName = "超级管理员",
  18. Avator = "",
  19. IsSuperManager = true,
  20. TokenType = (int)AprilEnums.TokenType.Web
  21. };
  22. string token = TokenUtil.GetToken(admin, out string expiretimestamp);
  23. int expiretime = 0;
  24. int.TryParse(expiretimestamp, out expiretime);
  25. //可以考虑记录登录日志等其他信息
  26. return ResponseUtil.Success("", new { username = admin.UserName, avator = admin.Avator, token = token, expire = expiretime });
  27. }
  28. }
  29. else if (formEntity.LoginName == "test")
  30. {
  31. //这里做权限演示
  32. AdminEntity admin = new AdminEntity
  33. {
  34. UserName = "测试",
  35. Avator = "",
  36. TokenType = (int)AprilEnums.TokenType.Web
  37. };
  38. admin.Controllers.Add("weatherforecast");
  39. admin.Permissions.Add("weatherforecast_log");//控制器_事件(Add,Update...)
  40. string token = TokenUtil.GetToken(admin, out string expiretimestamp);
  41. int expiretime = 0;
  42. int.TryParse(expiretimestamp, out expiretime);
  43. //可以考虑记录登录日志等其他信息
  44. return ResponseUtil.Success("", new { username = admin.UserName, avator = admin.Avator, token = token, expire = expiretime });
  45. }
  46. //这里其实已经可以考虑验证码相关了,但是这是示例工程,后续可持续关注我,会有基础工程(带权限)的实例公开
  47. return ResponseUtil.Fail("账号密码错误");
  48. }

可能乍一看会先吐槽下,明明是异步接口还用同步的方法,没有异步的实现空浪费内存xxx,因为db考虑是要搞异步,所以这里示例就这样先写了,主要是领会精神,咳咳。

来试下效果吧,首先我们随便访问个白名单外的接口。

然后我们通过账号登陆Login接口(直接写死了,admin,123456),获取到token。

然后我们来访问接口。

是不是还是未登录,没错,因为没有token的传值,当然我这里是通过query传值,支持header,token,query。

这里因为是超管,所以权限随意搞,无所谓,接下来展示下普通用户的权限标示。

目前可以通过标签AprilPermission,把当前的控制器与对应事件的权限作为参数传递,之后根据当前管理员信息做校验。

  1. public class AprilPermissionAttribute : Attribute, IActionFilter
  2. {
  3. public string Permission;
  4. public string Controller;
  5. /// <summary>
  6. /// 构造函数
  7. /// </summary>
  8. /// <param name="_controller">控制器</param>
  9. /// <param name="_permission">接口事件</param>
  10. public AprilPermissionAttribute(string _controller, string _permission)
  11. {
  12. Permission = _permission;
  13. Controller = _controller;
  14. }
  15. public void OnActionExecuted(ActionExecutedContext context)
  16. {
  17. LogUtil.Debug("AprilPermission OnActionExecuted");
  18. }
  19. public void OnActionExecuting(ActionExecutingContext context)
  20. {
  21. AdminEntity admin = TokenUtil.GetUserByToken();
  22. if (admin == null || admin.ExpireTime <= DateTime.Now)
  23. {
  24. context.Result = new ObjectResult(new { msg = "未登录", code = -2 });
  25. }
  26. if (!admin.IsSuperManager)
  27. {
  28. string controller_permission = $"{Controller}_{Permission}";
  29. if (!admin.Controllers.Contains(Controller) || !admin.Permissions.Contains(controller_permission))
  30. {
  31. context.Result = new ObjectResult(new { msg = "无权访问", code = 401 });
  32. }
  33. }
  34. }
  35. }

针对几个接口做了调整,附上标签后判断权限,我们来测试下登录test,密码随意。




至此权限相关的功能也统一起来,当然如果有个性化的还是需要调整的,后续也是会不断的更新改动。

小结

权限还是稍微麻烦点儿啊,通过中间层,标签以及TokenUtil来完成登录授权这块儿,至于数据的划分,毕竟这个东西不是通用的,所以只是点出来而没有去整合,如果有好的建议或者自己整合的通用类库也可以跟我交流。

April.Util更新之权限的更多相关文章

  1. net core WebApi——April.Util更新之权限

    目录 前言 权限 中间层 小结 前言 在之前已经提到过,公用类库Util已经开源,目的一是为了简化开发的工作量,毕竟有些常规的功能类库重复率还是挺高的,二是为了一起探讨学习软件开发,用的人越多问题也就 ...

  2. net core WebApi——公用库April.Util公开及发布

    前言 在之前鼓捣过一次基础工程April.WebApi后,就考虑把常用的类库打包做成一个公共类库,这样既方便维护也方便后续做快速开发使用,仓库地址:April.Util_github,April.Ut ...

  3. 最新版 Mysql 8.0.16 创建用户权限更新回收权限

    1.创建用户语法 : create user ‘写你自己的用户名’@‘写你需要哪个IP连接你的用户(%表示所有)’ identified by ‘密码’; 案例: create user ‘wangx ...

  4. Filter的应用--权限过滤

    因为项目比较长,需要一步步进行实现,所以分解成一个一个需求. 一:需求一 1.需求一 可以看某人的权限,同时,可以对这个用户进行权限的修改. 2.程序实现 3.程序目录 4.User.java pac ...

  5. 第十章 Fisco Bcos 权限控制下的数据上链实操演练

    一.目的 前面已经完成fisco bcos 相关底层搭建.sdk使用.控制台.webase中间件平台等系列实战开发, 本次进行最后一个部分,体系化管理区块链底层,建立有序的底层控管制度,实现权限化管理 ...

  6. SpringBoot中关于Shiro权限管理的整合使用

     转载:https://blog.csdn.net/fuweilian1/article/details/80309192 在整合Shiro的时候,我们先要确定一下我们的步骤: 1.加入Shiro的依 ...

  7. Spring Boot动态权限变更实现的整体方案

    1.前言 ​ 在Web项目中,权限管理即权限访问控制为网站访问安全提供了保障,并且很多项目使用了Session作为缓存,结合AOP技术进行token认证和权限控制.权限控制流程大致如下图所示: ​ 现 ...

  8. mysql 用户管理和权限设置

    用户管理 mysql>use mysql; 查看 mysql> select host,user,password from user ; 创建 mysql> create user ...

  9. 【6年开源路】FineUI家族今日全部更新(FineUI + FineUI3to4 + FineUI.Design + AppBox)!

    刚才询问博客园团队: [6年开源路]三石今日送福利,AppBox4.0源码免费拿!FineUI家族今日全部更新(FineUI + FineUI3to4 + FineUI.Design + AppBox ...

随机推荐

  1. 递推DP(至少和至多之间的转换

    UVa 10328 - Coin Toss 题意:给你一个硬币,抛掷n次,问出现连续至少k个正面向上的情况有多少种. 转换成抛N次至多连续有N个减去抛N次至多连续有K-1个1的情况 dp[i][k]表 ...

  2. 区块链——java实现

    简述 本文主要的内容试一次关于区块链的作业,本次作业中有很多地方和实际的区块链不符合,比如hash,本文实现的区块链只是用了区块本身的hash并没去区分,头部和数据部分.仅供参考学习. 介绍 内容有点 ...

  3. json校验失败的原因

    如下原因会造成JSON校验失败,而且会让你不知道为什么失败 JSON字符串里的非数字型键值没有双引号 JSON中存在\t这样的制表符,看起来和空格一样,但是就是因为它的存在校验不通过.去掉就能过了. ...

  4. JavaWeb--ServletContext

    https://www.jianshu.com/p/31d27181d542 java类中获取ServletContext的方法 起因是我想要获取一个相对路径,需要用到servletContext的g ...

  5. CSS 多行省略失效 (-webkit-box-orient 失效) Second Autoprefixer control comment was ignored. Autoprefixer applies control comment to whole block, not to next rules.

    webpck不能编译这个属性-webkit-box-orient: vertical https://github.com/fanyifanbumaimeng/Articles/issues/48 / ...

  6. 什么是Web Service(Web服务)?

    从表面上看,Web Service就是一个应用程序,它向外界暴露出一个能够通过Web进行调用的API.例如可以创建一个提供天气预报的Web Service,那么无论你用哪种编程语言开发的应用都可以通过 ...

  7. Postman(一)、断言

    postman常见断言方法介绍: 1.Clear a global variable (清除一个全局变量)  postman.clearGlobalVariable("variable_ke ...

  8. ubuntu E: Could not get lock /var/lib/apt/lists/lock 异常信息

    转载:https://www.cnblogs.com/qq952693358/p/6537846.html 在更换软件源时遇到了如下问题: sudo apt-get update E: Could n ...

  9. 计算机网络&http学习笔记持续整理

    http不常见状态码: 204: 请求处理成功,但是没有资源可返回. 206: 只返回请求资源的某一部分(客户端只想请求某一部分),响应报文中包含由Content-Range指定范围的实体内容. 30 ...

  10. C++入门经典-例5.18-通过引用交换数值

    1:在C++中,函数参数的传递方式主要有两种,即值传递和引用传递.值传递是指在函数调用时,将实际参数的值赋值一份传递到调用函数中,这样如果在调用函数中修改了参数的值,其改变将不会影响到实际参数的值.而 ...