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