找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
广告投放联系QQ68610888广告投放联系QQ68610888
广告投放联系QQ68610888广告投放联系QQ68610888
查看: 1156|回复: 4

吃灰玩客云3PRO 搞机第二季-手搓DDNS服务-JAVA21云原生编译初体验

[复制链接]
发表于 2024-10-19 10:06 | 显示全部楼层 |阅读模式
本帖最后由 598949112 于 2024-10-19 10:43 编辑

最近no-ip的域名每月经常忘记免费续期,已经彻底报废,而且现在很多网站都封杀no-ip的域名,让人苦不堪言,国内域名厂商全都要备案,我家里的服务器,上哪备案去。
于是去歪果域名注册商买了个域名,准备在这台玩客云3 PRO上挂个DDNS服务,检测外网IP自动更新解析。

JAVA21如果使用springboot3,是很容易进行native arrch64编译的,但是spring对于ARM来说还是太臃肿了,就算是个hello world,编译最快也需要半个多小时,就算只添加启动类的依赖,编译后的体积也有90多MB,运行时内存消耗也有70多MB,对于内存一般都不大的ARM设备来说,还是太奢侈了些,更别说直接使用JVM运行了,那内存消耗分分钟破几百兆,还需要依赖JDK环境。

DDNS服务运作流程:
1. 从小米路由器获取WAN口地址
2. 调用域名服务商提供的接口更新IP

在手搓DDNS服务前,我们需要先自行把springboot3的所有常用功能都实现(起码把用到的功能实现)

1.先把springboot所有常用注解都定义一遍,下面全部自己实现



