我在用户发布数据时遇到问题。有时候帖子运行得这么快,这会在我的网站上出现问题。
用户想要注册一张约100美元的表格,并且有120美元余额。
当按下帖子(保存)按钮时,有时两个帖子很快来到服务器,如:
2018-01-31 19:34:43.660注册表格5760 $
2018-01-31 19:34:43.663注册表格5760 $
因此我的客户余额变为负数。
我在代码中使用如果检查余额,但代码运行速度很快,我认为如果一起发生,我就错过了它们。
因此,我创建了 Lock Controll 类,以避免每个用户的并发,但效果不佳。
我制作了全局操作过滤器来控制用户这是我的代码:
public void OnActionExecuting(ActionExecutingContext context)
{
try
{
var controller = (Controller)context.Controller;
if (controller.User.Identity.IsAuthenticated)
{
bool jobDone = false;
int delay = 0;
int counter = 0;
do
{
delay = LockControllers.IsRequested(controller.User.Identity.Name);
if (delay == 0)
{
LockControllers.AddUser(controller.User.Identity.Name);
jobDone = true;
}
else
{
counter++;
System.Threading.Thread.Sleep(delay);
}
if (counter >= 10000)
{
context.HttpContext.Response.StatusCode = 400;
jobDone = true;
context.Result = new ContentResult()
{
Content = "Attack Detected"
};
}
} while (!jobDone);
}
}
catch (System.Exception)
{
}
}
public void OnActionExecuted(ActionExecutedContext context)
{
try
{
var controller = (Controller)context.Controller;
if (controller.User.Identity.IsAuthenticated)
{
LockControllers.RemoveUser(controller.User.Identity.Name);
}
}
catch (System.Exception)
{
}
}
我创建了列表静态用户列表,并在上一个任务发生之前一直休眠。
有没有更好的方法来解决这个问题?
答案 0 :(得分:0)
因此原始问题已被编辑,因此此答案无效。
所以问题不在于代码运行得太快。快速总是好:)问题是账户进入负资金。如果客户端决定发布两次客户端故障的表单。也许你只希望客户只付一次这是另一个问题。
因此,对于第一个问题,我建议使用事务(https://en.wikipedia.org/wiki/Database_transaction)来锁定表。这意味着添加更新/添加更改(或更改集),并强制对该表的其他调用等待这些操作完成。您始终可以开始交易,并检查帐户是否有足够的资金。
如果情况是他们只打算支付一次那么..那么在处理更新/添加之前,有一个单独的表来记录用户是否已经付款(再次在交易中)。
http://www.entityframeworktutorial.net/entityframework6/transaction-in-entity-framework.aspx
(编辑:修复链接)
答案 1 :(得分:0)
这里有几个选项
您在应用中实现了ETag功能,可用于乐观并发。这很有效,当您使用记录时,即您有一个包含数据记录的数据库,将其返回给用户,然后用户更改它。
您可以在视图模型中添加一个带有guid的必填字段,然后将其传递到应用程序并将其添加到内存缓存中并在每个请求中进行检查。
public class RegisterViewModel
{
[Required]
public Guid Id { get; set; }
/* other properties here */
...
}
然后使用IMemoryCache
或IDistributedMemoryCache
(请参阅ASP.NET Core Docs)将此Id放入内存缓存并根据请求验证
public Task<IActioNResult> Register(RegisterViewModel register)
{
if(!ModelState.IsValid)
return BadRequest(ModelState);
var userId = ...; /* get userId */
if(_cache.TryGetValue($"Registration-{userId}", register.Id))
{
return BadRequest(new { ErrorMessage = "Command already recieved by this user" });
}
// Set cache options.
var cacheEntryOptions = new MemoryCacheEntryOptions()
// Keep in cache for 5 minutes, reset time if accessed.
.SetSlidingExpiration(TimeSpan.FromMinutes(5));
// when we're here, the command wasn't executed before, so we save the key in the cache
_cache.Set($"Registration-{userId}", register.Id, cacheEntryOptions );
// call your service here to process it
registrationService.Register(...);
}
当第二个请求到达时,该值已经存在于(分布式)内存缓存中,操作将失败。
如果调用者未设置Id,则验证将失败。
当然Jonathan Hickey在下面的答案中列出的所有内容都适用于,您应该始终确认有足够的余额并使用EF-Cores optimistic or pessimistic concurrency