Files
Yi.Admin/Yi.Abp.Net8/module/ai-hub/Yi.Framework.AiHub.Domain/Managers/Fuwuhao/FuwuhaoManager.cs

257 lines
9.7 KiB
C#
Raw Normal View History

using System.Security.Cryptography;
using System.Text;
2025-08-27 23:42:46 +08:00
using System.Text.Json;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Logging;
2025-08-27 23:42:46 +08:00
using Microsoft.Extensions.Options;
using Volo.Abp.Caching;
using Volo.Abp.Domain.Services;
using Yi.Framework.AiHub.Domain.Entities;
using Yi.Framework.AiHub.Domain.Shared.Enums.Fuwuhao;
using Yi.Framework.SqlSugarCore.Abstractions;
namespace Yi.Framework.AiHub.Domain.Managers.Fuwuhao;
public class FuwuhaoManager : DomainService
{
private readonly FuwuhaoOptions _options;
private readonly IHttpClientFactory _httpClientFactory;
private IDistributedCache<AccessTokenResponse> _accessTokenCache;
private ISqlSugarRepository<AiUserExtraInfoEntity> _userRepository;
private readonly ILogger<FuwuhaoManager> _logger;
2025-08-27 23:42:46 +08:00
public FuwuhaoManager(IOptions<FuwuhaoOptions> options, IHttpClientFactory httpClientFactory,
ISqlSugarRepository<AiUserExtraInfoEntity> userRepository,
IDistributedCache<AccessTokenResponse> accessTokenCache, ILogger<FuwuhaoManager> logger)
2025-08-27 23:42:46 +08:00
{
_options = options.Value;
_httpClientFactory = httpClientFactory;
_userRepository = userRepository;
_accessTokenCache = accessTokenCache;
_logger = logger;
2025-08-27 23:42:46 +08:00
}
/// <summary>
/// 获取微信公众号AccessToken
/// </summary>
/// <returns>AccessToken响应对象</returns>
private async Task<string> GetAccessTokenAsync()
{
var output = await _accessTokenCache.GetOrAddAsync("Fuwuhao", async () =>
{
var url =
$"https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={_options.AppId}&secret={_options.Secret}";
var response = await _httpClientFactory.CreateClient().GetAsync(url);
response.EnsureSuccessStatusCode();
var jsonContent = await response.Content.ReadAsStringAsync();
var result = JsonSerializer.Deserialize<AccessTokenResponse>(jsonContent, new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower
});
if (result is null || string.IsNullOrEmpty(result.AccessToken))
{
throw new UserFriendlyException("微信服务号AccessToken为空");
}
2025-08-27 23:42:46 +08:00
return result;
}, () => new DistributedCacheEntryOptions()
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(3600)
});
return output.AccessToken;
}
/// <summary>
/// 创建带参数的二维码
/// </summary>
/// <param name="scene">场景值</param>
/// <returns>二维码URL</returns>
public async Task<string> CreateQrCodeAsync(string scene)
{
var accessToken = await GetAccessTokenAsync();
var url = $"https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token={accessToken}";
var requestBody = new
{
action_name = "QR_STR_SCENE",
expire_seconds = 600,
2025-08-27 23:42:46 +08:00
action_info = new
{
scene = new
{
scene_str = scene
}
}
};
var jsonContent = JsonSerializer.Serialize(requestBody);
var content = new StringContent(jsonContent, Encoding.UTF8, "application/json");
var response = await _httpClientFactory.CreateClient().PostAsync(url, content);
response.EnsureSuccessStatusCode();
var responseContent = await response.Content.ReadAsStringAsync();
var result = JsonSerializer.Deserialize<QrCodeResponse>(responseContent);
return $"https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket={result.Ticket}";
}
/// <summary>
/// 通过code获取用户基础信息接口为了获取用户access_token和openid
/// </summary>
/// <param name="code">授权码</param>
/// <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";
2025-08-27 23:42:46 +08:00
var response = await _httpClientFactory.CreateClient().GetAsync(url);
response.EnsureSuccessStatusCode();
var jsonContent = await response.Content.ReadAsStringAsync();
_logger.LogInformation($"服务号code获取用户基础信息{jsonContent}");
var result = JsonSerializer.Deserialize<UserBaseInfoResponse>(jsonContent);
2025-08-27 23:42:46 +08:00
return result;
}
/// <summary>
/// 通过code获取用户信息接口
/// </summary>
/// <param name="code">授权码</param>
/// <returns>用户信息响应对象</returns>
public async Task<UserInfoResponse?> GetUserInfoByCodeAsync(string code)
2025-08-27 23:42:46 +08:00
{
var baseUserInfo = await GetBaseUserInfoByCodeAsync(code);
var url =
$"https://api.weixin.qq.com/sns/userinfo?access_token={baseUserInfo.AccessToken}&openid={baseUserInfo.OpenId}&lang=zh_CN";
2025-08-27 23:42:46 +08:00
var response = await _httpClientFactory.CreateClient().GetAsync(url);
response.EnsureSuccessStatusCode();
var jsonContent = await response.Content.ReadAsStringAsync();
_logger.LogInformation($"服务号code获取用户详细信息{jsonContent}");
var result = JsonSerializer.Deserialize<UserInfoResponse>(jsonContent);
2025-08-27 23:42:46 +08:00
return result;
}
/// <summary>
/// 校验微信服务器回调参数是否正确
/// </summary>
/// <param name="signature">微信加密签名</param>
/// <param name="timestamp">时间戳</param>
/// <param name="nonce">随机数</param>
/// <returns>true表示验证通过false表示验证失败</returns>
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);
// 进行SHA1计算签名
using (var sha1 = SHA1.Create())
{
var hash = sha1.ComputeHash(Encoding.UTF8.GetBytes(concatenated));
var calculatedSignature = BitConverter.ToString(hash).Replace("-", "").ToLower();
// 与URL链接中的signature参数进行对比
var result = calculatedSignature.Equals(signature, StringComparison.OrdinalIgnoreCase);
if (!result)
{
throw new UserFriendlyException("服务号回调签名异常");
}
}
}
/// <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;
}
2025-08-27 23:42:46 +08:00
/// <summary>
/// 处理回调逻辑
/// </summary>
/// <param name="sceneType"></param>
/// <param name="openId"></param>
/// <param name="bindUserId"></param>
/// <returns></returns>
public async Task<(SceneResultEnum SceneResult, Guid? UserId)> CallBackHandlerAsync(SceneTypeEnum sceneType,
string openId, Guid? bindUserId)
2025-08-27 23:42:46 +08:00
{
var aiUserInfo = await _userRepository._DbQueryable.Where(x => x.FuwuhaoOpenId == openId).FirstAsync();
switch (sceneType)
{
case SceneTypeEnum.LoginOrRegister:
//有openid说明登录成功
if (aiUserInfo is not null)
{
return (SceneResultEnum.Login, aiUserInfo.UserId);
2025-08-27 23:42:46 +08:00
}
//无openid说明需要进行注册
else
{
return (SceneResultEnum.Register, null);
2025-08-27 23:42:46 +08:00
}
break;
case SceneTypeEnum.Bind:
//说明已经有微信号,直接换绑
if (aiUserInfo is not null)
{
await _userRepository.DeleteByIdAsync(aiUserInfo.Id);
}
if (bindUserId is null)
{
throw new UserFriendlyException("绑定用户需要传入绑定的用户id");
}
//说明没有绑定过,直接绑定
await _userRepository.InsertAsync(new AiUserExtraInfoEntity(bindUserId.Value, openId));
return (SceneResultEnum.Bind, bindUserId);
2025-08-27 23:42:46 +08:00
break;
default:
throw new ArgumentOutOfRangeException(nameof(sceneType), sceneType, null);
}
}
}