2.实现核心的bean工厂
  1. public class BeanFactory {
  2.         private static Map<String, Object> beanMap = new HashMap<String, Object>();

  3.         public synchronized static <T> T createBean(Class<T> clazz, Set<Class<?>> services, Dict props) {
  4.                 try {
  5.                         Object obj = beanMap.get(clazz.getName());
  6.                         if (obj == null && clazz.isInterface()) {
  7.                                 for (Class<?> service : services) {
  8.                                         if (clazz.isAssignableFrom(service)) {
  9.                                                 obj = beanMap.get(service.getName());
  10.                                         }
  11.                                 }
  12.                         } else if (obj == null && !clazz.isInterface()) {
  13.                                 Class<?>[] infs = clazz.getInterfaces();
  14.                                 for (Class<?> inf : infs) {
  15.                                         if (inf.getSimpleName().startsWith("I")) {
  16.                                                 obj = beanMap.get(inf.getName());
  17.                                         }
  18.                                 }
  19.                         }
  20.                         if (obj == null) {
  21.                                 Field[] fields = clazz.getDeclaredFields();
  22.                                 if (clazz.isInterface()) {
  23.                                         for (Class<?> service : services) {
  24.                                                 if (clazz.isAssignableFrom(service)) {
  25.                                                         fields = service.getDeclaredFields();
  26.                                                 }
  27.                                         }
  28.                                         for (Class<?> service : services) {
  29.                                                 if (clazz.isAssignableFrom(service)) {
  30.                                                         T bean = clazz.cast(service.getDeclaredConstructor().newInstance());
  31.                                                         for (Field field : fields) {
  32.                                                                 Autowired auto = field.getAnnotation(Autowired.class);
  33.                                                                 if (auto != null) {
  34.                                                                         Class<?> fieldClass = field.getType();
  35.                                                                         field.setAccessible(true);
  36.                                                                         Object fieldObj = createBean(fieldClass, services, props);
  37.                                                                         field.set(bean, fieldObj);
  38.                                                                 }
  39.                                                                 Value value = field.getAnnotation(Value.class);
  40.                                                                 if (value != null) {
  41.                                                                         String key = value.value().substring(2, value.value().length() - 1);
  42.                                                                         Class<?> fieldClass = field.getType();
  43.                                                                         field.setAccessible(true);
  44.                                                                         field.set(bean, getProp(key, props, fieldClass));
  45.                                                                 }
  46.                                                         }
  47.                                                         Method[] methods = clazz.getDeclaredMethods();
  48.                                                         for (Method method : methods) {
  49.                                                                 Cacheable cache = method.getAnnotation(Cacheable.class);
  50.                                                                 if (cache != null) {
  51.                                                                         bean = ProxyUtil.proxy(bean, CacheAspect.class);
  52.                                                                 }
  53.                                                         }
  54.                                                         beanMap.put(clazz.getName(), bean);
  55.                                                         beanMap.put(service.getName(), bean);
  56.                                                         return bean;
  57.                                                 }
  58.                                         }
  59.                                         return null;
  60.                                 } else {
  61.                                         T bean = clazz.getDeclaredConstructor().newInstance();
  62.                                         for (Field field : fields) {
  63.                                                 Autowired auto = field.getAnnotation(Autowired.class);
  64.                                                 if (auto != null) {
  65.                                                         Class<?> fieldClass = field.getType();
  66.                                                         field.setAccessible(true);
  67.                                                         Object fieldObj = createBean(fieldClass, services, props);
  68.                                                         field.set(bean, fieldObj);
  69.                                                 }
  70.                                                 Value value = field.getAnnotation(Value.class);
  71.                                                 if (value != null) {
  72.                                                         String key = value.value().substring(2, value.value().length() - 1);
  73.                                                         Class<?> fieldClass = field.getType();
  74.                                                         field.setAccessible(true);
  75.                                                         field.set(bean, getProp(key, props, fieldClass));
  76.                                                 }
  77.                                         }
  78.                                         Class<?>[] infs = clazz.getInterfaces();
  79.                                         for (Class<?> inf : infs) {
  80.                                                 if (inf.getSimpleName().startsWith("I")) {
  81.                                                         Method[] methods = inf.getDeclaredMethods();
  82.                                                         for (Method method : methods) {
  83.                                                                 Cacheable cache = method.getAnnotation(Cacheable.class);
  84.                                                                 if (cache != null) {
  85.                                                                         bean = ProxyUtil.proxy(bean, CacheAspect.class);
  86.                                                                 }
  87.                                                         }
  88.                                                         beanMap.put(inf.getName(), bean);
  89.                                                 }
  90.                                         }
  91.                                         beanMap.put(clazz.getName(), bean);
  92.                                         return bean;
  93.                                 }
  94.                         } else {
  95.                                 return clazz.cast(obj);
  96.                         }
  97.                 } catch (Exception e) {
  98.                         e.printStackTrace();
  99.                         return null;
  100.                 }
  101.         }

  102.         @SuppressWarnings("unchecked")
  103.         public synchronized static <T> T getBean(Class<T> clazz) {
  104.                 Object obj = beanMap.get(clazz.getName());
  105.                 if (obj == null) {
  106.                         return null;
  107.                 }
  108.                 return (T) obj;
  109.         }

  110.         public synchronized static void setBean(String key, Object val) {
  111.                 beanMap.put(key, val);
  112.         }

  113.         public synchronized static void printBean() {
  114.                 beanMap.forEach((k, v) -> {
  115.                         System.out.println(k + ":" + v);
  116.                 });
  117.         }

  118.         @SuppressWarnings("unchecked")
  119.         public static <T> T getProp(String key, Dict dict, Class<T> clazz) {
  120.                 String[] params = key.split("\\.");
  121.                 LinkedHashMap<String, Object> last = (LinkedHashMap<String, Object>) dict.get(params[0]);
  122.                 for (int i = 1; i < params.length - 1; i++) {
  123.                         if (last != null)
  124.                                 last = (LinkedHashMap<String, Object>) last.get(params[i]);
  125.                 }
  126.                 if (last == null)
  127.                         return null;
  128.                 key = params[params.length - 1];
  129.                 Dict props = new Dict(last);
  130.                 if (clazz == String.class)
  131.                         return clazz.cast(props.getStr(key));
  132.                 else if (clazz == Integer.class || clazz == int.class)
  133.                         return (T) props.getInt(key);
  134.                 else if (clazz == Long.class || clazz == long.class)
  135.                         return (T) props.getLong(key);
  136.                 else if (clazz == Double.class || clazz == double.class)
  137.                         return (T) props.getDouble(key);
  138.                 else if (clazz == Boolean.class || clazz == boolean.class)
  139.                         return (T) props.getBool(key);
  140.                 else if (clazz == Float.class || clazz == float.class)
  141.                         return (T) props.getFloat(key);
  142.                 else if (clazz == Short.class || clazz == short.class)
  143.                         return (T) props.getShort(key);
  144.                 else if (clazz == Byte.class || clazz == byte.class)
  145.                         return (T) props.getByte(key);
  146.                 else if (clazz == BigDecimal.class)
  147.                         return clazz.cast(props.getBigDecimal(key));
  148.                 else if (clazz == Date.class)
  149.                         return clazz.cast(props.getDate(key));
  150.                 else if (clazz == BigInteger.class)
  151.                         return clazz.cast(props.getBigInteger(key));
  152.                 else
  153.                         return null;
  154.         }

  155. }
