feat: 支持 SSL 证书自动申请并重构 DDNS 任务逻辑
- 实现阿里/腾讯云 SSL 证书的全生命周期自动化管理。 - 重构 NewJob 为 DdnsJob,优化子域名匹配与记录自动创建逻辑。 - 更新项目配置结构,移除冗余的 AppJob 相关代码。
This commit is contained in:
@@ -18,17 +18,17 @@ namespace Hua.DDNS.Test
|
|||||||
var config = new ConfigurationBuilder()
|
var config = new ConfigurationBuilder()
|
||||||
.SetBasePath(AppContext.BaseDirectory)
|
.SetBasePath(AppContext.BaseDirectory)
|
||||||
.AddJsonFile(configPath, true)
|
.AddJsonFile(configPath, true)
|
||||||
.AddEnvironmentVariables()// 把环境变量也放到 Configuraiton当中
|
.AddEnvironmentVariables()// �ѻ�������Ҳ�ŵ� Configuraiton����
|
||||||
.Build();
|
.Build();
|
||||||
|
|
||||||
var sc = DIConfig.ConfigureServices(config);
|
var sc = DIConfig.ConfigureServices(config);
|
||||||
var job = sc.GetService<NewJob>();
|
var job = sc.GetService<DdnsJob>();
|
||||||
|
|
||||||
job?.Execute(null);
|
job?.Execute(null);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Assert.False(false, $"请求异常:{e.Message}");
|
Assert.False(false, $"�����쳣:{e.Message}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ namespace Hua.DDNS.Test.Start
|
|||||||
services.AddSingleton<Url>();
|
services.AddSingleton<Url>();
|
||||||
services.AddSingleton<SqlHelper>();
|
services.AddSingleton<SqlHelper>();
|
||||||
services.AddTransient<IHttpHelper, HttpHelper>();
|
services.AddTransient<IHttpHelper, HttpHelper>();
|
||||||
services.AddTransient<NewJob>();
|
services.AddTransient<DdnsJob>();
|
||||||
return services.BuildServiceProvider();
|
return services.BuildServiceProvider();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ namespace Hua.DDNS.DDNSProviders.Ali
|
|||||||
{
|
{
|
||||||
foreach (var aliDomainRecord in records)
|
foreach (var aliDomainRecord in records)
|
||||||
{
|
{
|
||||||
|
aliDomainRecord.Ip = newIp;
|
||||||
await _client.UpdateDomainRecordAsync(_mapper.Map<UpdateDomainRecordRequest>(aliDomainRecord));
|
await _client.UpdateDomainRecordAsync(_mapper.Map<UpdateDomainRecordRequest>(aliDomainRecord));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
@@ -70,6 +70,7 @@ namespace Hua.DDNS.DDNSProviders.Dnspod
|
|||||||
var response = await _client.CreateRecord(new CreateRecordRequest()
|
var response = await _client.CreateRecord(new CreateRecordRequest()
|
||||||
{
|
{
|
||||||
Domain = _ddnsOption.Domain,
|
Domain = _ddnsOption.Domain,
|
||||||
|
SubDomain = record.SubDomain,
|
||||||
Value = record.Ip,
|
Value = record.Ip,
|
||||||
RecordLine = "默认",
|
RecordLine = "默认",
|
||||||
RecordType = record.RecordType,
|
RecordType = record.RecordType,
|
||||||
|
|||||||
@@ -58,13 +58,14 @@ namespace Hua.DDNS.DDNSProviders.Namesilo
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (from record in records.Cast<XmlNode>()
|
return (from record in records.Cast<XmlNode>()
|
||||||
let subDomain = record.ParentNode.SelectSingleNode("host/text()").Value.Replace(_ddnsOption.Domain, "")
|
let host = record.ParentNode.SelectSingleNode("host/text()").Value
|
||||||
where _ddnsOption.SubDomainArray.Contains(subDomain)
|
let subDomain = host.Replace($".{_ddnsOption.Domain}", "").Replace(_ddnsOption.Domain, "")
|
||||||
|
where _ddnsOption.SubDomainArray.Contains(subDomain) || (subDomain == "" && _ddnsOption.SubDomainArray.Contains("@"))
|
||||||
select new DnsRecord
|
select new DnsRecord
|
||||||
{
|
{
|
||||||
Id = record.ParentNode.SelectSingleNode("record_id/text()").Value,
|
Id = record.ParentNode.SelectSingleNode("record_id/text()").Value,
|
||||||
Ip = record.ParentNode.SelectSingleNode("value/text()").Value,
|
Ip = record.ParentNode.SelectSingleNode("value/text()").Value,
|
||||||
Host = record.ParentNode.SelectSingleNode("host/text()").Value,
|
Host = host,
|
||||||
Domain = _ddnsOption.Domain,
|
Domain = _ddnsOption.Domain,
|
||||||
TTL = record.ParentNode.SelectSingleNode("ttl/text()").Value,
|
TTL = record.ParentNode.SelectSingleNode("ttl/text()").Value,
|
||||||
SubDomain = subDomain,
|
SubDomain = subDomain,
|
||||||
@@ -78,9 +79,9 @@ namespace Hua.DDNS.DDNSProviders.Namesilo
|
|||||||
/// <returns>创建后的解析记录信息</returns>
|
/// <returns>创建后的解析记录信息</returns>
|
||||||
public async Task<DnsRecord> CreateDnsRecordAsync(DnsRecord dnsRecord)
|
public async Task<DnsRecord> CreateDnsRecordAsync(DnsRecord dnsRecord)
|
||||||
{
|
{
|
||||||
var host = dnsRecord.Host[..(dnsRecord.Host.Length - dnsRecord.Domain.Length - 1)];
|
var host = dnsRecord.SubDomain == "@" ? "" : dnsRecord.SubDomain;
|
||||||
//https://www.namesilo.com/api/dnsAddRecord?version=1&type=xml&key=12345&domain=namesilo.com&rrtype=A&rrhost=test&rrvalue=55.55.55.55&rrttl=7207
|
//https://www.namesilo.com/api/dnsAddRecord?version=1&type=xml&key=12345&domain=namesilo.com&rrtype=A&rrhost=test&rrvalue=55.55.55.55&rrttl=7207
|
||||||
var url = $"https://www.namesilo.com/api/dnsUpdateRecord?version=1&type=xml&key={_namesiloOption.ApiKey}&domain={dnsRecord.Domain}&rrtype={dnsRecord.RecordType}&rrid={dnsRecord.Id}&rrhost={host}&rrvalue={dnsRecord.Ip}&rrttl={dnsRecord.TTL}";
|
var url = $"https://www.namesilo.com/api/dnsAddRecord?version=1&type=xml&key={_namesiloOption.ApiKey}&domain={dnsRecord.Domain}&rrtype={dnsRecord.RecordType}&rrhost={host}&rrvalue={dnsRecord.Ip}&rrttl={dnsRecord.TTL}";
|
||||||
|
|
||||||
using var client = new HttpClient();
|
using var client = new HttpClient();
|
||||||
{
|
{
|
||||||
@@ -94,12 +95,13 @@ namespace Hua.DDNS.DDNSProviders.Namesilo
|
|||||||
if (status == null)
|
if (status == null)
|
||||||
{
|
{
|
||||||
await Console.Error.WriteLineAsync($"Failed to create record: '{JsonConvert.SerializeObject(dnsRecord)}'");
|
await Console.Error.WriteLineAsync($"Failed to create record: '{JsonConvert.SerializeObject(dnsRecord)}'");
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status.Value == "300")
|
if (status.Value != "300")
|
||||||
{
|
{
|
||||||
|
await Console.Error.WriteLineAsync($"Failed to create record: '{JsonConvert.SerializeObject(dnsRecord)}', Status: {status.Value}");
|
||||||
return null;
|
return null;
|
||||||
//continue;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return dnsRecord;
|
return dnsRecord;
|
||||||
@@ -117,7 +119,7 @@ namespace Hua.DDNS.DDNSProviders.Namesilo
|
|||||||
{
|
{
|
||||||
using var client = new HttpClient();
|
using var client = new HttpClient();
|
||||||
{
|
{
|
||||||
var host = dnsRecord.Host[..(dnsRecord.Host.Length - dnsRecord.Domain.Length - 1)];
|
var host = dnsRecord.SubDomain == "@" ? "" : dnsRecord.SubDomain;
|
||||||
var request =
|
var request =
|
||||||
$"https://www.namesilo.com/api/dnsUpdateRecord?version=1&type=xml&key={_namesiloOption.ApiKey}&domain={dnsRecord.Domain}&rrid={dnsRecord.Id}&rrhost={host}&rrvalue={newIp}&rrttl={dnsRecord.TTL}";
|
$"https://www.namesilo.com/api/dnsUpdateRecord?version=1&type=xml&key={_namesiloOption.ApiKey}&domain={dnsRecord.Domain}&rrid={dnsRecord.Id}&rrhost={host}&rrvalue={newIp}&rrttl={dnsRecord.TTL}";
|
||||||
//Console.WriteLine(request);
|
//Console.WriteLine(request);
|
||||||
@@ -127,17 +129,12 @@ namespace Hua.DDNS.DDNSProviders.Namesilo
|
|||||||
var reply = new XmlDocument();
|
var reply = new XmlDocument();
|
||||||
reply.LoadXml(content);
|
reply.LoadXml(content);
|
||||||
var status = reply.SelectSingleNode("/namesilo/reply/code/text()");
|
var status = reply.SelectSingleNode("/namesilo/reply/code/text()");
|
||||||
if (status == null)
|
if (status == null || status.Value != "300")
|
||||||
{
|
{
|
||||||
await Console.Error.WriteLineAsync($"Failed to update record: '{dnsRecord.Id}' with Ip: '{newIp}'.");
|
await Console.Error.WriteLineAsync($"Failed to update record: '{dnsRecord.Id}' with Ip: '{newIp}'. Status: {status?.Value}");
|
||||||
continue; //return false;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status.Value == "300") continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await Console.Error.WriteLineAsync($"Failed to update record: '{dnsRecord.Id}' with Ip: '{newIp}'.");
|
|
||||||
continue; //return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return records;
|
return records;
|
||||||
|
|||||||
@@ -1,138 +0,0 @@
|
|||||||
//using Hua.DDNS.Common;
|
|
||||||
//using Hua.DDNS.Common.Config;
|
|
||||||
//using Hua.DDNS.Common.Config.Options;
|
|
||||||
//using Hua.DDNS.Common.Http;
|
|
||||||
//using Hua.DDNS.Start;
|
|
||||||
//using Quartz;
|
|
||||||
//using System.Net;
|
|
||||||
//using AlibabaCloud.OpenApiClient.Models;
|
|
||||||
//using AlibabaCloud.SDK.Alidns20150109.Models;
|
|
||||||
//using Hua.DotNet.Code.Extension;
|
|
||||||
//using TencentCloud.Common;
|
|
||||||
//using TencentCloud.Common.Profile;
|
|
||||||
//using TencentCloud.Dnspod.V20210323;
|
|
||||||
//using TencentCloud.Dnspod.V20210323.Models;
|
|
||||||
|
|
||||||
//using Tea;
|
|
||||||
//using Tea.Utils;
|
|
||||||
//using Hua.DDNS.DDNSProviders;
|
|
||||||
|
|
||||||
//namespace Hua.DDNS.Jobs
|
|
||||||
//{
|
|
||||||
// [DisallowConcurrentExecution]
|
|
||||||
// public class AppJob : IJob, IDisposable
|
|
||||||
// {
|
|
||||||
// private readonly ILogger<AppJob> _logger;
|
|
||||||
// private readonly SettingProvider _settingProvider;
|
|
||||||
// private readonly DdnsOption _ddnsOption;
|
|
||||||
// private readonly IHttpHelper _httpHelper;
|
|
||||||
// public string CurrentIpv4Address;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// public AppJob(ILogger<AppJob> logger,SettingProvider settingProvider, IHttpHelper httpHelper)
|
|
||||||
// {
|
|
||||||
// _logger = logger;
|
|
||||||
// _settingProvider = settingProvider;
|
|
||||||
// _httpHelper = httpHelper;
|
|
||||||
// _ddnsOption = _settingProvider.App.DDNS;
|
|
||||||
|
|
||||||
|
|
||||||
// }
|
|
||||||
// public async Task Execute(IJobExecutionContext context)
|
|
||||||
// {
|
|
||||||
// _logger.LogInformation("开始任务执行");
|
|
||||||
// try
|
|
||||||
// {
|
|
||||||
// var oldIp = (await Dns.GetHostEntryAsync($"{_ddnsOption.SubDomainArray.First()}.{_ddnsOption.Domain}")).AddressList.First();
|
|
||||||
// CurrentIpv4Address = await _httpHelper.GetCurrentPublicIpv4();
|
|
||||||
|
|
||||||
// if (CurrentIpv4Address!=oldIp.ToString())
|
|
||||||
// {
|
|
||||||
// await UpdateDns();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// catch (Exception e)
|
|
||||||
// {
|
|
||||||
// _logger.LogError(e,e.Message);
|
|
||||||
// }
|
|
||||||
// finally
|
|
||||||
// {
|
|
||||||
// _logger.LogInformation("任务执行完成");
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// private async Task UpdateDns()
|
|
||||||
// {
|
|
||||||
// //更新Ip记录
|
|
||||||
// switch (_ddnsOption.Platform)
|
|
||||||
// {
|
|
||||||
// case PlatformEnum.Namesilo:
|
|
||||||
// case PlatformEnum.Tencent:
|
|
||||||
// var _dnspodClient = new DnspodClient(
|
|
||||||
// // 实例化一个认证对象,入参需要传入腾讯云账户secretId,secretKey,此处还需注意密钥对的保密
|
|
||||||
// // 密钥可前往https://console.cloud.tencent.com/cam/capi网站进行获取
|
|
||||||
// new Credential { SecretId = _ddnsOption.Id, SecretKey = _ddnsOption.Key },
|
|
||||||
// "",
|
|
||||||
// // 实例化一个client选项,可选的,没有特殊需求可以跳过
|
|
||||||
// new ClientProfile()
|
|
||||||
// {
|
|
||||||
// // 实例化一个http选项,可选的,没有特殊需求可以跳过
|
|
||||||
// HttpProfile = new HttpProfile { Endpoint = ("dnspod.tencentcloudapi.com") }
|
|
||||||
// });
|
|
||||||
|
|
||||||
// //获取域名解析记录
|
|
||||||
// var describeRecordList = await _dnspodClient.DescribeRecordList(new DescribeRecordListRequest() { Domain = _ddnsOption.Domain });
|
|
||||||
// var record = describeRecordList.RecordList.FirstOrDefault(m =>
|
|
||||||
// m.Value == CurrentIpv4Address && _ddnsOption.SubDomainArray.Any(n => m.Name == n));
|
|
||||||
// if (record!=null && record.Value == CurrentIpv4Address) return;//如果记录已经变更,不调用更新接口
|
|
||||||
|
|
||||||
// await _dnspodClient.ModifyRecordBatch(new ModifyRecordBatchRequest()
|
|
||||||
// {
|
|
||||||
// RecordIdList =
|
|
||||||
// describeRecordList.RecordList
|
|
||||||
// .Where(m => m.Value != CurrentIpv4Address && _ddnsOption.SubDomainArray.Any(n => m.Name == n))
|
|
||||||
// .Select(m => m.RecordId)
|
|
||||||
// .ToArray(),
|
|
||||||
// Change = "value",
|
|
||||||
// ChangeTo = CurrentIpv4Address
|
|
||||||
// });
|
|
||||||
|
|
||||||
// break;
|
|
||||||
// case PlatformEnum.Ali:
|
|
||||||
// var aliClient = new AlibabaCloud.SDK.Alidns20150109.Client(new Config()
|
|
||||||
// {
|
|
||||||
// // 您的 AccessKey ID
|
|
||||||
// AccessKeyId = _ddnsOption.Id,
|
|
||||||
// // 您的 AccessKey Secret
|
|
||||||
// AccessKeySecret = _ddnsOption.Key,
|
|
||||||
// Endpoint = "alidns.cn-beijing.aliyuncs.com",
|
|
||||||
// });
|
|
||||||
|
|
||||||
// var aliDescribeRecordList = (await aliClient.DescribeDomainRecordsAsync(new DescribeDomainRecordsRequest()
|
|
||||||
// {
|
|
||||||
// DomainName = _ddnsOption.Domain
|
|
||||||
// })).Body.DomainRecords.Record;
|
|
||||||
|
|
||||||
// foreach (var aliDomainRecord in aliDescribeRecordList
|
|
||||||
// .Where(m => m.Value != CurrentIpv4Address && _ddnsOption.SubDomainArray.Any(n => m.RR == n)))
|
|
||||||
// {
|
|
||||||
// await aliClient.UpdateDomainRecordAsync(new UpdateDomainRecordRequest()
|
|
||||||
// {
|
|
||||||
// RecordId = aliDomainRecord.RecordId,
|
|
||||||
// RR = aliDomainRecord.RR,
|
|
||||||
// Type = aliDomainRecord.Type,
|
|
||||||
// Value = CurrentIpv4Address,
|
|
||||||
// });
|
|
||||||
// _logger.LogInformation($"Update SubDomain[{aliDomainRecord.RR}.{aliDomainRecord.DomainName}] Value {aliDomainRecord.Value} To {CurrentIpv4Address}");
|
|
||||||
// }
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// public void Dispose()
|
|
||||||
// {
|
|
||||||
// _logger.LogInformation("AppJob已销毁");
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
namespace Hua.DDNS.Jobs
|
|
||||||
{
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Job上下文
|
|
||||||
/// </summary>
|
|
||||||
public class AppJobContext
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -26,12 +26,12 @@ using System.Net.Sockets;
|
|||||||
namespace Hua.DDNS.Jobs
|
namespace Hua.DDNS.Jobs
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 新的 DDNS 任务类,用于定期检查并更新域名解析记录
|
/// DDNS 任务类,用于定期检查并更新域名解析记录
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DisallowConcurrentExecution]
|
[DisallowConcurrentExecution]
|
||||||
public class NewJob : IJob, IDisposable
|
public class DdnsJob : IJob, IDisposable
|
||||||
{
|
{
|
||||||
private readonly ILogger<NewJob> _logger;
|
private readonly ILogger<DdnsJob> _logger;
|
||||||
private readonly IServiceProvider _serviceProvider;
|
private readonly IServiceProvider _serviceProvider;
|
||||||
private readonly DdnsOption _ddnsOption;
|
private readonly DdnsOption _ddnsOption;
|
||||||
private readonly IHttpHelper _httpHelper;
|
private readonly IHttpHelper _httpHelper;
|
||||||
@@ -48,7 +48,7 @@ namespace Hua.DDNS.Jobs
|
|||||||
/// <param name="httpHelper">Http 助手</param>
|
/// <param name="httpHelper">Http 助手</param>
|
||||||
/// <param name="ddnsOption">DDNS 配置选项</param>
|
/// <param name="ddnsOption">DDNS 配置选项</param>
|
||||||
/// <param name="serviceProvider">服务提供者</param>
|
/// <param name="serviceProvider">服务提供者</param>
|
||||||
public NewJob(ILogger<NewJob> logger,IHttpHelper httpHelper,IOptions<DdnsOption> ddnsOption, IServiceProvider serviceProvider)
|
public DdnsJob(ILogger<DdnsJob> logger,IHttpHelper httpHelper,IOptions<DdnsOption> ddnsOption, IServiceProvider serviceProvider)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_httpHelper = httpHelper;
|
_httpHelper = httpHelper;
|
||||||
@@ -73,25 +73,47 @@ namespace Hua.DDNS.Jobs
|
|||||||
};
|
};
|
||||||
newIp = await _httpHelper.GetCurrentPublicIpv4();
|
newIp = await _httpHelper.GetCurrentPublicIpv4();
|
||||||
|
|
||||||
|
if (_ddnsOption.SubDomainArray == null || _ddnsOption.SubDomainArray.Length == 0)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("未配置 SubDomainArray,跳过执行。");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
//1. 获取当前机器ip
|
// 1. 获取所有 DNS 记录
|
||||||
var domain = $"{_ddnsOption.SubDomainArray.First()}.{_ddnsOption.Domain}";
|
var dnsRecordList = (await ddnsProvider!.GetRecordListAsync())?.ToList() ?? new List<DnsRecord>();
|
||||||
IPAddress oldIp = null;
|
|
||||||
oldIp = (await Dns.GetHostEntryAsync(domain)).AddressList.First();
|
|
||||||
|
|
||||||
//1.1 如果当前dns记录与实际dns记录一致,跳出本次执行
|
// 2. 找出需要更新的记录(在配置中且 IP 不一致)
|
||||||
if (newIp == oldIp.ToString()) return;
|
var recordsToUpdate = dnsRecordList
|
||||||
|
.Where(m => _ddnsOption.SubDomainArray.Contains(m.SubDomain) && m.Ip != newIp)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (recordsToUpdate.Any())
|
||||||
|
{
|
||||||
|
_logger.LogInformation("发现 {Count} 个子域名需要更新 IP 为 {NewIp}", recordsToUpdate.Count, newIp);
|
||||||
|
await ddnsProvider.ModifyRecordListAsync(newIp, recordsToUpdate);
|
||||||
|
}
|
||||||
|
|
||||||
var dnsRecordList = await ddnsProvider!.GetRecordListAsync();
|
// 3. 找出需要新增的记录(在配置中但不在 DNS 记录中)
|
||||||
var record = dnsRecordList.FirstOrDefault(m =>
|
var existingSubDomains = dnsRecordList.Select(m => m.SubDomain).ToHashSet();
|
||||||
m.Ip == newIp && _ddnsOption.SubDomainArray.Any(n => m.SubDomain == n));
|
var subDomainsToCreate = _ddnsOption.SubDomainArray
|
||||||
if (record != null && record.Ip == newIp) return; //如果记录已经变更,不调用更新接口
|
.Where(s => !existingSubDomains.Contains(s))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
//3.比较并更新
|
foreach (var subDomain in subDomainsToCreate)
|
||||||
await ddnsProvider.ModifyRecordListAsync(newIp,
|
{
|
||||||
dnsRecordList.Where(m => m.Ip != newIp && _ddnsOption.SubDomainArray.Any(n => m.SubDomain == n)));
|
_logger.LogInformation("正在为子域名 {SubDomain} 创建新的解析记录,IP: {NewIp}", subDomain, newIp);
|
||||||
|
await ddnsProvider.CreateDnsRecordAsync(new DnsRecord
|
||||||
|
{
|
||||||
|
Ip = newIp,
|
||||||
|
SubDomain = subDomain,
|
||||||
|
Domain = _ddnsOption.Domain,
|
||||||
|
Host = string.IsNullOrEmpty(subDomain) || subDomain == "@" ? _ddnsOption.Domain : $"{subDomain}.{_ddnsOption.Domain}",
|
||||||
|
RecordType = "A", // 默认 A 记录
|
||||||
|
TTL = "3600" // 默认 TTL
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (SocketException e)
|
catch (SocketException e)
|
||||||
{
|
{
|
||||||
@@ -120,7 +142,7 @@ namespace Hua.DDNS.Jobs
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_logger.LogInformation("AppJob已销毁");
|
_logger.LogInformation("DdnsJob已销毁");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,11 +5,12 @@ using Hua.DDNS.SslProviders.Tencent;
|
|||||||
using Quartz;
|
using Quartz;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using System.Security.Cryptography.X509Certificates;
|
using System.Security.Cryptography.X509Certificates;
|
||||||
|
using Hua.DDNS.DDNSProviders;
|
||||||
|
|
||||||
namespace Hua.DDNS.Jobs
|
namespace Hua.DDNS.Jobs
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// SSL 证书下载任务类,用于定期检查并更新 SSL 证书
|
/// SSL 证书管理任务类,用于定期检查、申请并更新 SSL 证书
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DisallowConcurrentExecution]
|
[DisallowConcurrentExecution]
|
||||||
public class SslDownloadJob : IJob, IDisposable
|
public class SslDownloadJob : IJob, IDisposable
|
||||||
@@ -17,6 +18,7 @@ namespace Hua.DDNS.Jobs
|
|||||||
private readonly ILogger<SslDownloadJob> _logger;
|
private readonly ILogger<SslDownloadJob> _logger;
|
||||||
private readonly IServiceProvider _serviceProvider;
|
private readonly IServiceProvider _serviceProvider;
|
||||||
private readonly SslDownloadOption _sslDownloadOption;
|
private readonly SslDownloadOption _sslDownloadOption;
|
||||||
|
private readonly DdnsOption _ddnsOption;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 构造函数
|
/// 构造函数
|
||||||
@@ -24,14 +26,17 @@ namespace Hua.DDNS.Jobs
|
|||||||
/// <param name="logger">日志对象</param>
|
/// <param name="logger">日志对象</param>
|
||||||
/// <param name="serviceProvider">服务提供者</param>
|
/// <param name="serviceProvider">服务提供者</param>
|
||||||
/// <param name="sslDownloadOption">SSL 下载配置选项</param>
|
/// <param name="sslDownloadOption">SSL 下载配置选项</param>
|
||||||
|
/// <param name="ddnsOption">DDNS 配置选项</param>
|
||||||
public SslDownloadJob(
|
public SslDownloadJob(
|
||||||
ILogger<SslDownloadJob> logger,
|
ILogger<SslDownloadJob> logger,
|
||||||
IServiceProvider serviceProvider,
|
IServiceProvider serviceProvider,
|
||||||
IOptions<SslDownloadOption> sslDownloadOption)
|
IOptions<SslDownloadOption> sslDownloadOption,
|
||||||
|
IOptions<DdnsOption> ddnsOption)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_serviceProvider = serviceProvider;
|
_serviceProvider = serviceProvider;
|
||||||
_sslDownloadOption = sslDownloadOption.Value;
|
_sslDownloadOption = sslDownloadOption.Value;
|
||||||
|
_ddnsOption = ddnsOption.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -42,27 +47,27 @@ namespace Hua.DDNS.Jobs
|
|||||||
{
|
{
|
||||||
if (!_sslDownloadOption.Enabled)
|
if (!_sslDownloadOption.Enabled)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("SSL下载任务已禁用,跳过执行");
|
_logger.LogInformation("SSL管理任务已禁用,跳过执行");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogInformation("开始SSL文件下载任务");
|
_logger.LogInformation("开始SSL文件管理任务");
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
ISslDownloadProvider? sslProvider = _sslDownloadOption.Platform switch
|
ISslManagementProvider? sslProvider = _sslDownloadOption.Platform switch
|
||||||
{
|
{
|
||||||
SslPlatformEnum.Ali => _serviceProvider.GetService(typeof(AliSslProvider)) as ISslDownloadProvider,
|
SslPlatformEnum.Ali => _serviceProvider.GetService(typeof(AliSslProvider)) as ISslManagementProvider,
|
||||||
SslPlatformEnum.Tencent => _serviceProvider.GetService(typeof(TencentSslProvider)) as ISslDownloadProvider,
|
SslPlatformEnum.Tencent => _serviceProvider.GetService(typeof(TencentSslProvider)) as ISslManagementProvider,
|
||||||
_ => null
|
_ => null
|
||||||
};
|
};
|
||||||
|
|
||||||
if (sslProvider == null)
|
if (sslProvider == null)
|
||||||
{
|
{
|
||||||
_logger.LogError($"未找到 SSL 下载提供者: {_sslDownloadOption.Platform}");
|
_logger.LogError($"未找到 SSL 管理提供者: {_sslDownloadOption.Platform}");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
;
|
|
||||||
if (!Directory.Exists(_sslDownloadOption.SavePath))
|
if (!Directory.Exists(_sslDownloadOption.SavePath))
|
||||||
{
|
{
|
||||||
Directory.CreateDirectory(_sslDownloadOption.SavePath);
|
Directory.CreateDirectory(_sslDownloadOption.SavePath);
|
||||||
@@ -70,22 +75,72 @@ namespace Hua.DDNS.Jobs
|
|||||||
}
|
}
|
||||||
|
|
||||||
var certificates = await sslProvider.GetCertificatesAsync();
|
var certificates = await sslProvider.GetCertificatesAsync();
|
||||||
|
_logger.LogInformation($"获取到 {certificates.Count} 个{_sslDownloadOption.Platform} SSL 证书");
|
||||||
var downloadTasks = new List<Task>();
|
var downloadTasks = new List<Task>();
|
||||||
|
|
||||||
foreach (var item in _sslDownloadOption.DownloadItems)
|
// 合并 DownloadItems 和 DdnsOption.SubDomainArray
|
||||||
|
var itemsToProcess = _sslDownloadOption.DownloadItems?.ToList() ?? new List<SslDownloadItem>();
|
||||||
|
if (_ddnsOption.SubDomainArray != null && !string.IsNullOrEmpty(_ddnsOption.Domain))
|
||||||
|
{
|
||||||
|
foreach (var sub in _ddnsOption.SubDomainArray)
|
||||||
|
{
|
||||||
|
var fullDomain = (string.IsNullOrEmpty(sub) || sub == "@")
|
||||||
|
? _ddnsOption.Domain
|
||||||
|
: $"{sub}.{_ddnsOption.Domain}";
|
||||||
|
|
||||||
|
if (!itemsToProcess.Any(i => i.Domain == fullDomain))
|
||||||
|
{
|
||||||
|
itemsToProcess.Add(new SslDownloadItem
|
||||||
|
{
|
||||||
|
Domain = fullDomain,
|
||||||
|
FileName = $"{fullDomain}.pem"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var item in itemsToProcess)
|
||||||
{
|
{
|
||||||
var matchingCertificates = certificates.Where(c => c.Domain == item.Domain).ToList();
|
var matchingCertificates = certificates.Where(c => c.Domain == item.Domain).ToList();
|
||||||
|
|
||||||
if (matchingCertificates.Count == 0)
|
if (matchingCertificates.Count == 0)
|
||||||
{
|
{
|
||||||
_logger.LogWarning($"未找到域名 {item.Domain} 的证书,跳过下载");
|
_logger.LogWarning($"未找到域名 {item.Domain} 的证书,尝试自动申请");
|
||||||
|
var newCertId = await sslProvider.ApplyCertificateAsync(item.Domain);
|
||||||
|
if (!string.IsNullOrEmpty(newCertId))
|
||||||
|
{
|
||||||
|
_logger.LogInformation($"域名 {item.Domain} 证书申请已提交,ID: {newCertId},重新获取证书列表");
|
||||||
|
// 重新获取列表以获取新申请的证书信息
|
||||||
|
certificates = await sslProvider.GetCertificatesAsync();
|
||||||
|
matchingCertificates = certificates.Where(c => c.Domain == item.Domain).ToList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否有正在验证中的证书
|
||||||
|
var isPending = _sslDownloadOption.Platform switch
|
||||||
|
{
|
||||||
|
SslPlatformEnum.Ali => matchingCertificates.Any(c => c.StatusMsg == "CHECK" || c.StatusMsg == "PAYED"),
|
||||||
|
SslPlatformEnum.Tencent => matchingCertificates.Any(c => c.Status == 0 || c.Status == 4 || c.Status == 5 || c.Status == 8),
|
||||||
|
_ => false
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isPending && !matchingCertificates.Any(c => IsDownloadable(c, _sslDownloadOption.Platform)))
|
||||||
|
{
|
||||||
|
_logger.LogInformation($"域名 {item.Domain} 的证书正在申请/验证中,跳过下载");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var certificate = matchingCertificates.OrderByDescending(c => c.CertEndTime).First();
|
var downloadableCertificates = matchingCertificates.Where(c => IsDownloadable(c, _sslDownloadOption.Platform)).ToList();
|
||||||
|
|
||||||
|
if (downloadableCertificates.Count == 0)
|
||||||
|
{
|
||||||
|
_logger.LogWarning($"域名 {item.Domain} 没有可下载的已签发证书,跳过下载");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var certificate = downloadableCertificates.OrderByDescending(c => c.CertEndTime).First();
|
||||||
|
|
||||||
var localCertExpiry = GetLocalCertificateExpiry(item);
|
var localCertExpiry = GetLocalCertificateExpiry(item);
|
||||||
var daysUntilExpiry = (certificate.CertEndTime - DateTime.Now).Days;
|
|
||||||
|
|
||||||
if (localCertExpiry == null)
|
if (localCertExpiry == null)
|
||||||
{
|
{
|
||||||
@@ -115,14 +170,30 @@ namespace Hua.DDNS.Jobs
|
|||||||
|
|
||||||
await Task.WhenAll(downloadTasks);
|
await Task.WhenAll(downloadTasks);
|
||||||
|
|
||||||
_logger.LogInformation($"SSL文件下载任务完成,共下载 {downloadTasks.Count} 个文件");
|
_logger.LogInformation($"SSL文件管理任务完成,共下载 {downloadTasks.Count} 个文件");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "SSL文件下载任务执行失败");
|
_logger.LogError(ex, "SSL文件管理任务执行失败");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 判断证书是否可以下载(已签发)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="certificate">证书信息</param>
|
||||||
|
/// <param name="platform">所属平台</param>
|
||||||
|
/// <returns>是否可下载</returns>
|
||||||
|
private bool IsDownloadable(SslCertificate certificate, SslPlatformEnum platform)
|
||||||
|
{
|
||||||
|
return platform switch
|
||||||
|
{
|
||||||
|
SslPlatformEnum.Ali => certificate.StatusMsg == "ISSUED",
|
||||||
|
SslPlatformEnum.Tencent => certificate.Status == 1,
|
||||||
|
_ => false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取本地证书的过期时间
|
/// 获取本地证书的过期时间
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -163,7 +234,7 @@ namespace Hua.DDNS.Jobs
|
|||||||
/// <param name="certificateId">证书 ID</param>
|
/// <param name="certificateId">证书 ID</param>
|
||||||
/// <param name="domain">域名</param>
|
/// <param name="domain">域名</param>
|
||||||
/// <returns>Task</returns>
|
/// <returns>Task</returns>
|
||||||
private async Task DownloadFileAsync(ISslDownloadProvider provider, SslDownloadItem item, string certificateId, string domain)
|
private async Task DownloadFileAsync(ISslManagementProvider provider, SslDownloadItem item, string certificateId, string domain)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
namespace Hua.DDNS.SslProviders.Ali
|
||||||
|
{
|
||||||
|
public class AliSslApplyOption
|
||||||
|
{
|
||||||
|
public string? ProductCode { get; set; }
|
||||||
|
public string? Username { get; set; }
|
||||||
|
public string? Phone { get; set; }
|
||||||
|
public string? Email { get; set; }
|
||||||
|
public string? ValidateType { get; set; }
|
||||||
|
public Dictionary<string, string>? Tags { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,16 +5,18 @@ using Hua.DDNS.Common.Config.Options;
|
|||||||
using Hua.DDNS.SslProviders;
|
using Hua.DDNS.SslProviders;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
namespace Hua.DDNS.SslProviders.Ali
|
namespace Hua.DDNS.SslProviders.Ali
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 阿里云 SSL 证书下载提供者
|
/// 阿里云 SSL 证书管理提供者
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class AliSslProvider : ISslDownloadProvider
|
public class AliSslProvider : ISslManagementProvider
|
||||||
{
|
{
|
||||||
private readonly Client _client;
|
private readonly Client _client;
|
||||||
private readonly AliCloudOption _aliCloudOption;
|
private readonly AliCloudOption _aliCloudOption;
|
||||||
|
private readonly AliSslApplyOption _aliSslApplyOption;
|
||||||
private readonly ILogger<AliSslProvider> _logger;
|
private readonly ILogger<AliSslProvider> _logger;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -24,10 +26,12 @@ namespace Hua.DDNS.SslProviders.Ali
|
|||||||
/// <param name="aliCloudOption">阿里云配置</param>
|
/// <param name="aliCloudOption">阿里云配置</param>
|
||||||
public AliSslProvider(
|
public AliSslProvider(
|
||||||
ILogger<AliSslProvider> logger,
|
ILogger<AliSslProvider> logger,
|
||||||
IOptions<AliCloudOption> aliCloudOption)
|
IOptions<AliCloudOption> aliCloudOption,
|
||||||
|
IOptions<AliSslApplyOption> aliSslApplyOption)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_aliCloudOption = aliCloudOption.Value;
|
_aliCloudOption = aliCloudOption.Value;
|
||||||
|
_aliSslApplyOption = aliSslApplyOption.Value;
|
||||||
|
|
||||||
var config = new Config
|
var config = new Config
|
||||||
{
|
{
|
||||||
@@ -65,7 +69,7 @@ namespace Hua.DDNS.SslProviders.Ali
|
|||||||
CertificateId = cert.CertificateId.ToString(),
|
CertificateId = cert.CertificateId.ToString(),
|
||||||
Domain = cert.Domain,
|
Domain = cert.Domain,
|
||||||
Alias = cert.Name,
|
Alias = cert.Name,
|
||||||
CertEndTime = string.IsNullOrEmpty(cert.EndDate) ? DateTime.MinValue : DateTime.Parse(cert.EndDate),
|
CertEndTime = string.IsNullOrEmpty(cert.EndDate) ? DateTime.MaxValue : DateTime.Parse(cert.EndDate),
|
||||||
StatusMsg = cert.Status
|
StatusMsg = cert.Status
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -92,9 +96,44 @@ namespace Hua.DDNS.SslProviders.Ali
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// TODO: 阿里云证书下载逻辑
|
if (!long.TryParse(certificateId, out var certId))
|
||||||
_logger.LogWarning($"阿里云 SSL 证书下载功能待实现: {certificateId}");
|
{
|
||||||
return false;
|
throw new ArgumentException("Invalid certificate ID format", nameof(certificateId));
|
||||||
|
}
|
||||||
|
|
||||||
|
var request = new GetUserCertificateDetailRequest
|
||||||
|
{
|
||||||
|
CertId = certId
|
||||||
|
};
|
||||||
|
|
||||||
|
var response = await _client.GetUserCertificateDetailAsync(request);
|
||||||
|
if (response?.Body == null)
|
||||||
|
{
|
||||||
|
_logger.LogError($"阿里云证书详情返回为空: {certificateId}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(response.Body.Cert) || string.IsNullOrWhiteSpace(response.Body.Key))
|
||||||
|
{
|
||||||
|
_logger.LogError($"阿里云证书内容为空: {certificateId}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Directory.Exists(savePath))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(savePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
var certFileName = Path.GetFileNameWithoutExtension(fileName);
|
||||||
|
var certSavePath = Path.Combine(savePath, $"{certFileName}_bundle.crt");
|
||||||
|
var keySavePath = Path.Combine(savePath, $"{certFileName}.key");
|
||||||
|
|
||||||
|
await File.WriteAllTextAsync(certSavePath, response.Body.Cert, new UTF8Encoding(false));
|
||||||
|
await File.WriteAllTextAsync(keySavePath, response.Body.Key, new UTF8Encoding(false));
|
||||||
|
|
||||||
|
_logger.LogInformation($"阿里云证书保存成功: {certSavePath}");
|
||||||
|
_logger.LogInformation($"阿里云私钥保存成功: {keySavePath}");
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -113,15 +152,24 @@ namespace Hua.DDNS.SslProviders.Ali
|
|||||||
{
|
{
|
||||||
var certificates = await GetCertificatesAsync();
|
var certificates = await GetCertificatesAsync();
|
||||||
// 筛选过期的证书
|
// 筛选过期的证书
|
||||||
var expiredCertificates = certificates.Where(c => c.CertEndTime != DateTime.MinValue && c.CertEndTime < DateTime.Now).ToList();
|
var expiredCertificates = certificates.Where(c =>
|
||||||
|
c.CertEndTime != DateTime.MaxValue &&
|
||||||
|
c.CertEndTime < DateTime.Now &&
|
||||||
|
c.StatusMsg != "CHECK").ToList();
|
||||||
|
|
||||||
foreach (var cert in expiredCertificates)
|
foreach (var cert in expiredCertificates)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
if (!long.TryParse(cert.CertificateId, out var certId))
|
||||||
|
{
|
||||||
|
_logger.LogWarning($"阿里云证书 ID 格式不正确: {cert.CertificateId}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
var deleteRequest = new DeleteUserCertificateRequest
|
var deleteRequest = new DeleteUserCertificateRequest
|
||||||
{
|
{
|
||||||
CertId = long.Parse(cert.CertificateId)
|
CertId = certId
|
||||||
};
|
};
|
||||||
await _client.DeleteUserCertificateAsync(deleteRequest);
|
await _client.DeleteUserCertificateAsync(deleteRequest);
|
||||||
_logger.LogInformation($"已删除阿里云过期证书: {cert.Domain} ({cert.CertificateId}), 过期时间: {cert.CertEndTime}");
|
_logger.LogInformation($"已删除阿里云过期证书: {cert.Domain} ({cert.CertificateId}), 过期时间: {cert.CertEndTime}");
|
||||||
@@ -138,5 +186,54 @@ namespace Hua.DDNS.SslProviders.Ali
|
|||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 异步申请 SSL 证书
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="domain">域名</param>
|
||||||
|
/// <returns>申请结果,成功返回证书 ID,失败返回 null</returns>
|
||||||
|
public async Task<string?> ApplyCertificateAsync(string domain)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_logger.LogInformation($"开始为域名 {domain} 申请阿里云免费 SSL 证书");
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(_aliSslApplyOption.ProductCode) ||
|
||||||
|
string.IsNullOrWhiteSpace(_aliSslApplyOption.Username) ||
|
||||||
|
string.IsNullOrWhiteSpace(_aliSslApplyOption.Phone) ||
|
||||||
|
string.IsNullOrWhiteSpace(_aliSslApplyOption.Email) ||
|
||||||
|
string.IsNullOrWhiteSpace(_aliSslApplyOption.ValidateType))
|
||||||
|
{
|
||||||
|
_logger.LogError($"阿里云证书申请配置不完整,无法申请证书: {domain}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var request = new CreateCertificateRequestRequest
|
||||||
|
{
|
||||||
|
Domain = domain,
|
||||||
|
ProductCode = _aliSslApplyOption.ProductCode,
|
||||||
|
Username = _aliSslApplyOption.Username,
|
||||||
|
Phone = _aliSslApplyOption.Phone,
|
||||||
|
Email = _aliSslApplyOption.Email,
|
||||||
|
ValidateType = _aliSslApplyOption.ValidateType
|
||||||
|
};
|
||||||
|
|
||||||
|
var response = await _client.CreateCertificateRequestAsync(request);
|
||||||
|
var orderId = response?.Body?.OrderId;
|
||||||
|
if (orderId.HasValue)
|
||||||
|
{
|
||||||
|
_logger.LogInformation($"阿里云 SSL 证书申请提交成功: {domain}, OrderId: {orderId.Value}");
|
||||||
|
return orderId.Value.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogError($"阿里云 SSL 证书申请失败: {domain}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, $"申请阿里云 SSL 证书时发生错误: {domain}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-2
@@ -1,9 +1,9 @@
|
|||||||
namespace Hua.DDNS.SslProviders
|
namespace Hua.DDNS.SslProviders
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// SSL 证书下载提供者接口
|
/// SSL 证书管理提供者接口
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface ISslDownloadProvider
|
public interface ISslManagementProvider
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 异步获取 SSL 证书列表
|
/// 异步获取 SSL 证书列表
|
||||||
@@ -20,6 +20,13 @@ namespace Hua.DDNS.SslProviders
|
|||||||
/// <returns>下载成功返回 true,否则返回 false</returns>
|
/// <returns>下载成功返回 true,否则返回 false</returns>
|
||||||
Task<bool> DownloadCertificateAsync(string certificateId, string savePath, string fileName);
|
Task<bool> DownloadCertificateAsync(string certificateId, string savePath, string fileName);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 异步申请 SSL 证书
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="domain">域名</param>
|
||||||
|
/// <returns>申请结果,成功返回证书 ID,失败返回 null</returns>
|
||||||
|
Task<string?> ApplyCertificateAsync(string domain);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 异步清理无效证书
|
/// 异步清理无效证书
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -11,9 +11,9 @@ using System.IO.Compression;
|
|||||||
namespace Hua.DDNS.SslProviders.Tencent
|
namespace Hua.DDNS.SslProviders.Tencent
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 腾讯云 SSL 证书下载提供者
|
/// 腾讯云 SSL 证书管理提供者
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class TencentSslProvider : ISslDownloadProvider
|
public class TencentSslProvider : ISslManagementProvider
|
||||||
{
|
{
|
||||||
private readonly SslClient _client;
|
private readonly SslClient _client;
|
||||||
private readonly TencentCloudOption _tencentCloudOption;
|
private readonly TencentCloudOption _tencentCloudOption;
|
||||||
@@ -66,8 +66,8 @@ namespace Hua.DDNS.SslProviders.Tencent
|
|||||||
CertificateId = cert.CertificateId,
|
CertificateId = cert.CertificateId,
|
||||||
Domain = cert.Domain,
|
Domain = cert.Domain,
|
||||||
Alias = cert.Alias,
|
Alias = cert.Alias,
|
||||||
CertBeginTime = DateTime.Parse(cert.CertBeginTime),
|
CertBeginTime = string.IsNullOrEmpty(cert.CertBeginTime) ? DateTime.MinValue : DateTime.Parse(cert.CertBeginTime),
|
||||||
CertEndTime = DateTime.Parse(cert.CertEndTime),
|
CertEndTime = string.IsNullOrEmpty(cert.CertEndTime) ? DateTime.MaxValue : DateTime.Parse(cert.CertEndTime),
|
||||||
Status = (int)(cert.Status ?? 0),
|
Status = (int)(cert.Status ?? 0),
|
||||||
StatusMsg = cert.StatusMsg
|
StatusMsg = cert.StatusMsg
|
||||||
});
|
});
|
||||||
@@ -202,7 +202,10 @@ namespace Hua.DDNS.SslProviders.Tencent
|
|||||||
{
|
{
|
||||||
var certificates = await GetCertificatesAsync();
|
var certificates = await GetCertificatesAsync();
|
||||||
// 清理已过期的证书 (Status 为 10 或者当前时间已过过期时间)
|
// 清理已过期的证书 (Status 为 10 或者当前时间已过过期时间)
|
||||||
var expiredCertificates = certificates.Where(c => c.CertEndTime < DateTime.Now || c.Status == 10).ToList();
|
// 排除过期时间为 MaxValue 的情况(即未签发的证书)
|
||||||
|
var expiredCertificates = certificates.Where(c =>
|
||||||
|
(c.CertEndTime != DateTime.MaxValue && c.CertEndTime < DateTime.Now) ||
|
||||||
|
c.Status == 10).ToList();
|
||||||
|
|
||||||
foreach (var cert in expiredCertificates)
|
foreach (var cert in expiredCertificates)
|
||||||
{
|
{
|
||||||
@@ -227,5 +230,40 @@ namespace Hua.DDNS.SslProviders.Tencent
|
|||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 异步申请 SSL 证书
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="domain">域名</param>
|
||||||
|
/// <returns>申请结果,成功返回证书 ID,失败返回 null</returns>
|
||||||
|
public async Task<string?> ApplyCertificateAsync(string domain)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_logger.LogInformation($"开始为域名 {domain} 申请腾讯云免费 SSL 证书");
|
||||||
|
|
||||||
|
var request = new ApplyCertificateRequest
|
||||||
|
{
|
||||||
|
DomainName = domain,
|
||||||
|
DvAuthMethod = "DNS" // 默认使用 DNS 验证
|
||||||
|
};
|
||||||
|
|
||||||
|
var response = await _client.ApplyCertificate(request);
|
||||||
|
|
||||||
|
if (response != null && !string.IsNullOrEmpty(response.CertificateId))
|
||||||
|
{
|
||||||
|
_logger.LogInformation($"腾讯云 SSL 证书申请提交成功: {domain}, 证书 ID: {response.CertificateId}");
|
||||||
|
return response.CertificateId;
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogError($"腾讯云 SSL 证书申请失败: {domain}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, $"申请腾讯云 SSL 证书时发生错误: {domain}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ namespace Hua.DDNS.Start
|
|||||||
services.Configure<TencentCloudOption>(hostContext.Configuration.GetSection("TencentCloud"));
|
services.Configure<TencentCloudOption>(hostContext.Configuration.GetSection("TencentCloud"));
|
||||||
services.Configure<AliCloudOption>(hostContext.Configuration.GetSection("AliCloud"));
|
services.Configure<AliCloudOption>(hostContext.Configuration.GetSection("AliCloud"));
|
||||||
services.Configure<SslDownloadOption>(hostContext.Configuration.GetSection("SslDownload"));
|
services.Configure<SslDownloadOption>(hostContext.Configuration.GetSection("SslDownload"));
|
||||||
|
services.Configure<Hua.DDNS.SslProviders.Ali.AliSslApplyOption>(hostContext.Configuration.GetSection("SslDownload:AliApply"));
|
||||||
|
|
||||||
// 配置依赖注入和 Quartz
|
// 配置依赖注入和 Quartz
|
||||||
ConfigDi(hostContext, services);
|
ConfigDi(hostContext, services);
|
||||||
@@ -119,19 +120,19 @@ namespace Hua.DDNS.Start
|
|||||||
q.UseInMemoryStore();
|
q.UseInMemoryStore();
|
||||||
q.UseDefaultThreadPool(tp => { tp.MaxConcurrency = 10; });
|
q.UseDefaultThreadPool(tp => { tp.MaxConcurrency = 10; });
|
||||||
|
|
||||||
// 配置 DDNS 任务 (NewJob)
|
// 配置 DDNS 任务 (DdnsJob)
|
||||||
var appJobKey = new JobKey("NewJob", "NewJobGroup");
|
var appJobKey = new JobKey("DdnsJob", "DdnsJobGroup");
|
||||||
q.AddJob<NewJob>(j => j
|
q.AddJob<DdnsJob>(j => j
|
||||||
.StoreDurably()
|
.StoreDurably()
|
||||||
.WithIdentity(appJobKey)
|
.WithIdentity(appJobKey)
|
||||||
.WithDescription("NewJob")
|
.WithDescription("DdnsJob")
|
||||||
);
|
);
|
||||||
|
|
||||||
q.AddTrigger(t => t
|
q.AddTrigger(t => t
|
||||||
.WithIdentity("NewJob Trigger")
|
.WithIdentity("DdnsJob Trigger")
|
||||||
.ForJob(appJobKey)
|
.ForJob(appJobKey)
|
||||||
.WithCronSchedule(hostContext.Configuration.GetSection("App:AppJob:Corn").Value)
|
.WithCronSchedule(hostContext.Configuration.GetSection("App:AppJob:Corn").Value)
|
||||||
.WithDescription("NewJob trigger")
|
.WithDescription("DdnsJob trigger")
|
||||||
.StartNow()
|
.StartNow()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -195,7 +195,7 @@ Hua.DDNS/
|
|||||||
│ └── ...
|
│ └── ...
|
||||||
├── Jobs/ # Scheduled jobs
|
├── Jobs/ # Scheduled jobs
|
||||||
│ ├── AppJob.cs
|
│ ├── AppJob.cs
|
||||||
│ ├── NewJob.cs
|
│ ├── DdnsJob.cs
|
||||||
│ └── SslDownloadJob.cs
|
│ └── SslDownloadJob.cs
|
||||||
├── Models/ # Data models
|
├── Models/ # Data models
|
||||||
├── SslProviders/ # SSL certificate providers
|
├── SslProviders/ # SSL certificate providers
|
||||||
|
|||||||
Reference in New Issue
Block a user