ASP.NET Core 2.1定时批量调用Rest API采购游戏码方案咨询
问题描述
现有ASP.NET Core 2.1 Web应用,管理员通过页面选择游戏(如Razer Gold Pin)、采购数量、接收邮箱,调用Rest API采购游戏码,但API单次最多支持200份采购。当前需求增长至数万份,手动操作耗时极长,希望通过调度实现无人值守批量采购。同时有两个疑问:
- 如何将游戏类型参数传入定时任务?
- 能否从页面终止未完成的任务?
此外,本人提出了一套基于SQL数据库分表与IHostedService的实现思路,需确认该方案是否可行。
现有相关代码
cshtml中的Javascript代码
$("#btn_add").html( '<span class="spinner-border spinner-border-sm" role="status" id="spinner" aria-hidden="true"></span> Loading...' ); var selText = $("#validationCustom04").val(); var gameName = $("#validationCustom04 option:selected").text(); var quantity = $("#quantity").val(); var email = $("#email").val(); var price = $("#total").val(); var company = $("#validationCustom05").val(); if ($("#total").val() !== '') { price = $("#total").val(); } var serviceUrl = '/GameBanks/A101PinAsync?quantity=' + quantity + '&game=' + selText + '&email=' + email + '&prc=' + price + '&gameName=' + gameName + '&company=' + company; $.ajax({ type: "GET", url: serviceUrl, dataType: 'json', success: function (data) { ShowResult(data); $("#spinner").remove(); $("#btn_add").html('Add'); }, error: function (xhr, status, error) { $("#spinner").remove(); $("#btn_add").html('Add'); var errorMessage = xhr.responseText; alert('Error - ' + errorMessage); } });
Controller方法
[HttpGet] public async Task<IActionResult> A101PinAsync(int quantity, string game, string email, int prc, string gameName, string company) { var price = 0; try { string result = null; var dList = new DemoList(); if (prc > 0) { price = prc; } var games = new Game { Product = game, Quantity = quantity }; var content = await games.CallGameAsync("Test", "12345", game, quantity, company); var deserializedResult = JObject.Parse(content); var root = JsonConvert.DeserializeObject<Root>(content); if ((string)deserializedResult["coupons"]?[0]?["Serial"] == null) { result = result + gameName + ":" + (string)deserializedResult["Message"] + "\n"; } else { foreach (var coupon in root.coupons) { result = result + gameName + " Serial:" + coupon.Serial + " Pin:" + coupon.Pin + "\n"; } } dList.gameList = result; // NLOG NLogPin(logger, User.Identity.Name, DateTime.Now, result, email); return new JsonResult(dList); } catch (Exception e) { // NLOG NLog(logger2, "OyunPalas " + e.Message, DateTime.UtcNow,0); return StatusCode(500,e.Message); } }
Web API调用方法
public class Game { public string Username { get; set; } public string Password { get; set; } public string Product { get; set; } public int Quantity { get; set; } public string ShopNo { get; set; } private static readonly Logger logger2 = LogManager.GetLogger("exceptionFile"); public async Task<string> CallGameAsync(string username, string password, string product, int quantity, string company) { try { HttpWebRequest request = null; request = (HttpWebRequest)WebRequest.Create("http://111.111.111.111:1907//api/v2/web/purchase"); var svcCredentials = Convert.ToBase64String(Encoding.ASCII.GetBytes(username + ":" + password)); request.Headers.Add("Authorization", "Basic " + svcCredentials); request.Method = "POST"; request.KeepAlive = false; request.ContentType = "application/x-www-form-urlencoded"; var content = new FormUrlEncodedContent(new[] { new KeyValuePair<string, string>("productCode", product), new KeyValuePair<string, string>("quantity", quantity.ToString()), new KeyValuePair<string, string>("shopNo", company), new KeyValuePair<string, string>("safeNo", company), new KeyValuePair<string, string>("cashierNo", company) }); var urlEncodedString = await content.ReadAsStringAsync(); using (var streamWriter = new StreamWriter(await request.GetRequestStreamAsync())) { await streamWriter.WriteAsync(urlEncodedString); } var httpResponse = (HttpWebResponse)(await request.GetResponseAsync()); var response = new HttpResponseMessage { StatusCode = httpResponse.StatusCode, Content = new StreamContent(httpResponse.GetResponseStream()), }; //Read response var htmlResponse = await response.Content.ReadAsStringAsync(); return htmlResponse; } catch (Exception e) { //NLOG NLog(logger2, "Test" + e.Message); throw; } } public void NLog(Logger logger, string user) { var sb = new StringBuilder(); sb.AppendLine("Test: " + user); logger.Info(sb.ToString()); } }
本人提出的方案思路
- 用户在页面输入游戏类型、采购数量、邮箱,将数据存入SQL数据库的
BulkRequest表(含状态字段,初始为0) - 一个
IHostedService定时读取BulkRequest表中状态为0的数据,拆分为每份100的请求存入GameRequests表(状态初始为0) - 另一个
IHostedService每10分钟读取GameRequests表中状态为0的记录,调用第三方API采购,将结果存入GameResponses表,无错误则更新状态为1 - 当
GameRequests中对应BulkRequest的所有记录状态为1时,更新BulkRequest表对应记录状态为1 - 第三个
IHostedService检查BulkRequest状态,若为1则触发服务生成结果文件并发送至用户邮箱
问题解答
1. 方案可行性确认
你的方案完全可行,是ASP.NET Core中实现批量异步任务的典型思路,核心设计的合理性:
- 分表拆分任务:将大数量拆分为API支持的小批次(100份,小于API上限200),避免单次请求失败导致全部任务作废,同时符合API限制
- IHostedService调度:利用ASP.NET Core内置后台服务实现定时任务,无需额外引入第三方调度组件,适合轻量级场景
- 状态驱动流程:通过数据库状态字段(0=待处理,1=已完成)控制任务流转,保证幂等性,即使服务重启也能从断点继续执行
可优化的细节:
- 给
GameRequests表增加重试次数字段,API调用失败时自动重试N次后标记为失败,避免任务卡住 - 给
BulkRequest和GameRequests表增加创建时间、更新时间字段,便于排查任务执行情况 - 拆分任务的
IHostedService可改为事件触发(用户提交请求后直接触发拆分),替代定时轮询,减少数据库查询开销
2. 如何将游戏类型参数传入定时任务
游戏类型参数已通过BulkRequest表持久化,定时任务只需读取数据库记录即可获取:
- 拆分任务的服务读取
BulkRequest时,直接获取game字段值,拆分后存入GameRequests表对应字段 - 执行采购的服务读取
GameRequests时,取出game参数传入第三方API调用方法
整个流程参数通过数据库传递,无需额外的参数传递机制
3. 能否从页面终止未完成的任务
可以实现,具体步骤:
- 在
BulkRequest表中增加终止标记字段(如IsCancelled,默认0=未终止,1=已终止) - 页面新增"终止任务"按钮,点击后通过AJAX调用后端接口,将对应
BulkRequest的IsCancelled设为1 - 各个
IHostedService执行任务前,先检查对应BulkRequest的终止状态:- 拆分任务的服务:若已终止,不再处理该
BulkRequest - 采购任务的服务:读取
GameRequests时关联查询BulkRequest的终止状态,若已终止,跳过该记录并标记为已取消
- 拆分任务的服务:若已终止,不再处理该
- 注意:正在执行的API调用无法直接中断,可在调用完成后检查终止状态,若已终止则不保存结果或标记为取消
内容的提问来源于stack exchange,提问作者raysefo
相关产品推荐
相关产品推荐