复制代码


3.实现springboot启动器,用于加载yaml配置、初始化配置bean、实例化service、controller、定时任务、@Autowired 和 @Value 的依赖和配置注入等
  1. public class SpringApplication {

  2.         public static void launch(String[] args) {
  3.                 System.out.println("DDNS服务已启动...");
  4.         }

  5.         public static void run(Class<?> clazz, String[] args) {
  6.                 TimeZone.setDefault(TimeZone.getTimeZone("Asia/Shanghai"));
  7.                 Dict props = new Dict();
  8.                 InputStream input = ResourceUtil.getStream("application.yaml");
  9.                 if (input != null) {
  10.                         try {
  11.                                 props = YamlUtil.load(new InputStreamReader(input));
  12.                         } catch (Exception e) {
  13.                                 e.printStackTrace();
  14.                         }
  15.                 }
  16.                 if (args.length > 0) {
  17.                         try {
  18.                                 props = YamlUtil.load(new FileReader(args[0]));
  19.                         } catch (Exception e) {
  20.                                 e.printStackTrace();
  21.                         }
  22.                 }
  23.                 List<?> classList = (List<?>) props.get("scans");

  24.                 SpringBootApplication sba = clazz.getAnnotation(SpringBootApplication.class);
  25.                 Set<Object> controllers = new HashSet<Object>();
  26.                 String[] packages = sba.scanBasePackages();
  27.                 for (String pack : packages) {
  28.                         Set<Class<?>> confs = ClassNativeUtil.scanPackageByAnnotation(classList, pack, Configuration.class);
  29.                         Set<Class<?>> comps = ClassNativeUtil.scanPackageByAnnotation(classList, pack, Component.class);
  30.                         Set<Class<?>> subControllers = ClassNativeUtil.scanPackageByAnnotation(classList, pack,
  31.                                         RestController.class);
  32.                         Set<Class<?>> services = ClassNativeUtil.scanPackageByAnnotation(classList, pack, Service.class);
  33.                         for (Class<?> conf : confs) {
  34.                                 Object obj = createObj(conf, services, props);
  35.                                 Method[] methods = conf.getDeclaredMethods();
  36.                                 for (Method method : methods) {
  37.                                         Bean bean = method.getAnnotation(Bean.class);
  38.                                         if (bean != null) {
  39.                                                 try {
  40.                                                         Object res = method.invoke(obj);
  41.                                                         BeanFactory.setBean(method.getReturnType().getName(), res);
  42.                                                 } catch (Exception e) {
  43.                                                         e.printStackTrace();
  44.                                                 }
  45.                                         }
  46.                                 }
  47.                         }
  48.                         for (Class<?> comp : comps) {
  49.                                 createObj(comp, services, props);
  50.                         }
  51.                         for (Class<?> service : services) {
  52.                                 createObj(service, services, props);
  53.                                 EnableScheduling sch = service.getAnnotation(EnableScheduling.class);
  54.                                 if (sch != null) {
  55.                                         Object task = BeanFactory.getBean(service);
  56.                                         Method[] taskMethods = service.getDeclaredMethods();
  57.                                         for (Method taskMethod : taskMethods) {
  58.                                                 Scheduled scheduled = taskMethod.getAnnotation(Scheduled.class);
  59.                                                 if (scheduled != null) {
  60.                                                         taskMethod.setAccessible(true);
  61.                                                         CronUtil.schedule(scheduled.cron(), new Task() {
  62.                                                                 @Override
  63.                                                                 public void execute() {
  64.                                                                         try {
  65.                                                                                 taskMethod.invoke(task);
  66.                                                                         } catch (Exception e) {
  67.                                                                                 e.printStackTrace();
  68.                                                                         }
  69.                                                                 }
  70.                                                         });
  71.                                                 }
  72.                                         }
  73.                                 }
  74.                         }
  75.                         for (Class<?> controller : subControllers) {
  76.                                 Object controllerObject = createObj(controller, services, props);
  77.                                 controllers.add(controllerObject);
  78.                         }
  79.                 }
  80.                 Integer port = BeanFactory.getProp("server.port", props, Integer.class);
  81.                 if (port != null) {
  82.                         SimpleServer server = HttpUtil.createServer(port);
  83.                         for (Object controller : controllers) {
  84.                                 Method[] methods = controller.getClass().getDeclaredMethods();
  85.                                 for (Method method : methods) {
  86.                                         method.setAccessible(true);
  87.                                         StringBuffer reqMethod = new StringBuffer();
  88.                                         StringBuffer path = new StringBuffer();
  89.                                         GetMapping getMapping = method.getAnnotation(GetMapping.class);
  90.                                         PostMapping postMapping = method.getAnnotation(PostMapping.class);
  91.                                         if (getMapping != null) {
  92.                                                 reqMethod.append("GET");
  93.                                                 path.append(getMapping.value());
  94.                                         }
  95.                                         if (postMapping != null) {
  96.                                                 reqMethod.append("POST");
  97.                                                 path.append(postMapping.value());
  98.                                         }
  99.                                         if (!reqMethod.toString().equals(StrUtil.EMPTY)) {
  100.                                                 server.addAction(path.toString(), (req, res) -> {
  101.                                                         try {
  102.                                                                 if (!reqMethod.toString().equals(req.getMethod())) {
  103.                                                                         res.write("{"code":2,"msg":"不支持此请求方法"}", ContentType.JSON.toString());
  104.                                                                         return;
  105.                                                                 }
  106.                                                                 Parameter[] params = method.getParameters();
  107.                                                                 Object[] paramObject = new Object[params.length];
  108.                                                                 for (int i = 0; i < params.length; i++) {
  109.                                                                         Parameter param = params[i];
  110.                                                                         RequestParam qp = param.getAnnotation(RequestParam.class);
  111.                                                                         RequestBody rb = param.getAnnotation(RequestBody.class);
  112.                                                                         if (qp != null) {
  113.                                                                                 String name = param.getName();
  114.                                                                                 paramObject[i] = req.getParam(name);
  115.                                                                         } else if (reqMethod.toString().equals("POST") && rb != null) {
  116.                                                                                 Object obj = JSONUtil.toBean(req.getBody(), param.getType());
  117.                                                                                 paramObject[i] = obj;
  118.                                                                         }
  119.                                                                 }
  120.                                                                 Object data = method.invoke(controller, paramObject);
  121.                                                                 res.write(JSONUtil.toJsonStr(data), ContentType.JSON.toString());
  122.                                                         } catch (Exception e) {
  123.                                                                 e.printStackTrace();
  124.                                                                 res.write("{"code":1,"msg":"系统出错"}", ContentType.JSON.toString());
  125.                                                         }
  126.                                                 });
  127.                                         }
  128.                                 }
  129.                         }
  130.                         server.start();
  131.                 }
  132.                 CronUtil.setMatchSecond(true);
  133.                 CronUtil.start();
  134.                 launch(args);
  135.         }

  136.         public static <T> T createObj(Class<T> clazz, Set<Class<?>> services, Dict props) {
  137.                 T obj = BeanFactory.getBean(clazz);
  138.                 try {
  139.                         if (obj != null) {
  140.                                 return (T) obj;
  141.                         }
  142.                         obj = BeanFactory.createBean(clazz, services, props);
  143.                 } catch (Exception e) {
  144.                         e.printStackTrace();
  145.                         return null;
  146.                 }
  147.                 return obj;
  148.         }

  149. }
