feat: initialize Evergreen Health backend
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core.Service;
|
||||
|
||||
/// <summary>
|
||||
/// 生成网页授权Url
|
||||
/// </summary>
|
||||
public class GenAuthUrlInput
|
||||
{
|
||||
/// <summary>
|
||||
/// RedirectUrl
|
||||
/// </summary>
|
||||
public string RedirectUrl { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Scope
|
||||
/// </summary>
|
||||
public string Scope { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// State
|
||||
/// </summary>
|
||||
public string State { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取微信用户OpenId
|
||||
/// </summary>
|
||||
public class WechatOAuth2Input
|
||||
{
|
||||
/// <summary>
|
||||
/// Code
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "Code不能为空"), MinLength(10, ErrorMessage = "Code错误")]
|
||||
public string Code { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 微信用户登录
|
||||
/// </summary>
|
||||
public class WechatUserLogin
|
||||
{
|
||||
/// <summary>
|
||||
/// OpenId
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "微信标识不能为空"), MinLength(10, ErrorMessage = "微信标识长错误")]
|
||||
public string OpenId { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取配置签名
|
||||
/// </summary>
|
||||
public class SignatureInput
|
||||
{
|
||||
/// <summary>
|
||||
/// Url
|
||||
/// </summary>
|
||||
public string Url { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取消息模板列表
|
||||
/// </summary>
|
||||
public class MessageTemplateSendInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 订阅模板Id
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "订阅模板Id不能为空")]
|
||||
public string TemplateId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 接收者的OpenId
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "接收者的OpenId不能为空")]
|
||||
public string ToUserOpenId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 模板数据,格式形如 { "key1": { "value": any }, "key2": { "value": any } }
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "模板数据不能为空")]
|
||||
public Dictionary<string, CgibinMessageSubscribeSendRequest.Types.DataItem> Data { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 模板跳转链接
|
||||
/// </summary>
|
||||
public string Url { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 所需跳转到小程序的具体页面路径,支持带参数,(示例index?foo=bar)
|
||||
/// </summary>
|
||||
public string MiniProgramPagePath { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除消息模板
|
||||
/// </summary>
|
||||
public class DeleteMessageTemplateInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 订阅模板Id
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "订阅模板Id不能为空")]
|
||||
public string TemplateId { get; set; }
|
||||
}
|
||||
|
||||
public class UploadAvatarInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 小程序用户身份标识
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "OpenId不能为空")]
|
||||
public string OpenId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 文件
|
||||
/// </summary>
|
||||
[Required]
|
||||
public IFormFile File { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 文件类型
|
||||
/// </summary>
|
||||
public string FileType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 文件路径
|
||||
/// </summary>
|
||||
public string Path { get; set; }
|
||||
}
|
||||
|
||||
public class SetNickNameInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 小程序用户身份标识
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "OpenId不能为空")]
|
||||
public string OpenId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 昵称
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "昵称不能为空")]
|
||||
public string NickName { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core.Service;
|
||||
|
||||
public class WechatPayTransactionInput
|
||||
{
|
||||
/// <summary>
|
||||
/// OpenId
|
||||
/// </summary>
|
||||
public string OpenId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 订单金额
|
||||
/// </summary>
|
||||
public int Total { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 商品描述
|
||||
/// </summary>
|
||||
public string Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 附加数据
|
||||
/// </summary>
|
||||
public string Attachment { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 优惠标记
|
||||
/// </summary>
|
||||
public string GoodsTag { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 业务标签,用来区分做什么业务
|
||||
/// </summary>
|
||||
public string Tags { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 对应业务的主键
|
||||
/// </summary>
|
||||
public long BusinessId { get; set; }
|
||||
}
|
||||
|
||||
public class WechatPayParaInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 订单Id
|
||||
/// </summary>
|
||||
public string PrepayId { get; set; }
|
||||
}
|
||||
|
||||
public class WechatPayRefundDomesticInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 商户端生成的业务流水号
|
||||
/// </summary>
|
||||
[Required]
|
||||
public string TradeId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 退款原因
|
||||
/// </summary>
|
||||
public string Reason { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 退款金额
|
||||
/// </summary>
|
||||
[Required]
|
||||
public int Refund { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 原订单金额
|
||||
/// </summary>
|
||||
[Required]
|
||||
public int Total { get; set; }
|
||||
}
|
||||
|
||||
public class WechatPayPageInput : BasePageInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 添加时间范围
|
||||
/// </summary>
|
||||
public List<DateTime?> CreateTimeRange { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core.Service;
|
||||
|
||||
public class WechatPayOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// OpenId
|
||||
/// </summary>
|
||||
public string OpenId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 订单金额
|
||||
/// </summary>
|
||||
public int Total { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 附加数据
|
||||
/// </summary>
|
||||
public string Attachment { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 优惠标记
|
||||
/// </summary>
|
||||
public string GoodsTag { get; set; }
|
||||
}
|
||||
|
||||
public class WechatPayTransactionOutput
|
||||
{
|
||||
public string PrepayId { get; set; }
|
||||
|
||||
public string OutTradeNumber { get; set; }
|
||||
|
||||
public WechatPayParaOutput SingInfo { get; set; }
|
||||
}
|
||||
|
||||
public class WechatPayParaOutput
|
||||
{
|
||||
public string AppId { get; set; }
|
||||
|
||||
public string TimeStamp { get; set; }
|
||||
|
||||
public string NonceStr { get; set; }
|
||||
|
||||
public string Package { get; set; }
|
||||
|
||||
public string SignType { get; set; }
|
||||
|
||||
public string PaySign { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core.Service;
|
||||
|
||||
public class WechatUserInput : BasePageInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 昵称
|
||||
/// </summary>
|
||||
public string NickName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 手机号码
|
||||
/// </summary>
|
||||
public string Mobile { get; set; }
|
||||
}
|
||||
|
||||
public class DeleteWechatUserInput : BaseIdInput
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core.Service;
|
||||
|
||||
/// <summary>
|
||||
/// 获取微信用户OpenId
|
||||
/// </summary>
|
||||
public class JsCode2SessionInput
|
||||
{
|
||||
/// <summary>
|
||||
/// JsCode
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "JsCode不能为空"), MinLength(10, ErrorMessage = "JsCode错误")]
|
||||
public string JsCode { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取微信用户电话号码
|
||||
/// </summary>
|
||||
public class WxPhoneInput : WxOpenIdLoginInput
|
||||
{
|
||||
/// <summary>
|
||||
/// Code
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "Code不能为空"), MinLength(10, ErrorMessage = "Code错误")]
|
||||
public string Code { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 微信小程序登录
|
||||
/// </summary>
|
||||
public class WxOpenIdLoginInput
|
||||
{
|
||||
/// <summary>
|
||||
/// OpenId
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "微信标识不能为空"), MinLength(10, ErrorMessage = "微信标识错误")]
|
||||
public string OpenId { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 微信手机号登录
|
||||
/// </summary>
|
||||
public class WxPhoneLoginInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 电话号码
|
||||
/// </summary>
|
||||
[DataValidation(ValidationTypes.PhoneNumber, ErrorMessage = "电话号码错误")]
|
||||
public string PhoneNumber { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送订阅消息
|
||||
/// </summary>
|
||||
public class SendSubscribeMessageInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 订阅模板Id
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "订阅模板Id不能为空")]
|
||||
public string TemplateId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 接收者的OpenId
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "接收者的OpenId不能为空")]
|
||||
public string ToUserOpenId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 模板内容,格式形如 { "key1": { "value": any }, "key2": { "value": any } }
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "模板内容不能为空")]
|
||||
public Dictionary<string, CgibinMessageSubscribeSendRequest.Types.DataItem> Data { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 跳转小程序类型
|
||||
/// </summary>
|
||||
public string MiniprogramState { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 语言类型
|
||||
/// </summary>
|
||||
public string Language { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 点击模板卡片后的跳转页面(仅限本小程序内的页面),支持带参数(示例pages/app/index?foo=bar)
|
||||
/// </summary>
|
||||
public string MiniProgramPagePath { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 增加订阅消息模板
|
||||
/// </summary>
|
||||
public class AddSubscribeMessageTemplateInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 模板标题Id
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "模板标题Id不能为空")]
|
||||
public string TemplateTitleId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 模板关键词列表,例如 [3,5,4]
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "模板关键词列表不能为空")]
|
||||
public List<int> KeyworkIdList { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 服务场景描述,15个字以内
|
||||
/// </summary>
|
||||
[Required(ErrorMessage = "服务场景描述不能为空")]
|
||||
public string SceneDescription { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成带参数小程序二维码(总共生成的码数量限制为 100,000)
|
||||
/// </summary>
|
||||
public class GenerateQRImageInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 扫码进入的小程序页面路径,最大长度 128 个字符,不能为空; eg: pages/index?id=0001
|
||||
/// </summary>
|
||||
public string PagePath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 文件保存的名称
|
||||
/// </summary>
|
||||
public string ImageName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 图片宽度 默认430
|
||||
/// </summary>
|
||||
public int Width { get; set; } = 430;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成带参数小程序二维码(获取不受限制的小程序码)
|
||||
/// </summary>
|
||||
public class GenerateQRImageUnLimitInput : GenerateQRImageInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 二维码携带的参数 eg:a=1(最大32个可见字符,只支持数字,大小写英文以及部分特殊字符:<!-- !#$&'()*+,/:;=?@-._~ -->)
|
||||
/// </summary>
|
||||
public string Scene { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core.Service;
|
||||
|
||||
public class WxOpenIdOutput
|
||||
{
|
||||
public string OpenId { get; set; }
|
||||
}
|
||||
|
||||
public class WxPhoneOutput
|
||||
{
|
||||
public string PhoneNumber { get; set; }
|
||||
}
|
||||
|
||||
public class GenerateQRImageOutput
|
||||
{
|
||||
/// <summary>
|
||||
/// 生成状态
|
||||
/// </summary>
|
||||
public bool Success { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 生成图片的绝对路径
|
||||
/// </summary>
|
||||
public string ImgPath { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// 生成图片的相对路径
|
||||
/// </summary>
|
||||
public string RelativeImgPath { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// 生成图片的错误信息
|
||||
/// </summary>
|
||||
public string Message { get; set; } = "";
|
||||
}
|
||||
@@ -0,0 +1,520 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
using Furion.Logging.Extensions;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Admin.NET.Core.Service;
|
||||
|
||||
/// <summary>
|
||||
/// 微信支付服务 🧩
|
||||
/// </summary>
|
||||
[ApiDescriptionSettings(Order = 210)]
|
||||
public class SysWechatPayService : IDynamicApiController, ITransient
|
||||
{
|
||||
private readonly SqlSugarRepository<SysWechatPay> _sysWechatPayRep;
|
||||
private readonly SqlSugarRepository<SysWechatRefund> _sysWechatRefundRep;
|
||||
|
||||
private readonly WechatPayOptions _wechatPayOptions;
|
||||
private readonly PayCallBackOptions _payCallBackOptions;
|
||||
|
||||
private readonly WechatTenpayClient _wechatTenpayClient;
|
||||
|
||||
public SysWechatPayService(SqlSugarRepository<SysWechatPay> sysWechatPayUserRep,
|
||||
SqlSugarRepository<SysWechatRefund> sysWechatRefundRep,
|
||||
IOptions<WechatPayOptions> wechatPayOptions,
|
||||
IOptions<PayCallBackOptions> payCallBackOptions)
|
||||
{
|
||||
_sysWechatPayRep = sysWechatPayUserRep;
|
||||
_sysWechatRefundRep = sysWechatRefundRep;
|
||||
_wechatPayOptions = wechatPayOptions.Value;
|
||||
_payCallBackOptions = payCallBackOptions.Value;
|
||||
|
||||
_wechatTenpayClient = CreateTenpayClient();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化微信支付客户端
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private WechatTenpayClient CreateTenpayClient()
|
||||
{
|
||||
var cerFilePath = Path.Combine(App.WebHostEnvironment.ContentRootPath, _wechatPayOptions.MerchantCertificatePrivateKey.Replace("/", Path.DirectorySeparatorChar.ToString()));
|
||||
|
||||
var tenpayClientOptions = new WechatTenpayClientOptions()
|
||||
{
|
||||
MerchantId = _wechatPayOptions.MerchantId,
|
||||
MerchantV3Secret = _wechatPayOptions.MerchantV3Secret,
|
||||
MerchantCertificateSerialNumber = _wechatPayOptions.MerchantCertificateSerialNumber,
|
||||
MerchantCertificatePrivateKey = File.Exists(cerFilePath) ? File.ReadAllText(cerFilePath) : "",
|
||||
PlatformCertificateManager = new InMemoryCertificateManager()
|
||||
};
|
||||
return new WechatTenpayClient(tenpayClientOptions);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 分页查询支付列表 🔖
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[ApiDescriptionSettings(Name = "Page")]
|
||||
public async Task<SqlSugarPagedList<SysWechatPay>> Page(WechatPayPageInput input)
|
||||
{
|
||||
var query = _sysWechatPayRep.AsQueryable()
|
||||
.WhereIF(!string.IsNullOrWhiteSpace(input.Keyword), u => u.OutTradeNumber == input.Keyword || u.TransactionId == input.Keyword)
|
||||
.WhereIF(input.CreateTimeRange != null && input.CreateTimeRange.Count > 0 && input.CreateTimeRange[0].HasValue, x => x.CreateTime >= input.CreateTimeRange[0])
|
||||
.WhereIF(input.CreateTimeRange != null && input.CreateTimeRange.Count > 1 && input.CreateTimeRange[1].HasValue, x => x.CreateTime < ((DateTime)input.CreateTimeRange[1]).AddDays(1));
|
||||
return await query.OrderBuilder(input).ToPagedListAsync(input.Page, input.PageSize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查询退款信息列表
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[DisplayName("根据支付id获取退款信息列表")]
|
||||
public async Task<List<SysWechatRefund>> ListRefund([FromBody] string id)
|
||||
{
|
||||
var query = _sysWechatRefundRep.AsQueryable()
|
||||
.Where(u => u.TransactionId == id);
|
||||
return await query.ToListAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成JSAPI调起支付所需参数 🔖
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[DisplayName("生成JSAPI调起支付所需参数")]
|
||||
public WechatPayParaOutput GenerateParametersForJsapiPay(WechatPayParaInput input)
|
||||
{
|
||||
var data = _wechatTenpayClient.GenerateParametersForJsapiPayRequest(_wechatPayOptions.AppId, input.PrepayId);
|
||||
return new WechatPayParaOutput()
|
||||
{
|
||||
AppId = data["appId"],
|
||||
TimeStamp = data["timeStamp"],
|
||||
NonceStr = data["nonceStr"],
|
||||
Package = data["package"],
|
||||
SignType = data["signType"],
|
||||
PaySign = data["paySign"]
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 微信支付下单(商户直连) 🔖
|
||||
/// </summary>
|
||||
[DisplayName("微信支付下单(商户直连)")]
|
||||
public async Task<WechatPayTransactionOutput> CreatePayTransaction([FromBody] WechatPayTransactionInput input)
|
||||
{
|
||||
var request = new CreatePayTransactionJsapiRequest()
|
||||
{
|
||||
OutTradeNumber = DateTimeOffset.Now.ToString("yyyyMMddHHmmssfff") + (new Random()).Next(100, 1000), // 订单号
|
||||
AppId = _wechatPayOptions.AppId,
|
||||
Description = input.Description,
|
||||
Attachment = input.Attachment,
|
||||
GoodsTag = input.GoodsTag,
|
||||
ExpireTime = DateTimeOffset.Now.AddMinutes(10),
|
||||
NotifyUrl = _payCallBackOptions.WechatPayUrl,
|
||||
Amount = new CreatePayTransactionJsapiRequest.Types.Amount() { Total = input.Total },
|
||||
Payer = new CreatePayTransactionJsapiRequest.Types.Payer() { OpenId = input.OpenId }
|
||||
};
|
||||
var response = await _wechatTenpayClient.ExecuteCreatePayTransactionJsapiAsync(request);
|
||||
if (!response.IsSuccessful())
|
||||
throw Oops.Oh(response.ErrorMessage);
|
||||
|
||||
var singInfo = this.GenerateParametersForJsapiPay(new WechatPayParaInput() { PrepayId = response.PrepayId });
|
||||
// 保存订单信息
|
||||
var wechatPay = new SysWechatPay()
|
||||
{
|
||||
AppId = _wechatPayOptions.AppId,
|
||||
MerchantId = _wechatPayOptions.MerchantId,
|
||||
OutTradeNumber = request.OutTradeNumber,
|
||||
Description = input.Description,
|
||||
Attachment = input.Attachment,
|
||||
GoodsTag = input.GoodsTag,
|
||||
Total = input.Total,
|
||||
OpenId = input.OpenId,
|
||||
TransactionId = "",
|
||||
Tags = input.Tags,
|
||||
BusinessId = input.BusinessId,
|
||||
};
|
||||
await _sysWechatPayRep.InsertAsync(wechatPay);
|
||||
|
||||
return new WechatPayTransactionOutput()
|
||||
{
|
||||
PrepayId = response.PrepayId,
|
||||
OutTradeNumber = request.OutTradeNumber,
|
||||
SingInfo = singInfo
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 微信支付下单(商户直连)Native
|
||||
/// </summary>
|
||||
[DisplayName("微信支付下单(商户直连)Native")]
|
||||
public async Task<dynamic> CreatePayTransactionNative([FromBody] WechatPayTransactionInput input)
|
||||
{
|
||||
var request = new CreatePayTransactionNativeRequest()
|
||||
{
|
||||
OutTradeNumber = DateTimeOffset.Now.ToString("yyyyMMddHHmmssfff") + (new Random()).Next(100, 1000), // 订单号
|
||||
AppId = _wechatPayOptions.AppId,
|
||||
Description = input.Description,
|
||||
Attachment = input.Attachment,
|
||||
GoodsTag = input.GoodsTag,
|
||||
ExpireTime = DateTimeOffset.Now.AddMinutes(10),
|
||||
NotifyUrl = _payCallBackOptions.WechatPayUrl,
|
||||
Amount = new CreatePayTransactionNativeRequest.Types.Amount() { Total = input.Total },
|
||||
//Payer = new CreatePayTransactionNativeRequest.Types.Payer() { OpenId = input.OpenId }
|
||||
Scene = new CreatePayTransactionNativeRequest.Types.Scene() { ClientIp = "127.0.0.1" }
|
||||
};
|
||||
var response = await _wechatTenpayClient.ExecuteCreatePayTransactionNativeAsync(request);
|
||||
if (!response.IsSuccessful())
|
||||
{
|
||||
JsonConvert.SerializeObject(response).LogInformation();
|
||||
throw Oops.Oh(response.ErrorMessage);
|
||||
}
|
||||
// 保存订单信息
|
||||
var wechatPay = new SysWechatPay()
|
||||
{
|
||||
AppId = _wechatPayOptions.AppId,
|
||||
MerchantId = _wechatPayOptions.MerchantId,
|
||||
OutTradeNumber = request.OutTradeNumber,
|
||||
Description = input.Description,
|
||||
Attachment = input.Attachment,
|
||||
GoodsTag = input.GoodsTag,
|
||||
Total = input.Total,
|
||||
//OpenId = input.OpenId,
|
||||
TransactionId = "",
|
||||
QrcodeContent = response.QrcodeUrl,
|
||||
Tags = input.Tags,
|
||||
BusinessId = input.BusinessId,
|
||||
};
|
||||
await _sysWechatPayRep.InsertAsync(wechatPay);
|
||||
return new
|
||||
{
|
||||
request.OutTradeNumber,
|
||||
response.QrcodeUrl
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 微信支付下单(服务商模式) 🔖
|
||||
/// </summary>
|
||||
[DisplayName("微信支付下单(服务商模式)")]
|
||||
public async Task<dynamic> CreatePayPartnerTransaction([FromBody] WechatPayTransactionInput input)
|
||||
{
|
||||
var request = new CreatePayPartnerTransactionJsapiRequest()
|
||||
{
|
||||
OutTradeNumber = DateTimeOffset.Now.ToString("yyyyMMddHHmmssfff") + (new Random()).Next(100, 1000), // 订单号
|
||||
AppId = _wechatPayOptions.AppId,
|
||||
MerchantId = _wechatPayOptions.MerchantId,
|
||||
SubAppId = _wechatPayOptions.AppId,
|
||||
SubMerchantId = _wechatPayOptions.MerchantId,
|
||||
Description = input.Description,
|
||||
Attachment = input.Attachment,
|
||||
GoodsTag = input.GoodsTag,
|
||||
ExpireTime = DateTimeOffset.Now.AddMinutes(10),
|
||||
NotifyUrl = _payCallBackOptions.WechatPayUrl,
|
||||
Amount = new CreatePayPartnerTransactionJsapiRequest.Types.Amount() { Total = input.Total },
|
||||
Payer = new CreatePayPartnerTransactionJsapiRequest.Types.Payer() { OpenId = input.OpenId }
|
||||
};
|
||||
var response = await _wechatTenpayClient.ExecuteCreatePayPartnerTransactionJsapiAsync(request);
|
||||
if (!response.IsSuccessful())
|
||||
throw Oops.Oh(response.ErrorMessage);
|
||||
var singInfo = this.GenerateParametersForJsapiPay(new WechatPayParaInput() { PrepayId = response.PrepayId });
|
||||
// 保存订单信息
|
||||
var wechatPay = new SysWechatPay()
|
||||
{
|
||||
AppId = _wechatPayOptions.AppId,
|
||||
MerchantId = _wechatPayOptions.MerchantId,
|
||||
SubAppId = _wechatPayOptions.AppId,
|
||||
SubMerchantId = _wechatPayOptions.MerchantId,
|
||||
OutTradeNumber = request.OutTradeNumber,
|
||||
Description = input.Description,
|
||||
Attachment = input.Attachment,
|
||||
GoodsTag = input.GoodsTag,
|
||||
Total = input.Total,
|
||||
OpenId = input.OpenId,
|
||||
TransactionId = ""
|
||||
};
|
||||
await _sysWechatPayRep.InsertAsync(wechatPay);
|
||||
|
||||
return new
|
||||
{
|
||||
response.PrepayId,
|
||||
request.OutTradeNumber,
|
||||
singInfo
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取支付订单详情(本地库) 🔖
|
||||
/// </summary>
|
||||
/// <param name="tradeId"></param>
|
||||
/// <returns></returns>
|
||||
[DisplayName("获取支付订单详情(本地库)")]
|
||||
public async Task<SysWechatPay> GetPayInfo(string tradeId)
|
||||
{
|
||||
return await _sysWechatPayRep.GetFirstAsync(u => u.OutTradeNumber == tradeId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取支付订单详情(微信接口) 🔖
|
||||
/// </summary>
|
||||
/// <param name="tradeId"></param>
|
||||
/// <returns></returns>
|
||||
[DisplayName("获取支付订单详情(微信接口)")]
|
||||
public async Task<SysWechatPay> GetPayInfoFromWechat(string tradeId)
|
||||
{
|
||||
var request = new GetPayTransactionByOutTradeNumberRequest();
|
||||
request.OutTradeNumber = tradeId;
|
||||
var response = await _wechatTenpayClient.ExecuteGetPayTransactionByOutTradeNumberAsync(request);
|
||||
// 修改订单支付状态
|
||||
var wechatPay = await _sysWechatPayRep.GetFirstAsync(u => u.OutTradeNumber == response.OutTradeNumber
|
||||
&& u.MerchantId == response.MerchantId);
|
||||
// 如果状态不一致就更新数据库中的记录
|
||||
if (wechatPay != null && wechatPay.TradeState != response.TradeState)
|
||||
{
|
||||
wechatPay.OpenId = response.Payer.OpenId;
|
||||
wechatPay.TransactionId = response.TransactionId; // 支付订单号
|
||||
wechatPay.TradeType = response.TradeType; // 交易类型
|
||||
wechatPay.TradeState = response.TradeState; // 交易状态
|
||||
wechatPay.TradeStateDescription = response.TradeStateDescription; // 交易状态描述
|
||||
wechatPay.BankType = response.BankType; // 付款银行类型
|
||||
wechatPay.Total = response.Amount.Total; // 订单总金额
|
||||
wechatPay.PayerTotal = response.Amount.PayerTotal; // 用户支付金额
|
||||
wechatPay.SuccessTime = response.SuccessTime.Value.DateTime; // 支付完成时间
|
||||
await _sysWechatPayRep.AsUpdateable(wechatPay).IgnoreColumns(true).ExecuteCommandAsync();
|
||||
}
|
||||
wechatPay = new SysWechatPay()
|
||||
{
|
||||
AppId = _wechatPayOptions.AppId,
|
||||
MerchantId = _wechatPayOptions.MerchantId,
|
||||
SubAppId = _wechatPayOptions.AppId,
|
||||
SubMerchantId = _wechatPayOptions.MerchantId,
|
||||
OutTradeNumber = request.OutTradeNumber,
|
||||
Attachment = response.Attachment,
|
||||
Total = response.Amount.Total, // 订单总金额
|
||||
TransactionId = response.TransactionId,
|
||||
TradeType = response.TradeType, // 交易类型
|
||||
TradeState = response.TradeState, // 交易状态
|
||||
TradeStateDescription = response.TradeStateDescription, // 交易状态描述
|
||||
BankType = response.BankType, // 付款银行类型
|
||||
PayerTotal = response.Amount.PayerTotal, // 用户支付金额
|
||||
SuccessTime = response.SuccessTime.Value.DateTime // 支付完成时间
|
||||
};
|
||||
return wechatPay;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 退款申请
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[DisplayName("退款申请")]
|
||||
[HttpPost]
|
||||
public async Task<dynamic> CreateRefundDomestic([FromBody] WechatPayRefundDomesticInput input)
|
||||
{
|
||||
// refund/domestic/refunds
|
||||
var request = new CreateRefundDomesticRefundRequest()
|
||||
{
|
||||
Amount = new CreateRefundDomesticRefundRequest.Types.Amount()
|
||||
{
|
||||
Refund = input.Refund,
|
||||
Total = input.Total,
|
||||
Currency = "CNY"
|
||||
},
|
||||
|
||||
OutTradeNumber = input.TradeId,
|
||||
OutRefundNumber = "R" + DateTimeOffset.Now.ToString("yyyyMMddHHmmssfff") + (new Random()).Next(100, 1000), // 订单号
|
||||
NotifyUrl = _payCallBackOptions.WechatRefundUrl, // 应采用WechatRefundUrl参数,如与WechatPayUrl入口相同,也应分开设置参数
|
||||
Reason = input.Reason,
|
||||
};
|
||||
var response = await _wechatTenpayClient.ExecuteCreateRefundDomesticRefundAsync(request);
|
||||
if (string.IsNullOrEmpty(response.ErrorCode))
|
||||
{
|
||||
// 成功了,这里应该保存退款订单信息
|
||||
var wechatPay = await _sysWechatPayRep.GetFirstAsync(u => u.OutTradeNumber == response.OutTradeNumber);
|
||||
// 保存订单信息
|
||||
if (wechatPay != null)
|
||||
{
|
||||
var wechatRefund = new SysWechatRefund()
|
||||
{
|
||||
WechatPayId = wechatPay.Id,
|
||||
TransactionId = response.TransactionId,
|
||||
Refund = input.Refund,
|
||||
Reason = input.Reason,
|
||||
OutRefundNumber = request.OutRefundNumber,
|
||||
Channel = response.Channel,
|
||||
UserReceivedAccount = response.UserReceivedAccount,
|
||||
};
|
||||
await _sysWechatRefundRep.InsertAsync(wechatRefund);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw Oops.Bah($"[{response.ErrorCode}]{response.ErrorMessage}");
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取退款订单详情(微信接口)
|
||||
/// </summary>
|
||||
/// <param name="refundId"></param>
|
||||
/// <returns></returns>
|
||||
[DisplayName("获取退款订单详情(微信接口)")]
|
||||
public async Task<SysWechatRefund> GetRefundInfoFromWechat(string refundId)
|
||||
{
|
||||
var request = new GetRefundDomesticRefundByOutRefundNumberRequest();
|
||||
request.OutRefundNumber = refundId;
|
||||
var response = await _wechatTenpayClient.ExecuteGetRefundDomesticRefundByOutRefundNumberAsync(request);
|
||||
// 修改订单支付状态
|
||||
var wechatRefund = await _sysWechatRefundRep.GetFirstAsync(u => u.OutRefundNumber == refundId);
|
||||
// 如果状态不一致就更新数据库中的记录
|
||||
if (wechatRefund != null && wechatRefund.TradeState != response.Status)
|
||||
{
|
||||
wechatRefund.TransactionId = response.TransactionId; // 支付订单号
|
||||
wechatRefund.TradeState = response.Status; // 交易状态
|
||||
wechatRefund.SuccessTime = response.SuccessTime.Value.DateTime; // 支付完成时间
|
||||
await _sysWechatRefundRep.AsUpdateable(wechatRefund).IgnoreColumns(true).ExecuteCommandAsync();
|
||||
// 有退款,刷新一下订单状态
|
||||
var wechatPay = await _sysWechatPayRep.GetFirstAsync(u => u.Id == wechatRefund.WechatPayId);
|
||||
if (wechatPay != null)
|
||||
await GetPayInfoFromWechat(wechatPay.OutTradeNumber);
|
||||
}
|
||||
wechatRefund = new SysWechatRefund()
|
||||
{
|
||||
TransactionId = response.TransactionId,
|
||||
Refund = response.Amount.Refund,
|
||||
OutRefundNumber = request.OutRefundNumber,
|
||||
Channel = response.Channel,
|
||||
UserReceivedAccount = response.UserReceivedAccount,
|
||||
TradeState = response.Status, // 交易状态
|
||||
SuccessTime = response.SuccessTime.Value.DateTime, // 支付完成时间
|
||||
};
|
||||
return wechatRefund;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 微信支付成功回调(商户直连)
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[AllowAnonymous]
|
||||
[DisplayName("微信支付成功回调(商户直连)")]
|
||||
public async Task<WechatPayOutput> PayCallBack()
|
||||
{
|
||||
using var ms = new MemoryStream();
|
||||
await App.HttpContext.Request.Body.CopyToAsync(ms);
|
||||
var b = ms.ToArray();
|
||||
var callbackJson = Encoding.UTF8.GetString(b);
|
||||
|
||||
var callbackModel = _wechatTenpayClient.DeserializeEvent(callbackJson);
|
||||
if ("TRANSACTION.SUCCESS".Equals(callbackModel.EventType))
|
||||
{
|
||||
try
|
||||
{
|
||||
var callbackPayResource = _wechatTenpayClient.DecryptEventResource<TransactionResource>(callbackModel);
|
||||
|
||||
// 修改订单支付状态
|
||||
var wechatPay = await _sysWechatPayRep.GetFirstAsync(u => u.OutTradeNumber == callbackPayResource.OutTradeNumber
|
||||
&& u.MerchantId == callbackPayResource.MerchantId);
|
||||
if (wechatPay == null) return null;
|
||||
wechatPay.OpenId = callbackPayResource.Payer.OpenId; // 支付者标识
|
||||
//wechatPay.MerchantId = callbackResource.MerchantId; // 微信商户号
|
||||
//wechatPay.OutTradeNumber = callbackResource.OutTradeNumber; // 商户订单号
|
||||
wechatPay.TransactionId = callbackPayResource.TransactionId; // 支付订单号
|
||||
wechatPay.TradeType = callbackPayResource.TradeType; // 交易类型
|
||||
wechatPay.TradeState = callbackPayResource.TradeState; // 交易状态
|
||||
wechatPay.TradeStateDescription = callbackPayResource.TradeStateDescription; // 交易状态描述
|
||||
wechatPay.BankType = callbackPayResource.BankType; // 付款银行类型
|
||||
wechatPay.Total = callbackPayResource.Amount.Total; // 订单总金额
|
||||
wechatPay.PayerTotal = callbackPayResource.Amount.PayerTotal; // 用户支付金额
|
||||
wechatPay.SuccessTime = callbackPayResource.SuccessTime.DateTime; // 支付完成时间
|
||||
|
||||
await _sysWechatPayRep.AsUpdateable(wechatPay).IgnoreColumns(true).ExecuteCommandAsync();
|
||||
|
||||
return new WechatPayOutput()
|
||||
{
|
||||
Total = wechatPay.Total,
|
||||
Attachment = wechatPay.Attachment,
|
||||
GoodsTag = wechatPay.GoodsTag
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
"微信支付回调时出错:".LogError(ex);
|
||||
}
|
||||
}
|
||||
else if ("REFUND.SUCCESS".Equals(callbackModel.EventType))
|
||||
{
|
||||
//参考:https://pay.weixin.qq.com/docs/merchant/apis/jsapi-payment/refund-result-notice.html
|
||||
try
|
||||
{
|
||||
var callbackRefundResource = _wechatTenpayClient.DecryptEventResource<RefundResource>(callbackModel);
|
||||
// 修改订单支付状态
|
||||
var wechatRefund = await _sysWechatRefundRep.GetFirstAsync(u => u.OutRefundNumber == callbackRefundResource.OutRefundNumber);
|
||||
if (wechatRefund == null) return null;
|
||||
wechatRefund.TradeState = callbackRefundResource.RefundStatus; // 交易状态
|
||||
wechatRefund.SuccessTime = callbackRefundResource.SuccessTime.Value.DateTime; // 支付完成时间
|
||||
|
||||
await _sysWechatRefundRep.AsUpdateable(wechatRefund).IgnoreColumns(true).ExecuteCommandAsync();
|
||||
// 有退款,刷新一下订单状态
|
||||
await GetPayInfoFromWechat(callbackRefundResource.OutTradeNumber);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
"微信退款回调时出错:".LogError(ex);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
callbackModel.EventType.LogInformation();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 微信支付成功回调(服务商模式) 🔖
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[AllowAnonymous]
|
||||
[DisplayName("微信支付成功回调(服务商模式)")]
|
||||
public async Task PayPartnerCallBack()
|
||||
{
|
||||
using var ms = new MemoryStream();
|
||||
await App.HttpContext.Request.Body.CopyToAsync(ms);
|
||||
var b = ms.ToArray();
|
||||
var callbackJson = Encoding.UTF8.GetString(b);
|
||||
|
||||
var callbackModel = _wechatTenpayClient.DeserializeEvent(callbackJson);
|
||||
if ("TRANSACTION.SUCCESS".Equals(callbackModel.EventType))
|
||||
{
|
||||
var callbackResource = _wechatTenpayClient.DecryptEventResource<PartnerTransactionResource>(callbackModel);
|
||||
|
||||
// 修改订单支付状态
|
||||
var wechatPay = await _sysWechatPayRep.GetFirstAsync(u => u.OutTradeNumber == callbackResource.OutTradeNumber
|
||||
&& u.MerchantId == callbackResource.MerchantId);
|
||||
if (wechatPay == null) return;
|
||||
//wechatPay.OpenId = callbackResource.Payer.OpenId; // 支付者标识
|
||||
//wechatPay.MerchantId = callbackResource.MerchantId; // 微信商户号
|
||||
//wechatPay.OutTradeNumber = callbackResource.OutTradeNumber; // 商户订单号
|
||||
wechatPay.TransactionId = callbackResource.TransactionId; // 支付订单号
|
||||
wechatPay.TradeType = callbackResource.TradeType; // 交易类型
|
||||
wechatPay.TradeState = callbackResource.TradeState; // 交易状态
|
||||
wechatPay.TradeStateDescription = callbackResource.TradeStateDescription; // 交易状态描述
|
||||
wechatPay.BankType = callbackResource.BankType; // 付款银行类型
|
||||
wechatPay.Total = callbackResource.Amount.Total; // 订单总金额
|
||||
wechatPay.PayerTotal = callbackResource.Amount.PayerTotal; // 用户支付金额
|
||||
wechatPay.SuccessTime = callbackResource.SuccessTime.DateTime; // 支付完成时间
|
||||
|
||||
await _sysWechatPayRep.AsUpdateable(wechatPay).IgnoreColumns(true).ExecuteCommandAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core.Service;
|
||||
|
||||
/// <summary>
|
||||
/// 微信公众号服务 🧩
|
||||
/// </summary>
|
||||
[ApiDescriptionSettings(Order = 230)]
|
||||
public class SysWechatService : IDynamicApiController, ITransient
|
||||
{
|
||||
private readonly SqlSugarRepository<SysWechatUser> _sysWechatUserRep;
|
||||
private readonly SysConfigService _sysConfigService;
|
||||
private readonly WechatApiClientFactory _wechatApiClientFactory;
|
||||
private readonly WechatApiClient _wechatApiClient;
|
||||
|
||||
public SysWechatService(SqlSugarRepository<SysWechatUser> sysWechatUserRep,
|
||||
SysConfigService sysConfigService,
|
||||
WechatApiClientFactory wechatApiClientFactory,
|
||||
SysCacheService sysCacheService)
|
||||
{
|
||||
_sysWechatUserRep = sysWechatUserRep;
|
||||
_sysConfigService = sysConfigService;
|
||||
_wechatApiClientFactory = wechatApiClientFactory;
|
||||
_wechatApiClient = wechatApiClientFactory.CreateWechatClient();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成网页授权Url 🔖
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[AllowAnonymous]
|
||||
[DisplayName("生成网页授权Url")]
|
||||
public string GenAuthUrl(GenAuthUrlInput input)
|
||||
{
|
||||
return _wechatApiClient.GenerateParameterizedUrlForConnectOAuth2Authorize(input.RedirectUrl, input.Scope, input.State);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取微信用户OpenId 🔖
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
[AllowAnonymous]
|
||||
[DisplayName("获取微信用户OpenId")]
|
||||
public async Task<string> SnsOAuth2([FromQuery] WechatOAuth2Input input)
|
||||
{
|
||||
var reqOAuth2 = new SnsOAuth2AccessTokenRequest()
|
||||
{
|
||||
Code = input.Code,
|
||||
};
|
||||
var resOAuth2 = await _wechatApiClient.ExecuteSnsOAuth2AccessTokenAsync(reqOAuth2);
|
||||
if (resOAuth2.ErrorCode != (int)WechatReturnCodeEnum.请求成功)
|
||||
throw Oops.Oh(resOAuth2.ErrorMessage + " " + resOAuth2.ErrorCode);
|
||||
|
||||
var wxUser = await _sysWechatUserRep.GetFirstAsync(p => p.OpenId == resOAuth2.OpenId);
|
||||
if (wxUser == null)
|
||||
{
|
||||
var reqUserInfo = new SnsUserInfoRequest()
|
||||
{
|
||||
OpenId = resOAuth2.OpenId,
|
||||
AccessToken = resOAuth2.AccessToken,
|
||||
};
|
||||
var resUserInfo = await _wechatApiClient.ExecuteSnsUserInfoAsync(reqUserInfo);
|
||||
wxUser = resUserInfo.Adapt<SysWechatUser>();
|
||||
wxUser.Avatar = resUserInfo.HeadImageUrl;
|
||||
wxUser.NickName = resUserInfo.Nickname;
|
||||
wxUser.OpenId = resOAuth2.OpenId;
|
||||
wxUser.UnionId = resOAuth2.UnionId;
|
||||
wxUser.AccessToken = resOAuth2.AccessToken;
|
||||
wxUser.RefreshToken = resOAuth2.RefreshToken;
|
||||
wxUser = await _sysWechatUserRep.AsInsertable(wxUser).ExecuteReturnEntityAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
wxUser.AccessToken = resOAuth2.AccessToken;
|
||||
wxUser.RefreshToken = resOAuth2.RefreshToken;
|
||||
await _sysWechatUserRep.AsUpdateable(wxUser).IgnoreColumns(true).ExecuteCommandAsync();
|
||||
}
|
||||
|
||||
return resOAuth2.OpenId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 微信用户登录OpenId 🔖
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[AllowAnonymous]
|
||||
[DisplayName("微信用户登录OpenId")]
|
||||
public async Task<dynamic> OpenIdLogin(WechatUserLogin input)
|
||||
{
|
||||
var wxUser = await _sysWechatUserRep.GetFirstAsync(p => p.OpenId == input.OpenId);
|
||||
if (wxUser == null)
|
||||
throw Oops.Oh("微信用户登录OpenId错误");
|
||||
|
||||
var tokenExpire = await _sysConfigService.GetTokenExpire();
|
||||
return new
|
||||
{
|
||||
wxUser.Avatar,
|
||||
accessToken = JWTEncryption.Encrypt(new Dictionary<string, object>
|
||||
{
|
||||
{ ClaimConst.UserId, wxUser.Id },
|
||||
{ ClaimConst.NickName, wxUser.NickName },
|
||||
{ ClaimConst.LoginMode, LoginModeEnum.APP },
|
||||
}, tokenExpire)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取配置签名参数(wx.config) 🔖
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[DisplayName("获取配置签名参数(wx.config)")]
|
||||
public async Task<dynamic> GenConfigPara(SignatureInput input)
|
||||
{
|
||||
string ticket = await _wechatApiClientFactory.TryGetWechatJsApiTicketAsync();
|
||||
return _wechatApiClient.GenerateParametersForJSSDKConfig(ticket, input.Url);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取模板列表 🔖
|
||||
/// </summary>
|
||||
[DisplayName("获取模板列表")]
|
||||
public async Task<dynamic> GetMessageTemplateList()
|
||||
{
|
||||
var accessToken = await GetCgibinToken();
|
||||
var reqTemplate = new CgibinTemplateGetAllPrivateTemplateRequest()
|
||||
{
|
||||
AccessToken = accessToken
|
||||
};
|
||||
var resTemplate = await _wechatApiClient.ExecuteCgibinTemplateGetAllPrivateTemplateAsync(reqTemplate);
|
||||
if (resTemplate.ErrorCode != (int)WechatReturnCodeEnum.请求成功)
|
||||
throw Oops.Oh(resTemplate.ErrorMessage + " " + resTemplate.ErrorCode);
|
||||
|
||||
return resTemplate.TemplateList;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送模板消息 🔖
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[DisplayName("发送模板消息")]
|
||||
public async Task<dynamic> SendTemplateMessage(MessageTemplateSendInput input)
|
||||
{
|
||||
var dataInfo = input.Data.ToDictionary(k => k.Key, k => k.Value);
|
||||
var messageData = new Dictionary<string, CgibinMessageTemplateSendRequest.Types.DataItem>();
|
||||
foreach (var item in dataInfo)
|
||||
{
|
||||
messageData.Add(item.Key, new CgibinMessageTemplateSendRequest.Types.DataItem() { Value = "" + item.Value.Value.ToString() + "" });
|
||||
}
|
||||
|
||||
var accessToken = await GetCgibinToken();
|
||||
var reqMessage = new CgibinMessageTemplateSendRequest()
|
||||
{
|
||||
AccessToken = accessToken,
|
||||
TemplateId = input.TemplateId,
|
||||
ToUserOpenId = input.ToUserOpenId,
|
||||
Url = input.Url,
|
||||
MiniProgram = new CgibinMessageTemplateSendRequest.Types.MiniProgram
|
||||
{
|
||||
AppId = _wechatApiClientFactory._wechatOptions.WxOpenAppId,
|
||||
PagePath = input.MiniProgramPagePath,
|
||||
},
|
||||
Data = messageData
|
||||
};
|
||||
var resMessage = await _wechatApiClient.ExecuteCgibinMessageTemplateSendAsync(reqMessage);
|
||||
return resMessage;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除模板 🔖
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[ApiDescriptionSettings(Name = "DeleteMessageTemplate"), HttpPost]
|
||||
[DisplayName("删除模板")]
|
||||
public async Task<dynamic> DeleteMessageTemplate(DeleteMessageTemplateInput input)
|
||||
{
|
||||
var accessToken = await GetCgibinToken();
|
||||
var reqMessage = new CgibinTemplateDeletePrivateTemplateRequest()
|
||||
{
|
||||
AccessToken = accessToken,
|
||||
TemplateId = input.TemplateId
|
||||
};
|
||||
var resTemplate = await _wechatApiClient.ExecuteCgibinTemplateDeletePrivateTemplateAsync(reqMessage);
|
||||
return resTemplate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取Access_token
|
||||
/// </summary>
|
||||
[NonAction]
|
||||
public async Task<string> GetCgibinToken()
|
||||
{
|
||||
return await _wechatApiClientFactory.TryGetWechatAccessTokenAsync();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core.Service;
|
||||
|
||||
/// <summary>
|
||||
/// 微信账号服务 🧩
|
||||
/// </summary>
|
||||
[ApiDescriptionSettings(Order = 220)]
|
||||
public class SysWechatUserService : IDynamicApiController, ITransient
|
||||
{
|
||||
private readonly SqlSugarRepository<SysWechatUser> _sysWechatUserRep;
|
||||
|
||||
public SysWechatUserService(SqlSugarRepository<SysWechatUser> sysWechatUserRep)
|
||||
{
|
||||
_sysWechatUserRep = sysWechatUserRep;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取微信用户列表 🔖
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[DisplayName("获取微信用户列表")]
|
||||
public async Task<SqlSugarPagedList<SysWechatUser>> Page(WechatUserInput input)
|
||||
{
|
||||
return await _sysWechatUserRep.AsQueryable()
|
||||
.WhereIF(!string.IsNullOrWhiteSpace(input.NickName), u => u.NickName.Contains(input.NickName))
|
||||
.WhereIF(!string.IsNullOrWhiteSpace(input.Mobile), u => u.Mobile.Contains(input.Mobile))
|
||||
.OrderBy(u => u.Id, OrderByType.Desc)
|
||||
.ToPagedListAsync(input.Page, input.PageSize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 增加微信用户 🔖
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[ApiDescriptionSettings(Name = "Add"), HttpPost]
|
||||
[DisplayName("增加微信用户")]
|
||||
public async Task AddWechatUser(SysWechatUser input)
|
||||
{
|
||||
await _sysWechatUserRep.InsertAsync(input.Adapt<SysWechatUser>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新微信用户 🔖
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[ApiDescriptionSettings(Name = "Update"), HttpPost]
|
||||
[DisplayName("更新微信用户")]
|
||||
public async Task UpdateWechatUser(SysWechatUser input)
|
||||
{
|
||||
var weChatUser = input.Adapt<SysWechatUser>();
|
||||
await _sysWechatUserRep.AsUpdateable(weChatUser).IgnoreColumns(true).ExecuteCommandAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除微信用户 🔖
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[ApiDescriptionSettings(Name = "Delete"), HttpPost]
|
||||
[DisplayName("删除微信用户")]
|
||||
public async Task DeleteWechatUser(DeleteWechatUserInput input)
|
||||
{
|
||||
await _sysWechatUserRep.DeleteAsync(u => u.Id == input.Id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,407 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
namespace Admin.NET.Core.Service;
|
||||
|
||||
/// <summary>
|
||||
/// 微信小程序服务 🧩
|
||||
/// </summary>
|
||||
[ApiDescriptionSettings(Order = 240)]
|
||||
public class SysWxOpenService : IDynamicApiController, ITransient
|
||||
{
|
||||
private readonly SqlSugarRepository<SysWechatUser> _sysWechatUserRep;
|
||||
private readonly SysConfigService _sysConfigService;
|
||||
private readonly WechatApiClient _wechatApiClient;
|
||||
private readonly SysFileService _sysFileService;
|
||||
private readonly WechatApiClientFactory _wechatApiClientFactory;
|
||||
|
||||
public SysWxOpenService(SqlSugarRepository<SysWechatUser> sysWechatUserRep,
|
||||
SysConfigService sysConfigService,
|
||||
WechatApiClientFactory wechatApiClientFactory,
|
||||
SysFileService sysFileService)
|
||||
{
|
||||
_sysWechatUserRep = sysWechatUserRep;
|
||||
_sysConfigService = sysConfigService;
|
||||
_wechatApiClient = wechatApiClientFactory.CreateWxOpenClient();
|
||||
_sysFileService = sysFileService;
|
||||
_wechatApiClientFactory = wechatApiClientFactory;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取微信用户OpenId 🔖
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
[AllowAnonymous]
|
||||
[DisplayName("获取微信用户OpenId")]
|
||||
public async Task<WxOpenIdOutput> GetWxOpenId([FromQuery] JsCode2SessionInput input)
|
||||
{
|
||||
var reqJsCode2Session = new SnsJsCode2SessionRequest()
|
||||
{
|
||||
JsCode = input.JsCode,
|
||||
};
|
||||
var resCode2Session = await _wechatApiClient.ExecuteSnsJsCode2SessionAsync(reqJsCode2Session);
|
||||
if (resCode2Session.ErrorCode != (int)WechatReturnCodeEnum.请求成功)
|
||||
throw Oops.Oh(resCode2Session.ErrorMessage + " " + resCode2Session.ErrorCode);
|
||||
|
||||
var wxUser = await _sysWechatUserRep.GetFirstAsync(p => p.OpenId == resCode2Session.OpenId);
|
||||
if (wxUser == null)
|
||||
{
|
||||
wxUser = new SysWechatUser
|
||||
{
|
||||
OpenId = resCode2Session.OpenId,
|
||||
UnionId = resCode2Session.UnionId,
|
||||
SessionKey = resCode2Session.SessionKey,
|
||||
PlatformType = PlatformTypeEnum.微信小程序
|
||||
};
|
||||
wxUser = await _sysWechatUserRep.AsInsertable(wxUser).ExecuteReturnEntityAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
await _sysWechatUserRep.AsUpdateable(wxUser).IgnoreColumns(true).ExecuteCommandAsync();
|
||||
}
|
||||
|
||||
return new WxOpenIdOutput
|
||||
{
|
||||
OpenId = resCode2Session.OpenId
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取微信用户电话号码 🔖
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
[AllowAnonymous]
|
||||
[DisplayName("获取微信用户电话号码")]
|
||||
public async Task<WxPhoneOutput> GetWxPhone([FromQuery] WxPhoneInput input)
|
||||
{
|
||||
var accessToken = await GetCgibinToken();
|
||||
var reqUserPhoneNumber = new WxaBusinessGetUserPhoneNumberRequest()
|
||||
{
|
||||
Code = input.Code,
|
||||
AccessToken = accessToken,
|
||||
};
|
||||
var resUserPhoneNumber = await _wechatApiClient.ExecuteWxaBusinessGetUserPhoneNumberAsync(reqUserPhoneNumber);
|
||||
if (resUserPhoneNumber.ErrorCode != (int)WechatReturnCodeEnum.请求成功)
|
||||
throw Oops.Oh(resUserPhoneNumber.ErrorMessage + " " + resUserPhoneNumber.ErrorCode);
|
||||
|
||||
var wxUser = await _sysWechatUserRep.GetFirstAsync(p => p.OpenId == input.OpenId);
|
||||
if (wxUser == null)
|
||||
{
|
||||
wxUser = new SysWechatUser
|
||||
{
|
||||
OpenId = input.OpenId,
|
||||
Mobile = resUserPhoneNumber.PhoneInfo?.PhoneNumber,
|
||||
PlatformType = PlatformTypeEnum.微信小程序
|
||||
};
|
||||
wxUser = await _sysWechatUserRep.AsInsertable(wxUser).ExecuteReturnEntityAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
wxUser.Mobile = resUserPhoneNumber.PhoneInfo?.PhoneNumber;
|
||||
await _sysWechatUserRep.AsUpdateable(wxUser).IgnoreColumns(true).ExecuteCommandAsync();
|
||||
}
|
||||
|
||||
return new WxPhoneOutput
|
||||
{
|
||||
PhoneNumber = resUserPhoneNumber.PhoneInfo?.PhoneNumber
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 微信小程序登录OpenId 🔖
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[AllowAnonymous]
|
||||
[DisplayName("微信小程序登录OpenId")]
|
||||
public async Task<dynamic> WxOpenIdLogin(WxOpenIdLoginInput input)
|
||||
{
|
||||
var wxUser = await _sysWechatUserRep.GetFirstAsync(u => u.OpenId == input.OpenId);
|
||||
if (wxUser == null)
|
||||
throw Oops.Oh("微信小程序登录失败");
|
||||
|
||||
var tokenExpire = await _sysConfigService.GetTokenExpire();
|
||||
return new
|
||||
{
|
||||
wxUser.Avatar,
|
||||
accessToken = JWTEncryption.Encrypt(new Dictionary<string, object>
|
||||
{
|
||||
{ ClaimConst.UserId, wxUser.Id },
|
||||
{ ClaimConst.RealName, wxUser.NickName },
|
||||
{ ClaimConst.LoginMode, LoginModeEnum.APP },
|
||||
}, tokenExpire)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 上传小程序头像
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[AllowAnonymous]
|
||||
[DisplayName("上传小程序头像")]
|
||||
public async Task<SysFile> UploadAvatar([FromForm] UploadAvatarInput input)
|
||||
{
|
||||
var wxUser = await _sysWechatUserRep.GetFirstAsync(u => u.OpenId == input.OpenId);
|
||||
if (wxUser == null)
|
||||
throw Oops.Oh("未找到用户上传失败");
|
||||
|
||||
var res = await _sysFileService.UploadFile(new UploadFileInput { File = input.File, FileType = input.FileType }, "upload/wechatAvatar");
|
||||
wxUser.Avatar = res.Url;
|
||||
await _sysWechatUserRep.AsUpdateable(wxUser).IgnoreColumns(true).ExecuteCommandAsync();
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置小程序用户昵称
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[AllowAnonymous]
|
||||
[HttpPost]
|
||||
public async Task SetNickName(SetNickNameInput input)
|
||||
{
|
||||
var wxUser = await _sysWechatUserRep.GetFirstAsync(u => u.OpenId == input.OpenId);
|
||||
if (wxUser == null)
|
||||
throw Oops.Oh("未找到用户信息设置失败");
|
||||
wxUser.NickName = input.NickName;
|
||||
await _sysWechatUserRep.AsUpdateable(wxUser).IgnoreColumns(true).ExecuteCommandAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取小程序用户信息
|
||||
/// </summary>
|
||||
/// <param name="openid"></param>
|
||||
/// <returns></returns>
|
||||
[AllowAnonymous]
|
||||
public async Task<dynamic> GetUserInfo(string openid)
|
||||
{
|
||||
var wxUser = await _sysWechatUserRep.GetFirstAsync(u => u.OpenId == openid);
|
||||
if (wxUser == null)
|
||||
throw Oops.Oh("未找到用户信息获取失败");
|
||||
return new { nickName = wxUser.NickName, avator = wxUser.Avatar };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取订阅消息模板列表 🔖
|
||||
/// </summary>
|
||||
[DisplayName("获取订阅消息模板列表")]
|
||||
public async Task<dynamic> GetMessageTemplateList()
|
||||
{
|
||||
var accessToken = await GetCgibinToken();
|
||||
var reqTemplate = new WxaApiNewTemplateGetTemplateRequest()
|
||||
{
|
||||
AccessToken = accessToken
|
||||
};
|
||||
var resTemplate = await _wechatApiClient.ExecuteWxaApiNewTemplateGetTemplateAsync(reqTemplate);
|
||||
if (resTemplate.ErrorCode != (int)WechatReturnCodeEnum.请求成功)
|
||||
throw Oops.Oh(resTemplate.ErrorMessage + " " + resTemplate.ErrorCode);
|
||||
|
||||
return resTemplate.TemplateList;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送订阅消息 🔖
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[DisplayName("发送订阅消息")]
|
||||
public async Task<dynamic> SendSubscribeMessage(SendSubscribeMessageInput input)
|
||||
{
|
||||
var accessToken = await GetCgibinToken();
|
||||
var reqMessage = new CgibinMessageSubscribeSendRequest()
|
||||
{
|
||||
AccessToken = accessToken,
|
||||
TemplateId = input.TemplateId,
|
||||
ToUserOpenId = input.ToUserOpenId,
|
||||
Data = input.Data,
|
||||
MiniProgramState = input.MiniprogramState,
|
||||
Language = input.Language,
|
||||
MiniProgramPagePath = input.MiniProgramPagePath
|
||||
};
|
||||
var resMessage = await _wechatApiClient.ExecuteCgibinMessageSubscribeSendAsync(reqMessage);
|
||||
return resMessage;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 增加订阅消息模板 🔖
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <returns></returns>
|
||||
[ApiDescriptionSettings(Name = "AddSubscribeMessageTemplate"), HttpPost]
|
||||
[DisplayName("增加订阅消息模板")]
|
||||
public async Task<dynamic> AddSubscribeMessageTemplate(AddSubscribeMessageTemplateInput input)
|
||||
{
|
||||
var accessToken = await GetCgibinToken();
|
||||
var reqMessage = new WxaApiNewTemplateAddTemplateRequest()
|
||||
{
|
||||
AccessToken = accessToken,
|
||||
TemplateTitleId = input.TemplateTitleId,
|
||||
KeyworkIdList = input.KeyworkIdList,
|
||||
SceneDescription = input.SceneDescription
|
||||
};
|
||||
var resTemplate = await _wechatApiClient.ExecuteWxaApiNewTemplateAddTemplateAsync(reqMessage);
|
||||
return resTemplate;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成带参数小程序二维码(总共生成的码数量限制为 100,000)
|
||||
/// </summary>
|
||||
/// <param name="input"> 扫码进入的小程序页面路径,最大长度 128 个字符,不能为空; eg: pages / index ? id = AY000001 </param>
|
||||
/// <returns></returns>
|
||||
[DisplayName("生成小程序二维码")]
|
||||
[ApiDescriptionSettings(Name = "GenerateQRImage")]
|
||||
public async Task<GenerateQRImageOutput> GenerateQRImageAsync(GenerateQRImageInput input)
|
||||
{
|
||||
GenerateQRImageOutput generateQRImageOutInput = new GenerateQRImageOutput();
|
||||
if (input.PagePath.IsNullOrEmpty())
|
||||
{
|
||||
generateQRImageOutInput.Message = $"生成失败 页面路径不能为空";
|
||||
return generateQRImageOutInput;
|
||||
}
|
||||
|
||||
if (input.ImageName.IsNullOrEmpty())
|
||||
{
|
||||
input.ImageName = DateTime.Now.ToString("yyyyMMddHHmmss");
|
||||
}
|
||||
|
||||
var accessToken = await GetCgibinToken();
|
||||
var request = new CgibinWxaappCreateWxaQrcodeRequest
|
||||
{
|
||||
AccessToken = accessToken,
|
||||
Path = input.PagePath,
|
||||
Width = input.Width
|
||||
};
|
||||
var response = await _wechatApiClient.ExecuteCgibinWxaappCreateWxaQrcodeAsync(request);
|
||||
|
||||
if (response.IsSuccessful())
|
||||
{
|
||||
var QRImagePath = App.GetConfig<string>("Wechat:QRImagePath");
|
||||
var relativeImgPath = string.Empty;
|
||||
|
||||
// 判断路径是绝对路径还是相对路径
|
||||
var isPathRooted = Path.IsPathRooted(QRImagePath);
|
||||
if (!isPathRooted)
|
||||
{
|
||||
// 相对路径
|
||||
relativeImgPath = string.IsNullOrEmpty(QRImagePath) ? Path.Combine("upload", "QRImage") : QRImagePath;
|
||||
QRImagePath = Path.Combine(App.WebHostEnvironment.WebRootPath, relativeImgPath);
|
||||
}
|
||||
|
||||
//判断文件存放路径是否存在
|
||||
if (!Directory.Exists(QRImagePath))
|
||||
{
|
||||
Directory.CreateDirectory(QRImagePath);
|
||||
}
|
||||
// 将二维码图片数据保存为文件
|
||||
var fileName = $"{input.ImageName.ToUpper()}.png";
|
||||
var filePath = Path.Combine(QRImagePath, fileName);
|
||||
if (File.Exists(filePath))
|
||||
{
|
||||
File.Delete(filePath);
|
||||
}
|
||||
File.WriteAllBytes(filePath, response.GetRawBytes());
|
||||
|
||||
generateQRImageOutInput.Success = true;
|
||||
generateQRImageOutInput.ImgPath = filePath;
|
||||
generateQRImageOutInput.RelativeImgPath = Path.Combine(relativeImgPath, fileName);
|
||||
generateQRImageOutInput.Message = "生成成功";
|
||||
}
|
||||
else
|
||||
{
|
||||
// 处理错误情况
|
||||
generateQRImageOutInput.Message = $"生成失败 错误代码:{response.ErrorCode} 错误描述:{response.ErrorMessage}";
|
||||
}
|
||||
return generateQRImageOutInput;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成二维码(获取不受限制的小程序码)
|
||||
/// </summary>
|
||||
/// <param name="input">入参</param>
|
||||
/// <returns></returns>
|
||||
[DisplayName("生成小程序二维码")]
|
||||
[ApiDescriptionSettings(Name = "GenerateQRImageUnlimit")]
|
||||
public async Task<GenerateQRImageOutput> GenerateQRImageUnlimitAsync(GenerateQRImageUnLimitInput input)
|
||||
{
|
||||
GenerateQRImageOutput generateQRImageOutInput = new GenerateQRImageOutput();
|
||||
if (input.PagePath.IsNullOrEmpty())
|
||||
{
|
||||
generateQRImageOutInput.Message = $"生成失败,页面路径不能为空";
|
||||
return generateQRImageOutInput;
|
||||
}
|
||||
|
||||
if (input.Scene.Length > 32)
|
||||
{
|
||||
generateQRImageOutInput.Message = $"生成失败,携带的参数长度超过限制";
|
||||
return generateQRImageOutInput;
|
||||
}
|
||||
|
||||
if (input.ImageName.IsNullOrEmpty())
|
||||
{
|
||||
input.ImageName = DateTime.Now.ToString("yyyyMMddHHmmss");
|
||||
}
|
||||
|
||||
var accessToken = await GetCgibinToken();
|
||||
var request = new WxaGetWxaCodeRequest
|
||||
{
|
||||
AccessToken = accessToken,
|
||||
Width = input.Width,
|
||||
PagePath = input.PagePath,
|
||||
};
|
||||
var response = await _wechatApiClient.ExecuteWxaGetWxaCodeAsync(request);
|
||||
|
||||
if (response.IsSuccessful())
|
||||
{
|
||||
var QRImagePath = App.GetConfig<string>("Wechat:QRImagePath");
|
||||
var relativeImgPath = string.Empty;
|
||||
|
||||
// 判断路径是绝对路径还是相对路径
|
||||
var isPathRooted = Path.IsPathRooted(QRImagePath);
|
||||
if (!isPathRooted)
|
||||
{
|
||||
// 相对路径
|
||||
relativeImgPath = string.IsNullOrEmpty(QRImagePath) ? Path.Combine("upload", "QRImageUnLimit") : QRImagePath;
|
||||
QRImagePath = Path.Combine(App.WebHostEnvironment.WebRootPath, relativeImgPath);
|
||||
}
|
||||
|
||||
//判断文件存放路径是否存在
|
||||
if (!Directory.Exists(QRImagePath))
|
||||
{
|
||||
Directory.CreateDirectory(QRImagePath);
|
||||
}
|
||||
// 将二维码图片数据保存为文件
|
||||
var fileName = $"{input.ImageName.ToUpper()}.png";
|
||||
var filePath = Path.Combine(QRImagePath, fileName);
|
||||
if (File.Exists(filePath))
|
||||
{
|
||||
File.Delete(filePath);
|
||||
}
|
||||
File.WriteAllBytes(filePath, response.GetRawBytes());
|
||||
|
||||
generateQRImageOutInput.Success = true;
|
||||
generateQRImageOutInput.ImgPath = filePath;
|
||||
generateQRImageOutInput.RelativeImgPath = Path.Combine(relativeImgPath, fileName);
|
||||
generateQRImageOutInput.Message = "生成成功";
|
||||
}
|
||||
else
|
||||
{
|
||||
// 处理错误情况
|
||||
generateQRImageOutInput.Message = $"生成失败 错误代码:{response.ErrorCode} 错误描述:{response.ErrorMessage}";
|
||||
}
|
||||
return generateQRImageOutInput;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取Access_token
|
||||
/// </summary>
|
||||
private async Task<string> GetCgibinToken()
|
||||
{
|
||||
return await _wechatApiClientFactory.TryGetWxOpenAccessTokenAsync();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
|
||||
//
|
||||
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
|
||||
//
|
||||
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Admin.NET.Core.Service;
|
||||
|
||||
/// <summary>
|
||||
/// 微信API客户端
|
||||
/// </summary>
|
||||
public partial class WechatApiClientFactory : ISingleton
|
||||
{
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
public readonly WechatOptions _wechatOptions;
|
||||
private readonly SysCacheService _sysCacheService;
|
||||
|
||||
public WechatApiClientFactory(IHttpClientFactory httpClientFactory, IOptions<WechatOptions> wechatOptions, SysCacheService sysCacheService)
|
||||
{
|
||||
_httpClientFactory = httpClientFactory ?? throw new ArgumentNullException(nameof(httpClientFactory));
|
||||
_wechatOptions = wechatOptions.Value ?? throw new ArgumentNullException(nameof(wechatOptions));
|
||||
_sysCacheService = sysCacheService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 微信公众号
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public WechatApiClient CreateWechatClient()
|
||||
{
|
||||
if (string.IsNullOrEmpty(_wechatOptions.WechatAppId) || string.IsNullOrEmpty(_wechatOptions.WechatAppSecret))
|
||||
throw Oops.Oh("微信公众号配置错误");
|
||||
|
||||
var client = WechatApiClientBuilder.Create(new WechatApiClientOptions()
|
||||
{
|
||||
AppId = _wechatOptions.WechatAppId,
|
||||
AppSecret = _wechatOptions.WechatAppSecret,
|
||||
PushToken = _wechatOptions.WechatToken,
|
||||
PushEncodingAESKey = _wechatOptions.WechatEncodingAESKey,
|
||||
})
|
||||
.UseHttpClient(_httpClientFactory.CreateClient(), disposeClient: false) // 设置 HttpClient 不随客户端一同销毁
|
||||
.Build();
|
||||
|
||||
client.Configure(config =>
|
||||
{
|
||||
JsonSerializerSettings jsonSerializerSettings = NewtonsoftJsonSerializer.GetDefaultSerializerSettings();
|
||||
jsonSerializerSettings.Formatting = Formatting.Indented;
|
||||
config.JsonSerializer = new NewtonsoftJsonSerializer(jsonSerializerSettings); // 指定 System.Text.Json JSON序列化
|
||||
// config.JsonSerializer = new SystemTextJsonSerializer(jsonSerializerOptions); // 指定 Newtonsoft.Json JSON序列化
|
||||
});
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 微信小程序
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public WechatApiClient CreateWxOpenClient()
|
||||
{
|
||||
if (string.IsNullOrEmpty(_wechatOptions.WxOpenAppId) || string.IsNullOrEmpty(_wechatOptions.WxOpenAppSecret))
|
||||
throw Oops.Oh("微信小程序配置错误");
|
||||
|
||||
var client = WechatApiClientBuilder.Create(new WechatApiClientOptions()
|
||||
{
|
||||
AppId = _wechatOptions.WxOpenAppId,
|
||||
AppSecret = _wechatOptions.WxOpenAppSecret,
|
||||
PushToken = _wechatOptions.WxToken,
|
||||
PushEncodingAESKey = _wechatOptions.WxEncodingAESKey,
|
||||
})
|
||||
.UseHttpClient(_httpClientFactory.CreateClient(), disposeClient: false) // 设置 HttpClient 不随客户端一同销毁
|
||||
.Build();
|
||||
|
||||
client.Configure(config =>
|
||||
{
|
||||
JsonSerializerSettings jsonSerializerSettings = NewtonsoftJsonSerializer.GetDefaultSerializerSettings();
|
||||
jsonSerializerSettings.Formatting = Formatting.Indented;
|
||||
config.JsonSerializer = new NewtonsoftJsonSerializer(jsonSerializerSettings); // 指定 System.Text.Json JSON序列化
|
||||
// config.JsonSerializer = new SystemTextJsonSerializer(jsonSerializerOptions); // 指定 Newtonsoft.Json JSON序列化
|
||||
});
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取微信公众号AccessToken
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task<string> TryGetWechatAccessTokenAsync()
|
||||
{
|
||||
if (!_sysCacheService.ExistKey($"WxAccessToken_{_wechatOptions.WechatAppId}") || string.IsNullOrEmpty(_sysCacheService.Get<string>($"WxAccessToken_{_wechatOptions.WechatAppId}")))
|
||||
{
|
||||
var client = CreateWechatClient();
|
||||
var reqCgibinToken = new CgibinTokenRequest();
|
||||
var resCgibinToken = await client.ExecuteCgibinTokenAsync(reqCgibinToken);
|
||||
if (resCgibinToken.ErrorCode != (int)WechatReturnCodeEnum.请求成功)
|
||||
throw Oops.Oh(resCgibinToken.ErrorMessage + " " + resCgibinToken.ErrorCode);
|
||||
_sysCacheService.Set($"WxAccessToken_{_wechatOptions.WechatAppId}", resCgibinToken.AccessToken, TimeSpan.FromSeconds(resCgibinToken.ExpiresIn - 60));
|
||||
}
|
||||
|
||||
return _sysCacheService.Get<string>($"WxAccessToken_{_wechatOptions.WechatAppId}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取微信小程序AccessToken
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task<string> TryGetWxOpenAccessTokenAsync()
|
||||
{
|
||||
if (!_sysCacheService.ExistKey($"WxAccessToken_{_wechatOptions.WxOpenAppId}") || string.IsNullOrEmpty(_sysCacheService.Get<string>($"WxAccessToken_{_wechatOptions.WxOpenAppId}")))
|
||||
{
|
||||
var client = CreateWxOpenClient();
|
||||
var reqCgibinToken = new CgibinTokenRequest();
|
||||
var resCgibinToken = await client.ExecuteCgibinTokenAsync(reqCgibinToken);
|
||||
if (resCgibinToken.ErrorCode != (int)WechatReturnCodeEnum.请求成功)
|
||||
throw Oops.Oh(resCgibinToken.ErrorMessage + " " + resCgibinToken.ErrorCode);
|
||||
_sysCacheService.Set($"WxAccessToken_{_wechatOptions.WxOpenAppId}", resCgibinToken.AccessToken, TimeSpan.FromSeconds(resCgibinToken.ExpiresIn - 60));
|
||||
}
|
||||
|
||||
return _sysCacheService.Get<string>($"WxAccessToken_{_wechatOptions.WxOpenAppId}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查微信公众号AccessToken
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task CheckWechatAccessTokenAsync()
|
||||
{
|
||||
if (string.IsNullOrEmpty(_wechatOptions.WechatAppId) || string.IsNullOrEmpty(_wechatOptions.WechatAppSecret)) return;
|
||||
|
||||
var req = new CgibinOpenApiQuotaGetRequest
|
||||
{
|
||||
AccessToken = await TryGetWechatAccessTokenAsync(),
|
||||
CgiPath = "/cgi-bin/token"
|
||||
};
|
||||
var client = CreateWechatClient();
|
||||
var res = await client.ExecuteCgibinOpenApiQuotaGetAsync(req);
|
||||
|
||||
var originColor = Console.ForegroundColor;
|
||||
if (res.ErrorCode != (int)WechatReturnCodeEnum.请求成功)
|
||||
{
|
||||
_sysCacheService.Remove($"WxAccessToken_{_wechatOptions.WechatAppId}");
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.WriteLine("【" + DateTime.Now + "】" + _wechatOptions.WxOpenAppId + " 微信公众号令牌 无效");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Magenta;
|
||||
Console.WriteLine("【" + DateTime.Now + "】" + _wechatOptions.WxOpenAppId + " 微信公众号令牌 有效");
|
||||
}
|
||||
Console.ForegroundColor = originColor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查微信小程序AccessToken
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task CheckWxOpenAccessTokenAsync()
|
||||
{
|
||||
if (string.IsNullOrEmpty(_wechatOptions.WxOpenAppId) || string.IsNullOrEmpty(_wechatOptions.WxOpenAppSecret)) return;
|
||||
|
||||
var req = new CgibinOpenApiQuotaGetRequest
|
||||
{
|
||||
AccessToken = await TryGetWxOpenAccessTokenAsync(),
|
||||
CgiPath = "/cgi-bin/token"
|
||||
};
|
||||
var client = CreateWxOpenClient();
|
||||
var res = await client.ExecuteCgibinOpenApiQuotaGetAsync(req);
|
||||
|
||||
var originColor = Console.ForegroundColor;
|
||||
if (res.ErrorCode != (int)WechatReturnCodeEnum.请求成功)
|
||||
{
|
||||
_sysCacheService.Remove($"WxAccessToken_{_wechatOptions.WxOpenAppId}");
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.WriteLine("【" + DateTime.Now + "】" + _wechatOptions.WxOpenAppId + " 微信小程序令牌 无效");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Magenta;
|
||||
Console.WriteLine("【" + DateTime.Now + "】" + _wechatOptions.WxOpenAppId + " 微信小程序令牌 有效");
|
||||
}
|
||||
Console.ForegroundColor = originColor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取微信JS接口临时票据jsapi_ticket
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task<string> TryGetWechatJsApiTicketAsync()
|
||||
{
|
||||
if (!_sysCacheService.ExistKey($"WxJsApiTicket_{_wechatOptions.WechatAppId}") || string.IsNullOrEmpty(_sysCacheService.Get<string>($"WxJsApiTicket_{_wechatOptions.WechatAppId}")))
|
||||
{
|
||||
var accessToken = await TryGetWechatAccessTokenAsync();
|
||||
var client = CreateWechatClient();
|
||||
var request = new CgibinTicketGetTicketRequest()
|
||||
{
|
||||
AccessToken = accessToken
|
||||
};
|
||||
var response = await client.ExecuteCgibinTicketGetTicketAsync(request);
|
||||
if (!response.IsSuccessful())
|
||||
throw Oops.Oh(response.ErrorMessage + " " + response.ErrorCode);
|
||||
_sysCacheService.Set($"WxJsApiTicket_{_wechatOptions.WechatAppId}", response.Ticket, TimeSpan.FromSeconds(response.ExpiresIn - 60));
|
||||
}
|
||||
return _sysCacheService.Get<string>($"WxJsApiTicket_{_wechatOptions.WechatAppId}");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user