feat: 新增微信公众号扫码注册功能及幂等处理

- 新增 `FuwuhaoConst` 常量类,统一缓存 Key 前缀管理
- `FuwuhaoOptions` 增加 FromUser、RedirectUri、PicUrl 配置项
- `FuwuhaoManager` 新增 `BuildRegisterMessage` 方法,构建注册引导图文消息
- `FuwuhaoService`
  - 增加 OpenId 与 Scene 绑定缓存,支持扫码注册有效期管理
  - 回调处理支持注册场景,返回图文消息引导用户注册
  - 新增注册接口 `RegisterByCodeAsync`,根据微信授权信息自动注册账号并更新场景状态
- `AccountManager` 注册方法增加分布式锁,防止重复注册,并校验用户名唯一性
This commit is contained in:
chenchun
2025-08-29 11:01:09 +08:00
parent d2c6238df1
commit 6bd561b094
5 changed files with 146 additions and 31 deletions

View File

@@ -19,7 +19,8 @@ public class FuwuhaoManager : DomainService
private ISqlSugarRepository<AiUserExtraInfoEntity> _userRepository;
public FuwuhaoManager(IOptions<FuwuhaoOptions> options, IHttpClientFactory httpClientFactory,
ISqlSugarRepository<AiUserExtraInfoEntity> userRepository, IDistributedCache<AccessTokenResponse> accessTokenCache)
ISqlSugarRepository<AiUserExtraInfoEntity> userRepository,
IDistributedCache<AccessTokenResponse> accessTokenCache)
{
_options = options.Value;
_httpClientFactory = httpClientFactory;
@@ -97,7 +98,8 @@ public class FuwuhaoManager : DomainService
/// <returns>用户基础信息响应对象</returns>
private async Task<UserBaseInfoResponse> GetBaseUserInfoByCodeAsync(string code)
{
var url = $"https://api.weixin.qq.com/sns/oauth2/access_token?appid={_options.AppId}&secret={_options.Secret}&code={code}&grant_type=authorization_code";
var url =
$"https://api.weixin.qq.com/sns/oauth2/access_token?appid={_options.AppId}&secret={_options.Secret}&code={code}&grant_type=authorization_code";
var response = await _httpClientFactory.CreateClient().GetAsync(url);
response.EnsureSuccessStatusCode();
@@ -116,10 +118,11 @@ public class FuwuhaoManager : DomainService
/// </summary>
/// <param name="code">授权码</param>
/// <returns>用户信息响应对象</returns>
public async Task<UserInfoResponse> GetUserInfoByCodeAsync(string code)
public async Task<UserInfoResponse?> GetUserInfoByCodeAsync(string code)
{
var baseUserInfo = await GetBaseUserInfoByCodeAsync(code);
var url = $"https://api.weixin.qq.com/sns/userinfo?access_token={baseUserInfo.AccessToken}&openid={baseUserInfo.OpenId}&lang=zh_CN";
var baseUserInfo = await GetBaseUserInfoByCodeAsync(code);
var url =
$"https://api.weixin.qq.com/sns/userinfo?access_token={baseUserInfo.AccessToken}&openid={baseUserInfo.OpenId}&lang=zh_CN";
var response = await _httpClientFactory.CreateClient().GetAsync(url);
response.EnsureSuccessStatusCode();
@@ -142,11 +145,11 @@ public class FuwuhaoManager : DomainService
public void ValidateCallback(string signature, string timestamp, string nonce)
{
var token = _options.CallbackToken; // 您设置的token
// 将token、timestamp、nonce三个参数进行字典序排序
var parameters = new[] { token, timestamp, nonce };
Array.Sort(parameters);
// 将三个参数字符串拼接成一个字符串
var concatenated = string.Join("", parameters);
@@ -164,8 +167,40 @@ public class FuwuhaoManager : DomainService
}
}
}
/// <summary>
/// 构建引导注册图文消息体
/// </summary>
/// <param name="toUser">接收用户的OpenID</param>
/// <param name="title">图文消息标题</param>
/// <param name="description">图文消息描述</param>
/// <returns>XML格式的图文消息体</returns>
public string BuildRegisterMessage(string toUser, string title="意社区点击一键注册账号", string description="来自意社区SSO统一注册安全中心")
{
var createTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
var fromUser = _options.FromUser;
var url =
$"https://open.weixin.qq.com/connect/oauth2/authorize?appid={_options.AppId}&redirect_uri={_options.RedirectUri}&response_type=code&scope=snsapi_userinfo&state={createTime}#wechat_redirect";
var xml = $@"<xml>
<ToUserName><![CDATA[{toUser}]]></ToUserName>
<FromUserName><![CDATA[{fromUser}]]></FromUserName>
<CreateTime>{createTime}</CreateTime>
<MsgType><![CDATA[news]]></MsgType>
<ArticleCount>1</ArticleCount>
<Articles>
<item>
<Title><![CDATA[{title}]]></Title>
<Description><![CDATA[{description}]]></Description>
<PicUrl><![CDATA[{_options.PicUrl}]]></PicUrl>
<Url><![CDATA[{url}]]></Url>
</item>
</Articles>
</xml>";
return xml;
}
/// <summary>
/// 处理回调逻辑
/// </summary>

View File

@@ -6,7 +6,7 @@ public class FuwuhaoOptions
/// 微信公众号AppId
/// </summary>
public string AppId { get; set; }
/// <summary>
/// 微信公众号AppSecret
/// </summary>
@@ -16,4 +16,19 @@ public class FuwuhaoOptions
/// 回调token
/// </summary>
public string CallbackToken { get; set; }
/// <summary>
/// 微信公众号原始ID用于FromUser
/// </summary>
public string FromUser { get; set; }
/// <summary>
/// 微信网页授权跳转地址
/// </summary>
public string RedirectUri { get; set; }
/// <summary>
/// 图片地址
/// </summary>
public string PicUrl { get; set; }
}