复制代码


4.实现@Cacheable缓存注解


i)创建代理工具
  1. package com.elite.ddns.util;

  2. import com.elite.ddns.aop.JDKAspect;
  3. import com.elite.ddns.factory.JDKProxyFactory;
  4. import cn.hutool.core.util.ReflectUtil;

  5. public class ProxyUtil {
  6.         public static <T> T proxy(T target, Class<? extends JDKAspect> aspectClass) {
  7.                 return new JDKProxyFactory().proxy(target, ReflectUtil.newInstance(aspectClass));
  8.         }
  9. }
复制代码
ii)对切面接口进行增强,hutool工具默认不支持环绕切面,需要增加环绕方法
  1. public interface JDKAspect {
  2.         /**
  3.          * 目标方法执行前的操作
  4.          *
  5.          * @param target 目标对象
  6.          * @param method 目标方法
  7.          * @param args   参数
  8.          * [url=home.php?mod=space&uid=439838]@return[/url] 是否继续执行接下来的操作
  9.          */
  10.         boolean before(Object target, Method method, Object[] args);

  11.         /**
  12.          * 目标方法执行后的操作<br>
  13.          * 如果 target.method 抛出异常且 {[url=home.php?mod=space&uid=50640]@link[/url] Aspect#afterException} 返回true,则不会执行此操作<br>
  14.          * 如果 {@link Aspect#afterException} 返回false,则无论target.method是否抛出异常,均会执行此操作<br>
  15.          *
  16.          * @param target    目标对象
  17.          * @param method    目标方法
  18.          * @param args      参数
  19.          * @param returnVal 目标方法执行返回值
  20.          * @return 是否允许返回值(接下来的操作)
  21.          */
  22.         boolean after(Object target, Method method, Object[] args, Object returnVal);

  23.         /**
  24.          * 目标方法抛出异常时的操作
  25.          *
  26.          * @param target 目标对象
  27.          * @param method 目标方法
  28.          * @param args   参数
  29.          * @param e      异常
  30.          * @return 是否允许抛出异常
  31.          */
  32.         boolean afterException(Object target, Method method, Object[] args, Throwable e);

  33.         Object around(Object target, Method method, Object[] args);
复制代码
iii)实现JDK的动态代理
  1. public class JDKInterceptor implements InvocationHandler, Serializable {
  2.         private static final long serialVersionUID = 1L;

  3.         private final Object target;
  4.         private final JDKAspect aspect;

  5.         /**
  6.          * 构造
  7.          *
  8.          * @param target 被代理对象
  9.          * @param aspect 切面实现
  10.          */
  11.         public JDKInterceptor(Object target, JDKAspect aspect) {
  12.                 this.target = target;
  13.                 this.aspect = aspect;
  14.         }

  15.         public Object getTarget() {
  16.                 return this.target;
  17.         }

  18.         @Override
  19.         public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  20.                 final Object target = this.target;
  21.                 final JDKAspect aspect = this.aspect;
  22.                 Object result = null;

  23.                 // 开始前回调
  24.                 if (aspect.before(target, method, args)) {
  25.                         // 环绕切面
  26.                         result = aspect.around(target, method, args);
  27.                         if (result == null) {
  28.                                 ReflectUtil.setAccessible(method);
  29.                                 try {
  30.                                         result = method.invoke(ClassUtil.isStatic(method) ? null : target, args);
  31.                                 } catch (InvocationTargetException e) {
  32.                                         // 异常回调(只捕获业务代码导致的异常,而非反射导致的异常)
  33.                                         if (aspect.afterException(target, method, args, e.getTargetException())) {
  34.                                                 throw e;
  35.                                         }
  36.                                 }
  37.                         }
  38.                         // 结束执行回调
  39.                         if (aspect.after(target, method, args, result)) {
  40.                                 return result;
  41.                         }
  42.                 }
  43.                 return null;
  44.         }

  45. }
