You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ASP.NET Core 2.1定时批量调用Rest API采购游戏码方案咨询

问题描述

现有ASP.NET Core 2.1 Web应用,管理员通过页面选择游戏(如Razer Gold Pin)、采购数量、接收邮箱,调用Rest API采购游戏码,但API单次最多支持200份采购。当前需求增长至数万份,手动操作耗时极长,希望通过调度实现无人值守批量采购。同时有两个疑问:

  1. 如何将游戏类型参数传入定时任务?
  2. 能否从页面终止未完成的任务?
    此外,本人提出了一套基于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());
    }
}

本人提出的方案思路

  1. 用户在页面输入游戏类型、采购数量、邮箱,将数据存入SQL数据库的BulkRequest表(含状态字段,初始为0)
  2. 一个IHostedService定时读取BulkRequest表中状态为0的数据,拆分为每份100的请求存入GameRequests表(状态初始为0)
  3. 另一个IHostedService每10分钟读取GameRequests表中状态为0的记录,调用第三方API采购,将结果存入GameResponses表,无错误则更新状态为1
  4. 当GameRequests中对应BulkRequest的所有记录状态为1时,更新BulkRequest表对应记录状态为1
  5. 第三个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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 08:25:26