前言
最近在家里搭建了一个家庭服务器,但是家庭宽带无法申请固定IP,在外网以及公司无法远程或访问该服务器(观察IP是隔1天动态变化一次),但是我的域名又是阿里云的,目前路由器的DDNS功能好像没有支持阿里云的。
好在阿里云提供了DNS相关的SDK,这里使用SpringBoot+AliYunSDK实现动态刷新DNS解析记录,从而实现通过域名,随时随地访问家里的电脑的功能
一、DDNS是什么?
DDNS(Dynamic Domain Name Server,动态域名服务)是将用户的动态IP地址映射到一个固定的域名解析服务上,用户每次连接网络的时候客户端程序就会通过信息传递把该主机的动态IP地址传送给位于服务商主机上的服务器程序,服务器程序负责提供DNS服务并实现动态域名解析。
二、实现步骤
1. 阿里云添加解析记录
打开阿里云控制台>云解析DNS>选中使用的域名>解析设置>添加一条解析记录(示例):
2.创建AccessKey
打开AccessKey管理界面,创建自己的AccessKey并保存AccessKeyId和AccessKeySecretId
3. 创建SpringBoot项目并引入依赖
Maven依赖:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.78</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>tea-openapi</artifactId>
<version>0.0.19</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>alidns20150109</artifactId>
<version>2.0.1</version>
</dependency>
</dependencies>
4. 相关功能开发
-
封装获取当前公网IP工具类
WebToolUtils
package org.feasy.www.util; import org.springframework.util.StringUtils; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.InetAddress; import java.net.NetworkInterface; import java.net.URL; import java.util.Enumeration; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * @author yangxiaohui * @since 2022/4/15 10:35 */ public class WebToolUtils { // 方法1 private static String getNowIP1() throws IOException { String ip = null; String chinaz = "http://ip.chinaz.com"; StringBuilder inputLine = new StringBuilder(); String read = ""; URL url = null; HttpURLConnection urlConnection = null; BufferedReader in = null; try { url = new URL(chinaz); urlConnection = (HttpURLConnection) url.openConnection(); in = new BufferedReader(new InputStreamReader(urlConnection.getInputStream(), "UTF-8")); while ((read = in.readLine()) != null) { inputLine.append(read + "\r\n"); } Pattern p = Pattern.compile("\\<dd class\\=\"fz24\">(.*?)\\<\\/dd>"); Matcher m = p.matcher(inputLine.toString()); if (m.find()) { String ipstr = m.group(1); ip = ipstr; } } finally { if (in != null) { in.close(); } } if (StringUtils.isEmpty(ip)) { throw new RuntimeException(); } return ip; } // 方法2 private static String getNowIP2() throws IOException { String ip = null; BufferedReader br = null; try { URL url = new URL("https://v6r.ipip.net/?format=callback"); br = new BufferedReader(new InputStreamReader(url.openStream())); String s = ""; StringBuffer sb = new StringBuffer(""); String webContent = ""; while ((s = br.readLine()) != null) { sb.append(s + "\r\n"); } webContent = sb.toString(); int start = webContent.indexOf("(") + 2; int end = webContent.indexOf(")") - 1; webContent = webContent.substring(start, end); ip = webContent; } finally { if (br != null) br.close(); } if (StringUtils.isEmpty(ip)) { throw new RuntimeException(); } return ip; } // 方法3 private static String getNowIP3() throws IOException { String ip = null; String objWebURL = "https://ip.900cha.com/"; BufferedReader br = null; try { URL url = new URL(objWebURL); br = new BufferedReader(new InputStreamReader(url.openStream())); String s = ""; String webContent = ""; while ((s = br.readLine()) != null) { if (s.indexOf("我的IP:") != -1) { ip = s.substring(s.indexOf(":") + 1); break; } } } finally { if (br != null) br.close(); } if (StringUtils.isEmpty(ip)) { throw new RuntimeException(); } return ip; } // 方法4 private static String getNowIP4() throws IOException { String ip = null; String objWebURL = "https://bajiu.cn/ip/"; BufferedReader br = null; try { URL url = new URL(objWebURL); br = new BufferedReader(new InputStreamReader(url.openStream())); String s = ""; String webContent = ""; while ((s = br.readLine()) != null) { if (s.indexOf("互联网IP") != -1) { ip = s.substring(s.indexOf("'") + 1, s.lastIndexOf("'")); break; } } } finally { if (br != null) br.close(); } if (StringUtils.isEmpty(ip)) { throw new RuntimeException(); } return ip; } public static String getPublicIP() { String ip = null; // 第一种方式 try { ip = getNowIP1(); ip.trim(); } catch (Exception e) { System.out.println("getPublicIP - getNowIP1 failed ~ "); } if (!StringUtils.isEmpty(ip)) return ip; // 第二种方式 try { ip = getNowIP2(); ip.trim(); } catch (Exception e) { System.out.println("getPublicIP - getNowIP2 failed ~ "); } if (!StringUtils.isEmpty(ip)) return ip; // 第三种方式 try { ip = getNowIP3(); ip.trim(); } catch (Exception e) { System.out.println("getPublicIP - getNowIP3 failed ~ "); } if (!StringUtils.isEmpty(ip)) return ip; // 第四种方式 try { ip = getNowIP4(); ip.trim(); } catch (Exception e) { System.out.println("getPublicIP - getNowIP4 failed ~ "); } if (!StringUtils.isEmpty(ip)) return ip; return ip; } }
-
封装阿里云DNS-SDK操作,刷新逻辑
DdnsClient
package org.feasy.www.ddns; import com.alibaba.fastjson.JSON; import com.aliyun.alidns20150109.Client; import com.aliyun.alidns20150109.models.*; import com.aliyun.teaopenapi.models.Config; import org.feasy.www.util.WebToolUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import java.util.List; /** * @author yangxiaohui * @since 2022/4/15 10:10 */ @Component public class DdnsClient { private final Logger logger= LoggerFactory.getLogger(this.getClass()); @Value("${ali.domain}") private String domain; @Value("${ali.keyword}") private String keyWord; @Value("${ali.access-key}") private String accessKey; @Value("${ali.access-key-secret}") private String accessSecret; @Value("${ali.endpoint}") private String endpoint; private String currentDdnsIp=""; private final Client client; public DdnsClient() throws Exception { this.client = new Client(new Config() // 您的AccessKey ID .setAccessKeyId(accessKey) // 您的AccessKey Secret .setAccessKeySecret(accessSecret) .setEndpoint(endpoint) ); this.refreshDdns(); } public void refreshDdns() throws Exception { String currentIp = this.getCurrentIp(); if (!this.currentDdnsIp.equals(currentIp)){ // 获取解析记录 DescribeDomainRecordsResponseBody.DescribeDomainRecordsResponseBodyDomainRecordsRecord domainDnsRecord = this.getDomainDnsRecord(); // 刷新解析记录 this.updateDomainDnsRecord(domainDnsRecord,currentIp); } logger.info("无需修改解析记录,当前IP:{}",currentIp); } private String getCurrentIp(){ return WebToolUtils.getPublicIP(); } private DescribeDomainRecordsResponseBody.DescribeDomainRecordsResponseBodyDomainRecordsRecord getDomainDnsRecord() throws Exception { DescribeDomainRecordsRequest describeDomainRecordsRequest = new DescribeDomainRecordsRequest() .setDomainName(domain) .setKeyWord(keyWord); // 获取记录详情 DescribeDomainRecordsResponse domainRecordsResponse = client.describeDomainRecords(describeDomainRecordsRequest); List<DescribeDomainRecordsResponseBody.DescribeDomainRecordsResponseBodyDomainRecordsRecord> record = domainRecordsResponse.getBody().getDomainRecords().getRecord(); DescribeDomainRecordsResponseBody.DescribeDomainRecordsResponseBodyDomainRecordsRecord describeDomainRecordsResponseBodyDomainRecordsRecord = record.get(0); logger.info("获取解析记录:{}", JSON.toJSONString(describeDomainRecordsResponseBodyDomainRecordsRecord)); return describeDomainRecordsResponseBodyDomainRecordsRecord; } private void updateDomainDnsRecord(DescribeDomainRecordsResponseBody.DescribeDomainRecordsResponseBodyDomainRecordsRecord domainDnsRecord,String newIp) throws Exception { // 修改记录 UpdateDomainRecordRequest updateDomainRecordRequest = new UpdateDomainRecordRequest() .setRecordId(domainDnsRecord.getRecordId()) .setRR(domainDnsRecord.getRR()) .setType(domainDnsRecord.getType()) .setValue(newIp); // 修改记录 UpdateDomainRecordResponse updateDomainRecordResponse = client.updateDomainRecord(updateDomainRecordRequest); logger.info("修改解析结果:{}",JSON.toJSONString(updateDomainRecordResponse)); } }
-
设置定时任务
DdnsTask
package org.feasy.www.ddns; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; /** * @author yangxiaohui * @since 2022/4/15 10:28 */ @Component @EnableScheduling public class DdnsTask { private final Logger logger= LoggerFactory.getLogger(this.getClass()); private final DdnsClient ddnsClient; public DdnsTask(DdnsClient ddnsClient) { this.ddnsClient = ddnsClient; } /** * 刷新阿里巴巴DNS解析记录 */ @Scheduled(fixedRate=300000) public void refreshDdns() throws Exception { ddnsClient.refreshDdns(); } }
-
项目配置
application.properties
server.port=8089 ali.domain=域名 ali.keyword=解析主机记录 ali.endpoint=dns.aliyuncs.com ali.access-key=AccessKeyId ali.access-key-secret=AccessKeySecretId
总结
当前设置是启动项目会执行一次刷新DNS记录,执行频率默认5分钟执行一次,调整频率的话可以调整@Scheduled(fixedRate=300000),单位是毫秒
源码地址:https://gitee.com/yxh-china/alibaba-ddns.git