复制代码
iv)实现缓存切面
  1. public class CacheAspect implements JDKAspect {

  2.         private static Cache<String, Object> fifoCache = CacheUtil.newLRUCache(10);

  3.         @Override
  4.         public boolean before(Object target, Method method, Object[] args) {
  5.                 CacheEvict cacheEvict = method.getAnnotation(CacheEvict.class);
  6.                 if (cacheEvict != null) {
  7.                         String key = target.getClass().getName() + "." + cacheEvict.value();
  8.                         synchronized (fifoCache) {
  9.                                 fifoCache.remove(key);
  10.                         }
  11.                 }
  12.                 return true;
  13.         }

  14.         @Override
  15.         public boolean after(Object target, Method method, Object[] args, Object returnVal) {
  16.                 Cacheable cache = method.getAnnotation(Cacheable.class);
  17.                 if (cache != null) {
  18.                         String key = target.getClass().getName() + "." + cache.value();
  19.                         synchronized (fifoCache) {
  20.                                 if (fifoCache.get(key, false) == null)
  21.                                         fifoCache.put(key, returnVal, DateUnit.SECOND.getMillis() * cache.ttl());
  22.                         }
  23.                 }
  24.                 return true;
  25.         }

  26.         @Override
  27.         public boolean afterException(Object target, Method method, Object[] args, Throwable e) {
  28.                 return true;
  29.         }

  30.         @Override
  31.         public Object around(Object target, Method method, Object[] args) {
  32.                 Cacheable cache = method.getAnnotation(Cacheable.class);
  33.                 if (cache != null) {
  34.                         String key = target.getClass().getName() + "." + cache.value();
  35.                         synchronized (fifoCache) {
  36.                                 return fifoCache.get(key, false);
  37.                         }
  38.                 }
  39.                 return null;
  40.         }
  41. }
