当前位置: 首页 > news >正文

C#AWS signatureV4对接Amazon接口

马上要放假了,需要抓紧时间测试对接一个三方接口,对方是使用Amazon服务的,国内不多见,能查的资(代)料(码),时间紧比较紧,也没有时间去啃Amazon的文档,主要我的英文水平也不行,于是粗略过了两遍文档后下载了Amazon的示例后填入AKSK进行测试,结果返回403 Forbidden,WTF,官网示例都不行?
然后找接口方要了一个示例,用JAVA写的很简单,直接调用了Amazon的sdk签名然后使用HttpClient进行请求,看起来很Easy啊,于是我也安装了AmazonSDK的nuget准备进行dotnet版本的开发。。。然后发现dotnet版本SDK是各种抽象类,而且方法名都差不多,跟Java版本的差很多,同名方法调用不了,在不啃文档花时间以我的能力直接用官方SDK还是太难了。之前不是下载了官方提供的示例吗,我这次把里面的签名算法拷贝了出来,自己调用自己写,仍旧报错。。。
没办法了,找到同事他用python写了一个,调用成功了,WTF?为啥啊
后来总结了一下,开始用的HttpClient,怕有什么问题,于是使用了RestSharp再次尝试,好了!!!
于是我又开始用了HttpClient,签名算法什么的一概不变,还是不行。。。
省略中间各种挣扎,最后使用抓包工具一点点比较两个请求,终于HttpClient成功了。
附上两版代码,另外还有一个坑,HttpClient在header中用Add添加Authorization会报错,需要用

 request.Headers.Authorization = new AuthenticationHeaderValue("AWS4-HMAC-SHA256", authorizationHeader);

RestSharp版本的

