feat: 新增微信公众号扫码注册功能及幂等处理
- 新增 `FuwuhaoConst` 常量类,统一缓存 Key 前缀管理 - `FuwuhaoOptions` 增加 FromUser、RedirectUri、PicUrl 配置项 - `FuwuhaoManager` 新增 `BuildRegisterMessage` 方法,构建注册引导图文消息 - `FuwuhaoService` - 增加 OpenId 与 Scene 绑定缓存,支持扫码注册有效期管理 - 回调处理支持注册场景,返回图文消息引导用户注册 - 新增注册接口 `RegisterByCodeAsync`,根据微信授权信息自动注册账号并更新场景状态 - `AccountManager` 注册方法增加分布式锁,防止重复注册,并校验用户名唯一性
This commit is contained in:
@@ -11,6 +11,7 @@ using Volo.Abp.Caching;
|
||||
using Volo.Abp.Users;
|
||||
using Yi.Framework.AiHub.Application.Contracts.Dtos.Fuwuhao;
|
||||
using Yi.Framework.AiHub.Domain.Managers.Fuwuhao;
|
||||
using Yi.Framework.AiHub.Domain.Shared.Consts;
|
||||
using Yi.Framework.AiHub.Domain.Shared.Enums.Fuwuhao;
|
||||
using Yi.Framework.Rbac.Application.Contracts.Dtos.Account;
|
||||
using Yi.Framework.Rbac.Application.Contracts.IServices;
|
||||
@@ -26,12 +27,15 @@ public class FuwuhaoService : ApplicationService
|
||||
private readonly IHttpContextAccessor _accessor;
|
||||
private readonly FuwuhaoManager _fuwuhaoManager;
|
||||
private IDistributedCache<SceneCacheDto> _sceneCache;
|
||||
private IDistributedCache<string> _openIdToSceneCache;
|
||||
|
||||
private IAccountService _accountService;
|
||||
private IFileService _fileService;
|
||||
public IDistributedLockProvider DistributedLock => LazyServiceProvider.LazyGetService<IDistributedLockProvider>();
|
||||
|
||||
public FuwuhaoService(ILogger<FuwuhaoService> logger, IHttpContextAccessor accessor, FuwuhaoManager fuwuhaoManager,
|
||||
IDistributedCache<SceneCacheDto> sceneCache, IAccountService accountService, IFileService fileService)
|
||||
IDistributedCache<SceneCacheDto> sceneCache, IAccountService accountService, IFileService fileService,
|
||||
IDistributedCache<string> openIdToSceneCache)
|
||||
{
|
||||
_logger = logger;
|
||||
_accessor = accessor;
|
||||
@@ -39,6 +43,7 @@ public class FuwuhaoService : ApplicationService
|
||||
_sceneCache = sceneCache;
|
||||
_accountService = accountService;
|
||||
_fileService = fileService;
|
||||
_openIdToSceneCache = openIdToSceneCache;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -63,7 +68,7 @@ public class FuwuhaoService : ApplicationService
|
||||
public async Task<string> PostCallbackAsync([FromQuery] string signature, [FromQuery] string timestamp,
|
||||
[FromQuery] string nonce)
|
||||
{
|
||||
_fuwuhaoManager.ValidateCallback(signature,timestamp,nonce);
|
||||
_fuwuhaoManager.ValidateCallback(signature, timestamp, nonce);
|
||||
var request = _accessor.HttpContext.Request;
|
||||
// 1. 读取原始 XML 内容
|
||||
using var reader = new StreamReader(request.Body, Encoding.UTF8);
|
||||
@@ -74,11 +79,12 @@ public class FuwuhaoService : ApplicationService
|
||||
var body = (FuwuhaoCallModel)serializer.Deserialize(stringReader);
|
||||
|
||||
//获取场景值,后续通过场景值设置缓存状态,前端轮询这个场景值用户是否已操作即可
|
||||
var scene = body.EventKey.Replace("qrscene_","");
|
||||
var scene = body.EventKey.Replace("qrscene_", "");
|
||||
if (!(body.Event is "SCAN" or "subscribe"))
|
||||
{
|
||||
throw new UserFriendlyException("当前回调只处理扫码 与 关注");
|
||||
}
|
||||
|
||||
if (scene is null)
|
||||
{
|
||||
throw new UserFriendlyException("服务号返回无场景值");
|
||||
@@ -86,22 +92,35 @@ public class FuwuhaoService : ApplicationService
|
||||
|
||||
//制作幂等
|
||||
await using (var handle =
|
||||
await DistributedLock.TryAcquireLockAsync($"Yi:fuwuhao:callbacklock:{scene}", TimeSpan.FromSeconds(60)))
|
||||
await DistributedLock.TryAcquireLockAsync($"Yi:fuwuhao:callbacklock:{scene}",
|
||||
TimeSpan.FromSeconds(60)))
|
||||
{
|
||||
if (handle == null)
|
||||
{
|
||||
return "success"; // 跳过直接返回成功
|
||||
}
|
||||
|
||||
var cache = await _sceneCache.GetAsync($"fuwuhao:{scene}");
|
||||
var cache = await _sceneCache.GetAsync($"{FuwuhaoConst.SceneCacheKey}{scene}");
|
||||
|
||||
//根据操作类型,进行业务处理,返回处理结果,再写入缓存,10s过去,相当于用户10s扫完app后,轮询要在10秒内完成
|
||||
var scenResult =
|
||||
await _fuwuhaoManager.CallBackHandlerAsync(cache.SceneType, body.FromUserName, cache.UserId);
|
||||
cache.SceneResult = scenResult;
|
||||
|
||||
await _sceneCache.SetAsync($"fuwuhao:{scene}", cache,
|
||||
await _sceneCache.SetAsync($"{FuwuhaoConst.SceneCacheKey}{scene}", cache,
|
||||
new DistributedCacheEntryOptions() { AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(50) });
|
||||
|
||||
|
||||
//如果是注册,将OpenId与Scene进行绑定,代表用户有30分钟进行注册
|
||||
if (scenResult == SceneResultEnum.Register)
|
||||
{
|
||||
await _openIdToSceneCache.SetAsync($"{FuwuhaoConst.OpenIdToSceneCacheKey}{body.FromUserName}", scene,
|
||||
new DistributedCacheEntryOptions() { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30) });
|
||||
|
||||
var replyMessage =
|
||||
_fuwuhaoManager.BuildRegisterMessage(body.FromUserName);
|
||||
return replyMessage;
|
||||
}
|
||||
}
|
||||
|
||||
return "success";
|
||||
@@ -118,7 +137,7 @@ public class FuwuhaoService : ApplicationService
|
||||
//生成一个随机场景值
|
||||
var scene = Guid.NewGuid().ToString("N");
|
||||
var qrCodeUrl = await _fuwuhaoManager.CreateQrCodeAsync(scene);
|
||||
await _sceneCache.SetAsync($"fuwuhao:{scene}", new SceneCacheDto()
|
||||
await _sceneCache.SetAsync($"{FuwuhaoConst.SceneCacheKey}{scene}", new SceneCacheDto()
|
||||
{
|
||||
UserId = CurrentUser.IsAuthenticated ? CurrentUser.GetId() : null,
|
||||
SceneType = sceneType
|
||||
@@ -139,7 +158,7 @@ public class FuwuhaoService : ApplicationService
|
||||
public async Task<QrCodeResultOutput> GetQrCodeResultAsync([FromQuery] string scene)
|
||||
{
|
||||
var output = new QrCodeResultOutput();
|
||||
var cache = await _sceneCache.GetAsync($"fuwuhao:{scene}");
|
||||
var cache = await _sceneCache.GetAsync($"{FuwuhaoConst.SceneCacheKey}{scene}");
|
||||
if (cache is null)
|
||||
{
|
||||
output.SceneResult = SceneResultEnum.Expired;
|
||||
@@ -164,13 +183,23 @@ public class FuwuhaoService : ApplicationService
|
||||
/// </summary>
|
||||
/// <param name="code"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost("fuwuhao/register")]
|
||||
public async Task<LoginOutputDto> RegisterByCodeAsync([FromQuery] string code)
|
||||
[HttpGet("fuwuhao/register")]
|
||||
public async Task<string> RegisterByCodeAsync([FromQuery] string code)
|
||||
{
|
||||
//根据code获取到openid、微信用户昵称、头像
|
||||
var userInfo = await _fuwuhaoManager.GetUserInfoByCodeAsync(code);
|
||||
var files = new FormFileCollection();
|
||||
if (userInfo is null)
|
||||
{
|
||||
return "当前注册已经失效,请重新扫码注册";
|
||||
}
|
||||
|
||||
var scene = await _openIdToSceneCache.GetAsync($"{FuwuhaoConst.OpenIdToSceneCacheKey}{userInfo.OpenId}");
|
||||
if (scene is null)
|
||||
{
|
||||
return "当前注册已经过期,请重新扫码注册";
|
||||
}
|
||||
|
||||
var files = new FormFileCollection();
|
||||
// 下载头像并添加到系统文件中
|
||||
if (!string.IsNullOrEmpty(userInfo.HeadImgUrl))
|
||||
{
|
||||
@@ -190,16 +219,22 @@ public class FuwuhaoService : ApplicationService
|
||||
|
||||
var result = await _fileService.Post(files);
|
||||
|
||||
var userId = await _accountService.PostSystemRegisterAsync(new RegisterDto
|
||||
await _accountService.PostSystemRegisterAsync(new RegisterDto
|
||||
{
|
||||
UserName = userInfo.Nickname,
|
||||
UserName = $"wx{Random.Shared.Next(100000, 999999)}",
|
||||
Password = Guid.NewGuid().ToString("N"),
|
||||
Phone = null,
|
||||
Email = null,
|
||||
Nick = userInfo.Nickname,
|
||||
Icon = result.FirstOrDefault()?.Id.ToString()
|
||||
});
|
||||
return await _accountService.PostLoginAsync(userId);
|
||||
|
||||
var sceneCache = await _sceneCache.GetAsync($"{FuwuhaoConst.SceneCacheKey}{scene}");
|
||||
sceneCache.SceneResult = SceneResultEnum.Login;
|
||||
await _sceneCache.SetAsync($"{FuwuhaoConst.SceneCacheKey}:{scene}", sceneCache,
|
||||
new DistributedCacheEntryOptions() { AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(50) });
|
||||
|
||||
return "恭喜你已注册成功意社区账号!";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user