复制代码

4.现在,就可以正常使用所有自定义的springboot注解进行开发,使用方法和原框架完全一致,只是自行对功能进行简单实现,可以成称为精简版springboot


5.将项目打包成jar,效果很好,只有2.3MB,因为移除了所有springboot的依赖,项目体积极小



6.使用agentlib工具进行反射分析
java -agentlib:native-image-agent=config-output-dir=./native -jar D:\\ddns.native.jar

7.将生成的native目录下所有文件复制到这个目录,就可以开始原生编译: mvn -Pnative native:compile -DskipNativeTests


8.编译需要5分钟,比springboot3速度快了30分钟,体积是36MB,虽然体积还是不小,但是对于臃肿的JAVA来说,已经很强了!


9.迫不及待添加系统脚本,让系统运行时自动启动


10.不错运行一切正常



11.查看内存占用,50MB,不错不错,虽然还是有点多,但比springboot编译少了一大半,相当于JVM启动的零头!


11.源码:借助hutool工具实现了springboot3的所有常用功能,依赖包只有2MB,适合ARM的云原生编译,完美!



激动的心,颤抖的手,我基于这套框架继续开发其他应用来压榨这台玩客云3 PRO去了!



PS:非保姆教程,需要对JAVA开发有一定经验者才行


本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
只谈技术、莫论政事!(点击见详情) | 互相尊重、友善交流、切勿过度反应、玻璃心。胡乱输入灌水等操作将会被封禁ID。
 楼主| 发表于 2024-10-19 10:08 | 显示全部楼层
额,我保存了个草稿之后,怎么不能继续编辑了,也不能删除这个帖子了.....
只谈技术、莫论政事!(点击见详情) | 互相尊重、友善交流、切勿过度反应、玻璃心。胡乱输入灌水等操作将会被封禁ID。
回复

使用道具 举报

发表于 2024-10-19 10:12 | 显示全部楼层
现在这机器能刷了吗
只谈技术、莫论政事!(点击见详情) | 互相尊重、友善交流、切勿过度反应、玻璃心。胡乱输入灌水等操作将会被封禁ID。
回复

使用道具 举报

发表于 2024-11-23 10:43 | 显示全部楼层
最好加入访问ip白名单,运营商现在查ddns。
只谈技术、莫论政事!(点击见详情) | 互相尊重、友善交流、切勿过度反应、玻璃心。胡乱输入灌水等操作将会被封禁ID。
回复

使用道具 举报

发表于 2024-11-24 07:32 | 显示全部楼层
感谢分享
只谈技术、莫论政事!(点击见详情) | 互相尊重、友善交流、切勿过度反应、玻璃心。胡乱输入灌水等操作将会被封禁ID。
回复

使用道具 举报

*滑块验证:
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关闭

欢迎大家光临恩山无线论坛上一条 /2 下一条

有疑问请添加管理员QQ86788181|手机版|小黑屋|Archiver|恩山无线论坛(常州市恩山计算机开发有限公司版权所有) ( 苏ICP备05084872号 )

GMT+8, 2026-8-23 16:52

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

| 江苏省互联网有害信息举报中心 举报信箱:js12377 | @jischina.com.cn 举报电话:025-88802724 本站不良内容举报信箱:68610888@qq.com 电话:13506125623

快速回复 返回顶部 返回列表