From b8083381b6b45a850f42b759810699eca5bf7d9b Mon Sep 17 00:00:00 2001 From: xiang Date: Sat, 2 May 2026 14:36:21 +0800 Subject: [PATCH] =?UTF-8?q?feat:ddns=20=E5=8A=A8=E6=80=81=E8=A7=A3?= =?UTF-8?q?=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 8 ++ .../config/AliyunDnsPropertyConfig.java | 17 +++ .../com/xiang/common/config/RedisConfig.java | 2 - .../common/enums/DingTalkBizTypeEnum.java | 20 ++++ .../common/factory/ScriptDingTalkFactory.java | 19 +++ .../java/com/xiang/common/utils/IpUtils.java | 21 ++++ .../schedule/DomainDynamicAnalysisTask.java | 31 +++++ .../domain/server/DomainController.java | 45 +++++++ .../module/domain/service/IDomainService.java | 15 +++ .../domain/service/IDomainServiceImpl.java | 111 ++++++++++++++++++ src/main/resources/application-local.yml | 33 +++++- 11 files changed, 318 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/xiang/common/config/AliyunDnsPropertyConfig.java create mode 100644 src/main/java/com/xiang/common/enums/DingTalkBizTypeEnum.java create mode 100644 src/main/java/com/xiang/common/factory/ScriptDingTalkFactory.java create mode 100644 src/main/java/com/xiang/common/utils/IpUtils.java create mode 100644 src/main/java/com/xiang/service/module/domain/schedule/DomainDynamicAnalysisTask.java create mode 100644 src/main/java/com/xiang/service/module/domain/server/DomainController.java create mode 100644 src/main/java/com/xiang/service/module/domain/service/IDomainService.java create mode 100644 src/main/java/com/xiang/service/module/domain/service/IDomainServiceImpl.java diff --git a/pom.xml b/pom.xml index af9e3d0..a65e6bc 100644 --- a/pom.xml +++ b/pom.xml @@ -145,6 +145,14 @@ alibaba-dingtalk-service-sdk 2.0.0 + + + + com.aliyun + alidns20150109 + 3.4.7 + + \ No newline at end of file diff --git a/src/main/java/com/xiang/common/config/AliyunDnsPropertyConfig.java b/src/main/java/com/xiang/common/config/AliyunDnsPropertyConfig.java new file mode 100644 index 0000000..e092887 --- /dev/null +++ b/src/main/java/com/xiang/common/config/AliyunDnsPropertyConfig.java @@ -0,0 +1,17 @@ +package com.xiang.common.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component +@ConfigurationProperties(prefix = "aliyun.dns") +@Data +public class AliyunDnsPropertyConfig { + + private List RR; + + private String rootDomain; +} diff --git a/src/main/java/com/xiang/common/config/RedisConfig.java b/src/main/java/com/xiang/common/config/RedisConfig.java index 60f5a5a..ad5657a 100644 --- a/src/main/java/com/xiang/common/config/RedisConfig.java +++ b/src/main/java/com/xiang/common/config/RedisConfig.java @@ -3,8 +3,6 @@ package com.xiang.common.config; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; -import com.xiang.common.utils.RedisService; -import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; diff --git a/src/main/java/com/xiang/common/enums/DingTalkBizTypeEnum.java b/src/main/java/com/xiang/common/enums/DingTalkBizTypeEnum.java new file mode 100644 index 0000000..f197b91 --- /dev/null +++ b/src/main/java/com/xiang/common/enums/DingTalkBizTypeEnum.java @@ -0,0 +1,20 @@ +package com.xiang.common.enums; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +/** + * @Author: xiang + * @Date: 2026-01-04 16:13 + */ +@Getter +@RequiredArgsConstructor +public enum DingTalkBizTypeEnum implements BaseDingTalkBizType { + + JT("venue", "江南体育中心"), + XB("xb", "股票基金变化通知"), + SCRIPT("script", "脚本运行通知群") + ; + private final String bizName; + private final String desc; +} diff --git a/src/main/java/com/xiang/common/factory/ScriptDingTalkFactory.java b/src/main/java/com/xiang/common/factory/ScriptDingTalkFactory.java new file mode 100644 index 0000000..d97e7a8 --- /dev/null +++ b/src/main/java/com/xiang/common/factory/ScriptDingTalkFactory.java @@ -0,0 +1,19 @@ +package com.xiang.common.factory; + +import com.xiang.common.config.DingTalkRobotProperties; +import com.xiang.common.enums.DingTalkBizTypeEnum; +import com.xiang.common.utils.dingTalk.AbstractDingTalkFactory; +import com.xiang.common.utils.dingTalk.DingTalkSender; +import org.springframework.stereotype.Service; + +@Service +public class ScriptDingTalkFactory extends AbstractDingTalkFactory { + public ScriptDingTalkFactory(DingTalkRobotProperties dingTalkRobotProperties, DingTalkSender dingTalkSender) { + super(dingTalkRobotProperties, dingTalkSender); + } + + @Override + public void sendMsg(String msg) { + getClient(DingTalkBizTypeEnum.SCRIPT).sendDingTalkMsg(msg); + } +} diff --git a/src/main/java/com/xiang/common/utils/IpUtils.java b/src/main/java/com/xiang/common/utils/IpUtils.java new file mode 100644 index 0000000..147ceb5 --- /dev/null +++ b/src/main/java/com/xiang/common/utils/IpUtils.java @@ -0,0 +1,21 @@ +package com.xiang.common.utils; + +import com.google.common.collect.Maps; + +import java.io.IOException; +import java.util.Map; + +/** + * @Author: xiang + * @Date: 2025-06-10 16:50 + */ +public class IpUtils { + + private final static String PUBLIC_IP_URL = "https://api-ipv4.ip.sb/ip"; + + public static String getPublicIp() throws IOException { + Map header = Maps.newHashMap(); + header.put("User-Agent", "Mozilla/5.0"); + return HttpService.doGet(PUBLIC_IP_URL, header, null).trim(); + } +} diff --git a/src/main/java/com/xiang/service/module/domain/schedule/DomainDynamicAnalysisTask.java b/src/main/java/com/xiang/service/module/domain/schedule/DomainDynamicAnalysisTask.java new file mode 100644 index 0000000..521e3d7 --- /dev/null +++ b/src/main/java/com/xiang/service/module/domain/schedule/DomainDynamicAnalysisTask.java @@ -0,0 +1,31 @@ +package com.xiang.service.module.domain.schedule; + +import com.xiang.common.utils.IpUtils; +import com.xiang.service.module.domain.service.IDomainService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.scheduling.annotation.Scheduled; + +@Slf4j +@RequiredArgsConstructor +public class DomainDynamicAnalysisTask { + private final IDomainService IDomainService; + + @Scheduled(cron = "0 0/30 * * * ? ") + public void dynamicDomainSchedule() { + String publicIp = ""; + try { + publicIp = IpUtils.getPublicIp(); + } catch (Exception e) { + log.error("获取公网ip失败,", e); + } + if (StringUtils.isNotBlank(publicIp)) { + try { + IDomainService.dynamicDomainAnalysis(publicIp); + } catch (Exception e) { + log.error("动态解析公网ip失败, ip:{}", publicIp, e); + } + } + } +} diff --git a/src/main/java/com/xiang/service/module/domain/server/DomainController.java b/src/main/java/com/xiang/service/module/domain/server/DomainController.java new file mode 100644 index 0000000..9619039 --- /dev/null +++ b/src/main/java/com/xiang/service/module/domain/server/DomainController.java @@ -0,0 +1,45 @@ +package com.xiang.service.module.domain.server; + +import com.xiang.common.utils.IpUtils; +import com.xiang.service.module.domain.service.IDomainService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@Slf4j +@RestController +@RequiredArgsConstructor +@RequestMapping("/script/domain") +public class DomainController { + + private final IDomainService domainService; + @GetMapping("/ddns") + public void dynamicDomainAnalysis() { + String publicIp = ""; + try { + publicIp = IpUtils.getPublicIp(); + } catch (Exception e) { + log.error("获取公网ip失败,", e); + } + if (StringUtils.isNotBlank(publicIp)) { + try { + domainService.dynamicDomainAnalysis(publicIp); + } catch (Exception e) { + log.error("动态解析公网ip失败, ip:{}", publicIp, e); + } + } + } + + @GetMapping("/ddns4ip") + public void dynamicDomainAnalysis(@RequestParam("ip") String ip, @RequestParam("rr") String rr) { + try { + domainService.dynamicDomainAnalysis(ip, rr); + } catch (Exception e) { + log.error("动态解析公网ip失败, ip:{}", ip, e); + } + } +} diff --git a/src/main/java/com/xiang/service/module/domain/service/IDomainService.java b/src/main/java/com/xiang/service/module/domain/service/IDomainService.java new file mode 100644 index 0000000..2d50c0d --- /dev/null +++ b/src/main/java/com/xiang/service/module/domain/service/IDomainService.java @@ -0,0 +1,15 @@ +package com.xiang.service.module.domain.service; + +/** + * @Author: xiang + * @Date: 2025-06-10 16:48 + */ +public interface IDomainService { + + /** + * 动态域名解析 + * @param publicIp 动态ip + */ + void dynamicDomainAnalysis(String publicIp) throws Exception; + void dynamicDomainAnalysis(String publicIp, String rr) throws Exception; +} diff --git a/src/main/java/com/xiang/service/module/domain/service/IDomainServiceImpl.java b/src/main/java/com/xiang/service/module/domain/service/IDomainServiceImpl.java new file mode 100644 index 0000000..b0fa16d --- /dev/null +++ b/src/main/java/com/xiang/service/module/domain/service/IDomainServiceImpl.java @@ -0,0 +1,111 @@ +package com.xiang.service.module.domain.service; + + +import com.aliyun.alidns20150109.Client; +import com.aliyun.alidns20150109.models.AddDomainRecordRequest; +import com.aliyun.alidns20150109.models.DescribeSubDomainRecordsRequest; +import com.aliyun.alidns20150109.models.DescribeSubDomainRecordsResponse; +import com.aliyun.alidns20150109.models.DescribeSubDomainRecordsResponseBody; +import com.aliyun.alidns20150109.models.UpdateDomainRecordRequest; +import com.aliyun.teaopenapi.models.Config; +import com.xiang.common.config.AliyunDnsPropertyConfig; +import com.xiang.common.factory.ScriptDingTalkFactory; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * @Author: xiang + * @Date: 2025-06-10 16:48 + */ +@Service +@Slf4j +@RequiredArgsConstructor +public class IDomainServiceImpl implements IDomainService { + + private static final String ACCESS_KEY_ID = "LTAI5tDMjaVF8Bbqcpp4dmvP"; + private static final String ACCESS_KEY_SECRET = "nkmnaNjWQy5984C5kjyS0oDmdMKGQd"; + /** + * 根域名 + */ + @Value("${aliyun.dns.rootDomain}") + private String rootDomain; + /** + * 主机记录,例如 home.example.com + */ + private final AliyunDnsPropertyConfig aliyunDnsPropertyConfig; + private static final String TYPE = "A"; + + private final ScriptDingTalkFactory dingTalkService; + @Override + public void dynamicDomainAnalysis(String publicIp) throws Exception { + Client client = createClient(); + + for (String rr : aliyunDnsPropertyConfig.getRR()) { + record(client, publicIp, rr); + } + } + + @Override + public void dynamicDomainAnalysis(String publicIp, String rr) throws Exception { + Client client = createClient(); + record(client, publicIp, rr); + } + + private void record(Client client, String publicIp, String rr) throws Exception{ + // 查询记录 + DescribeSubDomainRecordsRequest query = new DescribeSubDomainRecordsRequest() + .setSubDomain(rr + "." + rootDomain) + .setType(TYPE); + DescribeSubDomainRecordsResponse response = client.describeSubDomainRecords(query); + List records = + response.getBody().getDomainRecords().getRecord(); + + if (records.isEmpty()) { + log.info("未找到记录,添加记录..., ip:{}", publicIp); + addDnsRecord(client, publicIp, rr); + dingTalkService.sendMsg("动态解析公网ip成功,域名:" + rr + "." + rootDomain + ", 新ip:" + publicIp); + } else { + String recordId = records.get(0).getRecordId(); + String currentValue = records.get(0).getValue(); + if (!publicIp.equals(currentValue)) { + log.info("IP变更,更新记录...,ip:{}", publicIp); + updateDnsRecord(client, recordId, publicIp, rr); + dingTalkService.sendMsg("动态解析公网ip成功,域名:" + rr + "." + rootDomain + ", 新ip:" + publicIp); + } else { + log.info("ip未变更,无需修改,ip:{}", publicIp); + } + } + } + + private Client createClient() throws Exception { + Config config = new Config() + .setAccessKeyId(ACCESS_KEY_ID) + .setAccessKeySecret(ACCESS_KEY_SECRET) + .setEndpoint("alidns.cn-hangzhou.aliyuncs.com"); + return new Client(config); + } + + private void updateDnsRecord(Client client, String recordId, String newIp, String rr) throws Exception { + UpdateDomainRecordRequest request = new UpdateDomainRecordRequest() + .setRecordId(recordId) + .setRR(rr) + .setType(TYPE) + .setValue(newIp); + client.updateDomainRecord(request); + log.info("更新成功: ,newIP:{}", newIp); + } + + private void addDnsRecord(Client client, String ip, String rr) throws Exception { + AddDomainRecordRequest request = new AddDomainRecordRequest() + .setDomainName(rootDomain) + .setRR(rr) + .setType(TYPE) + .setValue(ip); + client.addDomainRecord(request); + log.info("添加成功: ip:{}", ip); + } +} diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml index 5453b02..dcc9a1b 100644 --- a/src/main/resources/application-local.yml +++ b/src/main/resources/application-local.yml @@ -15,11 +15,40 @@ spring: host: r-bp1wt59a6nfyt4e3ltpd.redis.rds.aliyuncs.com port: 6379 password: Xiang0000 - database: 11 + database: 10 timeout: 3000 lettuce: pool: max-active: 20 max-idle: 10 min-idle: 2 - max-wait: 3000 \ No newline at end of file + max-wait: 3000 + +aliyun: + dns: + rootDomain: xiangtech.xyz + RR: + - local + + +dingtalk: + robot: + properties: + venue: + name: 江南体育中心通知群 + token: 6a218646972c684c75832b0229ea93a234778af537d7469ce96bef290faf530e + secret: SEC9018755ba86d3e5c1ed2fbfa1d6953d84bb2a6c8ebe7ed4e318457bfed5e0465 + users: + - 450841600726084717 + script: + name: 脚本运行通知群 + token: 797be7f32062e31dec1d567f8b490a5649a5366083618e236c7a1263df1f4af3 + secret: SEC9aca642c0c29c9da261462869c464d34623247583d98fc82343a0a4464abbe91 + users: + - 450841600726084717 + xb: + name: 股票基金变化通知群 + token: ad21ead99f0fdc63aa00d6732b7b0888c17590f7612c68297edfcb71844d1437 + secret: SECc09d8aad6635f1a4cbadb7c0ab365523c46299f138438cd885e445e0f5f4d730 + users: + - 450841600726084717 \ No newline at end of file