public class ApiClient
{private const string AccessKey = "AccessKey ";private const string SecretKey = "SecretKey ";private const string Region = "cn-northwest-1";private const string Service = "execute-api";private const string Url = "https://test.execute-api.cn-northwest-1.amazonaws.com.cn/api/test";private const string ApiKey = "ApiKey";public async Task CallApiAsync(){var client = new RestClient(Url);// 准备请求payload  var payload = new{key=value};var request = new RestRequest("", Method.Post);string jsonPayload = JsonSerializer.Serialize(payload);// 计算payload hash  string payloadHash;using (var sha256 = SHA256.Create()){var bytes = Encoding.UTF8.GetBytes(jsonPayload);var hash = sha256.ComputeHash(bytes);payloadHash = BitConverter.ToString(hash).Replace("-", "").ToLower();}// 获取当前UTC时间  var now = DateTime.UtcNow;var amzDate = now.ToString("yyyyMMddTHHmmssZ");// 设置请求头  request.AddStringBody(jsonPayload, DataFormat.Json);request.AddHeader("Content-Type", "application/json");request.AddHeader("Host", "test.execute-api.cn-northwest-1.amazonaws.com.cn");request.AddHeader("x-amz-content-sha256", payloadHash);request.AddHeader("x-amz-date", amzDate);  // 使用当前UTC时间  request.AddHeader("x-api-key", ApiKey);// 计算签名  var signer = new AWS4RequestSigner(AccessKey, SecretKey);await signer.Sign(request, Service, Region, payloadHash);try{var response = await client.ExecuteAsync(request);Console.WriteLine($"Status Code: {response.StatusCode}");Console.WriteLine($"Response: {response.Content}");}catch (Exception ex){Console.WriteLine($"Error: {ex.Message}");}}
}public class AWS4RequestSigner
{private readonly string _accessKey;private readonly string _secretKey;public AWS4RequestSigner(string accessKey, string secretKey){_accessKey = accessKey;_secretKey = secretKey;}public async Task Sign(RestRequest request, string service, string region, string payloadHash){var amzDate = request.Parameters.First(p => p.Name == "x-amz-date").Value.ToString();var dateStamp = amzDate.Substring(0, 8);// 准备签名所需的字符串  var credentialScope = $"{dateStamp}/{region}/{service}/aws4_request";// 计算签名  var stringToSign = CreateStringToSign(request, credentialScope, amzDate, payloadHash);var signingKey = GetSigningKey(dateStamp, region, service);var signature = CalculateSignature(signingKey, stringToSign);// 构造授权头  var authorizationHeader = $"AWS4-HMAC-SHA256 " +$"Credential={_accessKey}/{credentialScope}, " +$"SignedHeaders=content-type;host;x-amz-content-sha256;x-amz-date;x-api-key, " +$"Signature={signature}";Console.WriteLine($"Authorization header: {authorizationHeader}");request.AddHeader("Authorization", authorizationHeader);}private string CreateStringToSign(RestRequest request, string credentialScope, string amzDate, string payloadHash){var canonicalRequest = CreateCanonicalRequest(request, payloadHash);return $"AWS4-HMAC-SHA256\n{amzDate}\n{credentialScope}\n" +CalculateHash(canonicalRequest);}private string CreateCanonicalRequest(RestRequest request, string payloadHash){var canonicalUrl = "/api/test";var canonicalQueryString = "";// 按照错误消息中的顺序构建规范头部  var contentType = request.Parameters.First(p => p.Name == "Content-Type").Value.ToString();var host = request.Parameters.First(p => p.Name == "Host").Value.ToString();var xAmzContentSha256 = request.Parameters.First(p => p.Name == "x-amz-content-sha256").Value.ToString();var xAmzDate = request.Parameters.First(p => p.Name == "x-amz-date").Value.ToString();var xApiKey = request.Parameters.First(p => p.Name == "x-api-key").Value.ToString();var canonicalHeaders =$"content-type:{contentType}\n" +$"host:{host}\n" +$"x-amz-content-sha256:{xAmzContentSha256}\n" +$"x-amz-date:{xAmzDate}\n" +$"x-api-key:{xApiKey}\n";var signedHeaders = "content-type;host;x-amz-content-sha256;x-amz-date;x-api-key";return $"POST\n{canonicalUrl}\n{canonicalQueryString}\n{canonicalHeaders}\n{signedHeaders}\n{payloadHash}";}private byte[] GetSigningKey(string dateStamp, string region, string service){var kSecret = Encoding.UTF8.GetBytes($"AWS4{_secretKey}");var kDate = Sign(dateStamp, kSecret);var kRegion = Sign(region, kDate);var kService = Sign(service, kRegion);return Sign("aws4_request", kService);}private byte[] Sign(string data, byte[] key){using (var hmac = new HMACSHA256(key)){return hmac.ComputeHash(Encoding.UTF8.GetBytes(data));}}private string CalculateSignature(byte[] signingKey, string stringToSign){using (var hmac = new HMACSHA256(signingKey)){var signature = hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign));return BitConverter.ToString(signature).Replace("-", "").ToLower();}}private string CalculateHash(string text){using (var sha256 = SHA256.Create()){var bytes = Encoding.UTF8.GetBytes(text);var hash = sha256.ComputeHash(bytes);return BitConverter.ToString(hash).Replace("-", "").ToLower();}}
}

以下是HttpClient的

public class ApiClientHttpClient
{private const string AccessKey = "AccessKey";private const string SecretKey = "SecretKey";private const string Region = "cn-northwest-1";private const string Service = "execute-api";private const string Url = "https://test.execute-api.cn-northwest-1.amazonaws.com.cn/api/test";private const string ApiKey = "ApiKey";public async Task CallApiAsync(){using (var client = new HttpClient()){// 准备请求payload  var payload = new{Key=value};string jsonPayload = JsonSerializer.Serialize(payload);// 计算payload hash  string payloadHash = CalculateSha256(jsonPayload);// 获取当前UTC时间  var now = DateTime.UtcNow;var amzDate = now.ToString("yyyyMMddTHHmmssZ");var request = new HttpRequestMessage(HttpMethod.Post, Url){Content = new StringContent(jsonPayload, Encoding.UTF8, "application/json") // 使用UTF8编码和"application/json"内容类型};// 移除默认添加的charset参数request.Content.Headers.ContentType.CharSet = null;//request.Headers.Accept.ParseAdd("application/json");//request.Headers.Accept.ParseAdd("text/json");//request.Headers.Accept.ParseAdd("text/x-json");//request.Headers.Accept.ParseAdd("text/javascript");//request.Headers.Accept.ParseAdd("application/xml");//request.Headers.Accept.ParseAdd("text/xml");//request.Headers.AcceptEncoding.ParseAdd("gzip");//request.Headers.AcceptEncoding.ParseAdd("deflate");//request.Headers.AcceptEncoding.ParseAdd("br");request.Headers.Host = "test.execute-api.cn-northwest-1.amazonaws.com.cn";request.Headers.Add("x-amz-content-sha256", payloadHash);request.Headers.Add("x-amz-date", amzDate);  // 使用当前UTC时间  request.Headers.Add("x-api-key", ApiKey);request.Headers.UserAgent.ParseAdd("test/1.0"); // 替换为你的应用名称和版本// 计算签名  var signer = new AWS4RequestSignerHttpClient(AccessKey, SecretKey);await signer.Sign(request, Service, Region, payloadHash, amzDate);try{var response = await client.SendAsync(request);Console.WriteLine($"Status Code: {response.StatusCode}");Console.WriteLine($"Response: {await response.Content.ReadAsStringAsync()}");}catch (Exception ex){Console.WriteLine($"Error: {ex.Message}");}}}private static string CalculateSha256(string input){using (SHA256 sha256 = SHA256.Create()){byte[] bytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(input));return BitConverter.ToString(bytes).Replace("-", "").ToLower();}}
}public class AWS4RequestSignerHttpClient
{private readonly string _accessKey;private readonly string _secretKey;public AWS4RequestSignerHttpClient(string accessKey, string secretKey){_accessKey = accessKey;_secretKey = secretKey;}public async Task Sign(HttpRequestMessage request, string service, string region, string payloadHash, string amzDate){var dateStamp = amzDate.Substring(0, 8);var credentialScope = $"{dateStamp}/{region}/{service}/aws4_request";// 计算签名  var stringToSign = CreateStringToSign(request, credentialScope, amzDate, payloadHash);var signingKey = GetSigningKey(dateStamp, region, service);var signature = CalculateSignature(signingKey, stringToSign);// 构造授权头  var authorizationHeader = $"Credential={_accessKey}/{credentialScope}, " +$"SignedHeaders=content-type;host;x-amz-content-sha256;x-amz-date;x-api-key, " +$"Signature={signature}";request.Headers.Authorization = new AuthenticationHeaderValue("AWS4-HMAC-SHA256", authorizationHeader);}private string CreateStringToSign(HttpRequestMessage request, string credentialScope, string amzDate, string payloadHash){var canonicalRequest = CreateCanonicalRequest(request, payloadHash);return $"AWS4-HMAC-SHA256\n{amzDate}\n{credentialScope}\n" + CalculateHash(canonicalRequest);}private string CreateCanonicalRequest(HttpRequestMessage request, string payloadHash){var canonicalUrl = "/api/test";var canonicalQueryString = "";// 按照错误消息中的顺序构建规范头部  var contentType = request.Content.Headers.ContentType?.ToString().Split(';').First();var host = request.Headers.Host;var xAmzContentSha256 = request.Headers.FirstOrDefault(h => h.Key == "x-amz-content-sha256").Value.First();var xAmzDate = request.Headers.FirstOrDefault(h => h.Key == "x-amz-date").Value.First();var xApiKey = request.Headers.FirstOrDefault(h => h.Key == "x-api-key").Value.First();var canonicalHeaders =$"content-type:{contentType}\n" +$"host:{host}\n" +$"x-amz-content-sha256:{xAmzContentSha256}\n" +$"x-amz-date:{xAmzDate}\n" +$"x-api-key:{xApiKey}\n";var signedHeaders = "content-type;host;x-amz-content-sha256;x-amz-date;x-api-key";return $"POST\n{canonicalUrl}\n{canonicalQueryString}\n{canonicalHeaders}\n{signedHeaders}\n{payloadHash}";}private byte[] GetSigningKey(string dateStamp, string region, string service){var kSecret = Encoding.UTF8.GetBytes($"AWS4{_secretKey}");var kDate = Sign(dateStamp, kSecret);var kRegion = Sign(region, kDate);var kService = Sign(service, kRegion);return Sign("aws4_request", kService);}private byte[] Sign(string data, byte[] key){using (var hmac = new HMACSHA256(key)){return hmac.ComputeHash(Encoding.UTF8.GetBytes(data));}}private string CalculateSignature(byte[] signingKey, string stringToSign){using (var hmac = new HMACSHA256(signingKey)){var signature = hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign));return BitConverter.ToString(signature).Replace("-", "").ToLower();}}private string CalculateHash(string text){using (var sha256 = SHA256.Create()){var bytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(text));return BitConverter.ToString(bytes).Replace("-", "").ToLower();}}
}

主要的问题是Content-Type: application/json不要有charset=utf-8;第二个事需要一个User-Agent,其他的算法对了并且正确的添加到请求中就好了
另外,x-api-key只是我的接口要求字段

相关文章:

C#AWS signatureV4对接Amazon接口

马上要放假了,需要抓紧时间测试对接一个三方接口,对方是使用Amazon服务的,国内不多见,能查的资(代)料(码),时间紧比较紧,也没有时间去啃Amazon的文档,主要我的英文水平也不行,于是粗…...

C语言操作符(下)

上一篇文章传送门:操作符上 前言:上期我们介绍了C语言的操作符的使用方法,这期我们主要侧重讲当我们已经了解了操作符的基本知识后怎样样来看待运算路径的问题。 操作符 一,优先级和结合性1,优先级2,结合性…...

学习资料收藏 游戏开发

本文整理了本人在学习 Unity3D 游戏开发过程中知晓的一些学习资料。 视频教程 siki学院 M_Studio Unity中文课堂 博客 林新发 浅墨_毛星云 冯乐乐 Roystan Sorumi 宣雨松 陆泽西 书籍 《Unity 游戏设计与实现》(加藤政树) 《Unity Shader 入…...

我的2024年总结

趁着摸鱼赶紧写一下吧 去年目标review 还是将去年的目标完成了一些 【接纳不完美,多拍照片】 这个还是部分做到了,今年和一些朋友们见面时都注意拍照留记录了,不过还可以继续加强,因为外貌上发生了重大变化,下面细说…...

freeswitch在centos上编译过程

操作系统:centos9-last usr/local/freeswitch/bin/freeswitch -version FreeSWITCH version: 1.10.13-devgit~20250125T131725Z~3f1e4bf90a~64bit (git 3f1e4bf 2025-01-25 13:17:25Z 64bit)vi /etc/ssh/sshd_config ip a nmtui reboot ip a curl -o /etc/pki/rpm-…...

docker如何查看容器启动命令(已运行的容器)

docker ps 查看正在运行的容器 该命令主要是为了详细展示查看运行时的command参数 # 通过docker --no-trunc参数来详细展示容器运行命令 docker ps -a --no-trunc | grep <container_name>通过docker inspect命令 使用docker inspect&#xff0c;但是docker inspect打…...

正则表达式以及Qt中的使用

目录 一、正则表达式 1、基本匹配&#xff1a; 2、元字符&#xff1a; 2.1 .运算符&#xff1a; 2.2 字符集&#xff1a; 2.3 重复次数&#xff1a; 2.4 量词{} 2.5 特征标群() 2.6 或运算符 2.7 \反斜线转码特殊字符 2.8 锚点 3、简写字符 4、零宽度断言 4.1 正…...

当高兴、尊重和优雅三位一体是什么情况吗?

英语单词 disgrace 表示“失脸&#xff0c;耻辱&#xff0c;不光彩&#xff0c;名誉扫地”一类的含义&#xff0c;可做名词或动词使用&#xff0c;含义基本一致&#xff0c;只是词性不同。 disgrace n.丢脸&#xff1b;耻辱&#xff1b;不光彩&#xff1b;令人感到羞耻的人(或…...

Vue 3 中的 TypeScript:接口、自定义类型与泛型

在 Vue 3 中&#xff0c;TypeScript 提供了强大的类型系统&#xff0c;帮助我们更好地管理代码的类型安全。通过使用 接口&#xff08;Interface&#xff09;、自定义类型&#xff08;Type Aliases&#xff09; 和 泛型&#xff08;Generics&#xff09;&#xff0c;我们可以编…...

【Super Tilemap Editor使用详解】(十六):高级主题:深入理解 Super Tilemap Editor

在本节中,我们将深入探讨 Super Tilemap Editor 的工作原理,特别是图块地图(Tilemap)的渲染机制以及如何优化性能。这些知识将帮助你更好地理解工具的内部机制,并在开发中做出更明智的决策。 一、图块地图与图块渲染 图块地图是 Super Tilemap Editor 的核心组件之一。它由…...

如何运用python爬虫爬取知网相关内容信息?

爬取知网内容的详细过程 爬取知网内容需要考虑多个因素&#xff0c;包括网站的结构、反爬虫机制等。以下是一个详细的步骤和代码实现&#xff0c;帮助你使用Python爬取知网上的论文信息。 1. 数据准备 首先&#xff0c;需要准备一些基础数据&#xff0c;如知网的URL、请求头…...

2025年数学建模美赛 A题分析(2)楼梯使用频率数学模型

2025年数学建模美赛 A题分析&#xff08;1&#xff09;Testing Time: The Constant Wear On Stairs 2025年数学建模美赛 A题分析&#xff08;2&#xff09;楼梯磨损分析模型 2025年数学建模美赛 A题分析&#xff08;3&#xff09;楼梯使用方向偏好模型 2025年数学建模美赛 A题分…...

云原生:构建现代化应用的基石

一、什么是云原生&#xff1f; 云原生是一种构建和运行应用程序的方法&#xff0c;旨在充分利用云计算的分布式系统优势&#xff0c;例如弹性伸缩、微服务架构、容器化技术等。云原生应用程序从设计之初就考虑到了云环境的特点&#xff0c;能够更好地适应云平台的动态变化&…...

18.Word:数据库培训课程❗【34】

目录 题目 NO1.2.3.4 NO5设置文档内容的格式与样式 NO6 NO7 NO8.9 NO10.11标签邮件合并 题目 NO1.2.3.4 FnF12&#xff1a;打开"Word素材.docx”文件,将其另存为"Word.docx”在考生文件夹下之后到任务9的所有操作均基于此文件&#xff1a;"Word.docx”…...

批量创建ES索引

7.x from elasticsearch import Elasticsearch# 配置 Elasticsearch 连接 # 替换为你的 Elasticsearch 地址、端口、用户名和密码 es Elasticsearch([http://10.10.x.x:43885],basic_auth(admin, XN272G9THEAPYD5N5QORX3PB1TSQELLB) )# # 测试连接 # try: # # 尝试获取集…...

RoboVLM——通用机器人策略的VLA设计哲学:如何选择骨干网络、如何构建VLA架构、何时添加跨本体数据

前言 本博客内解读不少VLA模型了&#xff0c;包括π0等&#xff0c;且如此文的开头所说 前两天又重点看了下openvla&#xff0c;和cogact&#xff0c;发现 目前cogACT把openvla的动作预测换成了dit&#xff0c;在模型架构层面上&#xff0c;逼近了π0​那为了进一步逼近&#…...

25美赛ABCDEF题详细建模过程+可视化图表+参考论文+写作模版+数据预处理

详情见该链接&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; 25美国大学生数学建模如何准备&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;-CSDN博客文章浏览阅读791次&#xff0c;点赞13次&#xff0c;收藏7次。通过了解比赛基本…...

基于RIP的MGRE VPN综合实验

实验拓扑 实验需求 1、R5为ISP&#xff0c;只能进行IP地址配置&#xff0c;其所有地址均配为公有IP地址&#xff1b; 2、R1和R5间使用PPP的PAP认证&#xff0c;R5为主认证方&#xff1b; R2与R5之间使用ppp的CHAP认证&#xff0c;R5为主认证方&#xff1b; R3与R5之间使用HDLC封…...

如何获取小程序的code在uniapp开发中

如何获取小程序的code在uniapp开发中&#xff0c;也就是本地环境&#xff0c;微信开发者工具中获取code&#xff0c;这里的操作是页面一进入就获取code登录&#xff0c;没有登录页面的交互&#xff0c;所以写在了APP.vue中&#xff0c;也就是小程序一打开就获取用户的code APP.…...

【Linux】 冯诺依曼体系与计算机系统架构全解

Linux相关知识点可以通过点击以下链接进行学习一起加油&#xff01;初识指令指令进阶权限管理yum包管理与vim编辑器GCC/G编译器make与Makefile自动化构建GDB调试器与Git版本控制工具Linux下进度条 冯诺依曼体系是现代计算机设计的基石&#xff0c;其统一存储和顺序执行理念推动…...

Python|GIF 解析与构建(5):手搓截屏和帧率控制

目录 Python&#xff5c;GIF 解析与构建&#xff08;5&#xff09;&#xff1a;手搓截屏和帧率控制 一、引言 二、技术实现&#xff1a;手搓截屏模块 2.1 核心原理 2.2 代码解析&#xff1a;ScreenshotData类 2.2.1 截图函数&#xff1a;capture_screen 三、技术实现&…...

JavaSec-RCE

简介 RCE(Remote Code Execution)&#xff0c;可以分为:命令注入(Command Injection)、代码注入(Code Injection) 代码注入 1.漏洞场景&#xff1a;Groovy代码注入 Groovy是一种基于JVM的动态语言&#xff0c;语法简洁&#xff0c;支持闭包、动态类型和Java互操作性&#xff0c…...

深入浅出Asp.Net Core MVC应用开发系列-AspNetCore中的日志记录

ASP.NET Core 是一个跨平台的开源框架&#xff0c;用于在 Windows、macOS 或 Linux 上生成基于云的新式 Web 应用。 ASP.NET Core 中的日志记录 .NET 通过 ILogger API 支持高性能结构化日志记录&#xff0c;以帮助监视应用程序行为和诊断问题。 可以通过配置不同的记录提供程…...

【Linux】shell脚本忽略错误继续执行

在 shell 脚本中&#xff0c;可以使用 set -e 命令来设置脚本在遇到错误时退出执行。如果你希望脚本忽略错误并继续执行&#xff0c;可以在脚本开头添加 set e 命令来取消该设置。 举例1 #!/bin/bash# 取消 set -e 的设置 set e# 执行命令&#xff0c;并忽略错误 rm somefile…...

蓝桥杯 2024 15届国赛 A组 儿童节快乐

P10576 [蓝桥杯 2024 国 A] 儿童节快乐 题目描述 五彩斑斓的气球在蓝天下悠然飘荡&#xff0c;轻快的音乐在耳边持续回荡&#xff0c;小朋友们手牵着手一同畅快欢笑。在这样一片安乐祥和的氛围下&#xff0c;六一来了。 今天是六一儿童节&#xff0c;小蓝老师为了让大家在节…...

2.Vue编写一个app

1.src中重要的组成 1.1main.ts // 引入createApp用于创建应用 import { createApp } from "vue"; // 引用App根组件 import App from ./App.vue;createApp(App).mount(#app)1.2 App.vue 其中要写三种标签 <template> <!--html--> </template>…...

定时器任务——若依源码分析

分析util包下面的工具类schedule utils&#xff1a; ScheduleUtils 是若依中用于与 Quartz 框架交互的工具类&#xff0c;封装了定时任务的 创建、更新、暂停、删除等核心逻辑。 createScheduleJob createScheduleJob 用于将任务注册到 Quartz&#xff0c;先构建任务的 JobD…...

令牌桶 滑动窗口->限流 分布式信号量->限并发的原理 lua脚本分析介绍

文章目录 前言限流限制并发的实际理解限流令牌桶代码实现结果分析令牌桶lua的模拟实现原理总结&#xff1a; 滑动窗口代码实现结果分析lua脚本原理解析 限并发分布式信号量代码实现结果分析lua脚本实现原理 双注解去实现限流 并发结果分析&#xff1a; 实际业务去理解体会统一注…...

Linux 中如何提取压缩文件 ?

Linux 是一种流行的开源操作系统&#xff0c;它提供了许多工具来管理、压缩和解压缩文件。压缩文件有助于节省存储空间&#xff0c;使数据传输更快。本指南将向您展示如何在 Linux 中提取不同类型的压缩文件。 1. Unpacking ZIP Files ZIP 文件是非常常见的&#xff0c;要在 …...

深入理解Optional:处理空指针异常

1. 使用Optional处理可能为空的集合 在Java开发中&#xff0c;集合判空是一个常见但容易出错的场景。传统方式虽然可行&#xff0c;但存在一些潜在问题&#xff1a; // 传统判空方式 if (!CollectionUtils.isEmpty(userInfoList)) {for (UserInfo userInfo : userInfoList) {…...