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

249 lines
9.5 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Logging;
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;
public FuwuhaoManager(IOptions<FuwuhaoOptions> options, IHttpClientFactory httpClientFactory,
ISqlSugarRepository<AiUserExtraInfoEntity> userRepository,
IDistributedCache<AccessTokenResponse> accessTokenCache, ILogger<FuwuhaoManager> logger)
{
_options = options.Value;
_httpClientFactory = httpClientFactory;
_userRepository = userRepository;
_accessTokenCache = accessTokenCache;
_logger = logger;
}
/// <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
});
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,
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";
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);
return result;
}
/// <summary>
/// 通过code获取用户信息接口
/// </summary>
/// <param name="code">授权码</param>
/// <returns>用户信息响应对象</returns>
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 response = await _httpClientFactory.CreateClient().GetAsync(url);
response.EnsureSuccessStatusCode();
var jsonContent = await response.Content.ReadAsStringAsync();
_logger.LogInformation($"服务号code获取用户详细信息{jsonContent}");
var result = JsonSerializer.Deserialize<UserInfoResponse>(jsonContent);
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;
}
/// <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)
{
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);
}
//无openid说明需要进行注册
else
{
return (SceneResultEnum.Register,null);
}
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);
break;
default:
throw new ArgumentOutOfRangeException(nameof(sceneType), sceneType, null);
}
}
}