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 _accessTokenCache; private ISqlSugarRepository _userRepository; private readonly ILogger _logger; public FuwuhaoManager(IOptions options, IHttpClientFactory httpClientFactory, ISqlSugarRepository userRepository, IDistributedCache accessTokenCache, ILogger logger) { _options = options.Value; _httpClientFactory = httpClientFactory; _userRepository = userRepository; _accessTokenCache = accessTokenCache; _logger = logger; } /// /// 获取微信公众号AccessToken /// /// AccessToken响应对象 private async Task 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(jsonContent, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower }); return result; }, () => new DistributedCacheEntryOptions() { AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(3600) }); return output.AccessToken; } /// /// 创建带参数的二维码 /// /// 场景值 /// 二维码URL public async Task 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(responseContent); return $"https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket={result.Ticket}"; } /// /// 通过code获取用户基础信息接口(为了获取用户access_token和openid) /// /// 授权码 /// 用户基础信息响应对象 private async Task 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(jsonContent); return result; } /// /// 通过code获取用户信息接口 /// /// 授权码 /// 用户信息响应对象 public async Task 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(jsonContent); return result; } /// /// 校验微信服务器回调参数是否正确 /// /// 微信加密签名 /// 时间戳 /// 随机数 /// true表示验证通过,false表示验证失败 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("服务号回调签名异常"); } } } /// /// 构建引导注册图文消息体 /// /// 接收用户的OpenID /// 图文消息标题 /// 图文消息描述 /// XML格式的图文消息体 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 = $@" {createTime} 1 <![CDATA[{title}]]> "; return xml; } /// /// 处理回调逻辑 /// /// /// /// /// public async Task 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; } //无openid,说明需要进行注册 else { return SceneResultEnum.Register; } 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; break; default: throw new ArgumentOutOfRangeException(nameof(sceneType), sceneType, null); } } }