恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

Java SDK源码保护实战:基于自定义类加载器的JAR加密与集成方案

  • 首页
  • 资讯中心
  • /
  • Java SDK源码保护实战:基于自定义类加载器的JAR加密与集成方案

相关资讯

Python依赖管理与项目打包:Poetry工具实战指南 2026/8/7 5:17:56
Cocos Creator实战:从零开发《汉字找茬》小游戏 2026/8/7 5:17:56
Unity资源管理深度解析:Addressables与YooAsset资源卸载机制对比 2026/8/7 5:17:56

最新资讯

AI科研绘图平台测评与工具对比
Unity去马赛克算法框架:从拜耳阵列到实时图像处理的二次开发指南
TeePor:让AI编程助手深度感知开发环境,告别重复沟通
从零构建炫彩3D展示:Blender着色器与PBR材质实战指南
Godot C#实现物体环绕旋转:从基础数学到物理模拟
AI编程技能设计指南:构建通用提示词模板提升开发效率

今日推荐

CAD图库管理:从文件归档到设计资产管理的效率革命
5分钟掌握Wand-Enhancer:2026年终极WeMod专业版免费解锁指南
“Quality Control(质量控制)”在软件工程中通常指通过一系列活动确保软件产品符合预定的质量标准和用户需求

本周热门

ncmdumpGUI:一键解锁网易云音乐ncm文件的终极解决方案
分布式配置中心选型实战:Nacos与Consul在创业场景下的对比
MoneyPrinterPlus实战指南:AI视频批量生成与自动化发布完整解决方案

本月精选

如何用DamaiHelper实现演唱会门票的智能自动化抢购:完整技术解决方案指南
第4篇:59 倍性能差距的索引瓶颈定位——一次教科书级的全表扫描调优
终极歌词批量下载神器:5分钟解决离线音乐库歌词同步难题

Java SDK源码保护实战:基于自定义类加载器的JAR加密与集成方案

发布时间:2026/8/7 5:17:56
Java SDK源码保护实战:基于自定义类加载器的JAR加密与集成方案 1. 项目概述从源码保护到商业交付的最后一公里在软件开发的商业实践中我们常常面临一个两难的局面一方面我们希望将核心功能封装成SDK软件开发工具包方便合作伙伴或客户集成从而拓展产品的生态和影响力另一方面我们又必须保护自己的知识产权防止核心逻辑被轻易反编译、分析和复用。尤其是在Java生态中将项目打包成JAR文件进行分发是标准操作但一个普通的JAR文件在反编译工具面前几乎“透明”。这时“JAR加密后当作SDK使用”就成了连接代码保护与便捷分发的关键桥梁。这个项目的核心目标就是解决这个矛盾。它不仅仅是简单地对JAR包进行混淆或加密而是一套完整的、面向生产的解决方案。我们需要产出一个经过加密处理的JAR文件这个文件不仅能像普通SDK一样通过Maven仓库被引入到用户的Spring Boot或其他Java项目中还能保证其内部的类和方法在被JVM加载和执行时依然是加密状态从而有效抵御静态反编译。同时整个流程必须对SDK的使用者透明他们无需关心解密过程像使用普通依赖一样添加坐标、调用API即可。这涉及到自定义类加载器、字节码操作、Maven插件打包以及发布到私有或公共仓库等一系列技术点的串联。我自己在多次商业SDK交付中实践并优化了这套方案它有效地在便利性与安全性之间找到了平衡点。2. 整体方案设计与核心思路拆解2.1 为什么选择运行时解密而非单纯混淆在保护Java代码时常见的有两种思路代码混淆和字节码加密。代码混淆如ProGuard通过重命名类、方法、变量名为无意义的字符并移除调试信息增加反编译后的阅读难度。但它本质上没有改变字节码对于有经验的开发者结合上下文依然可能理清逻辑。字节码加密则更进一步它将原始的.class文件内容进行加密转换生成一个新的、无法被JVM直接识别的文件。只有在运行时通过我们预设的解密逻辑才能将字节码还原并加载。对于需要作为SDK分发的场景单纯混淆的强度往往不够。因为SDK的接口API必须是清晰、可读的否则使用者无法调用这就使得混淆不能应用于公开的API类和方法。攻击者可以轻易地从清晰的接口切入分析其调用的内部混淆类从而破解核心逻辑。而加密方案可以对所有实现类包括非公开API的内部类进行无差别保护只在JVM加载类的瞬间进行解密对使用者完全透明。因此“混淆接口加密实现”或“全量加密自定义加载”成为了更优的选择。本方案采用的就是后者发布到Maven仓库的JAR包中所有核心.class文件都是加密后的密文。当使用者的应用启动尝试加载我们SDK中的某个类时一个我们内置的自定义类加载器会拦截该请求从JAR中读取加密的字节码在内存中解密然后动态定义并返回给JVM。这样磁盘上和打包后的JAR里始终是密文只有运行时的内存中才存在明文字节码。2.2 技术栈选型与架构总览要实现上述目标我们需要一个环环相扣的技术组合加密/解密算法选择对称加密算法如AES。因为加解密使用同一密钥且需要在SDK内部集成解密逻辑算法需兼顾安全性和性能。AES-256在强度上足够且JRE内置支持无需额外依赖。字节码操作库用于在加密后处理Class文件可能需要在文件头添加标识或进行简单的格式包装。通常使用ASM或Javassist这里我们选择更轻量级的ASM因为它不依赖其他库且性能极高。自定义类加载器这是整个方案的核心。我们需要继承java.lang.ClassLoader重写findClass方法。在这个方法里根据类名找到对应的加密class文件密文调用解密算法得到原始字节码最后通过defineClass方法将其转换为JVM可用的Class对象。Maven插件与打包流程我们需要定制Maven的打包过程。在package阶段之后使用Maven插件如maven-antrun-plugin或自定义Mojo遍历生成的JAR包对其中的.class文件进行加密并替换原文件。同时需要将我们写好的自定义类加载器也是一个.class文件一同打包进去但这个加载器本身不能加密因为它是解密的起点。SDK启动与类加载器接管SDK需要提供一个入口让使用者的应用在启动时用我们的自定义类加载器去加载SDK中的类。常见做法是在SDK的某个关键类如工厂类或配置类的静态代码块中将当前线程的上下文类加载器设置为我们的自定义类加载器或者通过Java Agent技术在更早的阶段介入。整个流程可以概括为开发源码 - Maven打包生成原始JAR - 插件加密JAR内Class文件 - 发布加密JAR至仓库 - 用户引入依赖 - 用户程序运行时SDK内自定义加载器解密并加载类。3. 核心模块实现细节解析3.1 自定义类加载器的实现与密钥管理自定义类加载器是解密行为的执行者。它的首要职责是“找到并解密”。public class SecureClassLoader extends ClassLoader { // 解密密钥此处仅为示例实际应采取更安全的方案 private static final byte[] DECRYPT_KEY your-32-byte-aes-256-key.getBytes(StandardCharsets.UTF_8); private final MapString, Class? classCache new ConcurrentHashMap(); private final String basePackage; public SecureClassLoader(ClassLoader parent, String basePackage) { super(parent); // 指定父加载器通常为当前线程上下文类加载器 this.basePackage basePackage; } Override protected Class? findClass(String name) throws ClassNotFoundException { // 1. 检查缓存避免重复解密加载 Class? clazz classCache.get(name); if (clazz ! null) { return clazz; } // 2. 只处理我们SDK自身范围内的类其他类委派给父加载器 if (!name.startsWith(basePackage)) { return super.loadClass(name); } // 3. 将类名转换为资源路径 String resourcePath name.replace(., /) .class.enc; // 假设加密文件后缀为 .class.enc try (InputStream is getParent().getResourceAsStream(resourcePath)) { if (is null) { throw new ClassNotFoundException(Encrypted class resource not found: name); } byte[] encryptedBytes readAllBytes(is); // 4. 解密字节码 byte[] decryptedBytes decrypt(encryptedBytes, DECRYPT_KEY); // 5. 定义类 clazz defineClass(name, decryptedBytes, 0, decryptedBytes.length); classCache.put(name, clazz); return clazz; } catch (IOException | GeneralSecurityException e) { throw new ClassNotFoundException(Failed to load encrypted class: name, e); } } private byte[] decrypt(byte[] encryptedData, byte[] key) throws GeneralSecurityException { // 实现AES解密例如使用AES/CBC/PKCS5Padding模式 // 此处省略具体Cipher初始化和解密逻辑 // 需要特别注意IV初始化向量的存储和读取通常可以拼接在加密数据头部 // ... return decryptedData; } }密钥管理是安全的重中之重。上述代码将密钥硬编码在加载器中这是极不安全的因为反编译加载器本身即可获得密钥。更安全的做法有白盒加密使用专门的白盒密码库将密钥和算法混淆使得在内存中提取密钥变得极其困难。动态密钥密钥不存储在代码中而是在SDK首次启动时通过网络请求从授权服务器获取需结合授权码、机器指纹等。这种方式对网络环境有要求。环境密钥将密钥拆分部分来自系统属性、环境变量部分来自文件在运行时组合。提高了静态分析的难度。注意没有绝对安全的方案。我们的目标是提高破解的成本和难度使其超过代码本身的价值。对于大多数商业场景结合代码混淆、字符串加密和白盒加密技术来保护自定义类加载器本身已经能构成足够高的门槛。3.2 基于Maven插件的自动化加密打包流程手动加密每个class文件并替换是不现实的。我们需要在Maven构建的生命周期中自动化完成。这里推荐使用maven-antrun-plugin来执行Ant任务因其灵活性强。在SDK项目的pom.xml中配置build plugins plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-jar-plugin/artifactId version3.2.0/version configuration !-- 指定生成的原始JAR文件名 -- finalNamemy-sdk-original/finalName /configuration /plugin plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-antrun-plugin/artifactId version3.0.0/version executions execution phasepackage/phase !-- 绑定到package阶段之后 -- goals goalrun/goal /goals configuration target !-- 1. 复制原始JAR到临时目录 -- copy file${project.build.directory}/my-sdk-original.jar tofile${project.build.directory}/my-sdk-encrypted.jar/ !-- 2. 使用Java任务调用我们编写的加密工具类 -- java classnamecom.yourcompany.encrypt.ClassEncryptor forktrue classpathrefmaven.runtime.classpath arg value${project.build.directory}/my-sdk-encrypted.jar/ arg valueyour-encryption-key/ !-- 传递加密密钥 -- /java !-- 3. 将加密后的JAR文件设置为最终产物 -- copy file${project.build.directory}/my-sdk-encrypted.jar tofile${project.build.directory}/${project.artifactId}-${project.version}.jar/ /target /configuration /execution /executions /plugin /plugins /build我们需要编写一个独立的工具类ClassEncryptor它接受JAR文件路径和密钥作为参数。这个工具类的工作是使用java.util.jar.JarFile和JarOutputStream打开原始的JAR和准备输出的新JAR。遍历JAR中的每一个条目JarEntry。如果条目是.class文件且不属于需要排除的类如自定义类加载器本身、接口类等则读取其字节码调用AES加密然后将加密后的字节写入新JAR条目名称可以改为.class.enc或保持.class但内容已加密。如果是其他文件如资源文件、META-INF/MANIFEST.MF或排除的类则原样复制。最后将我们预先编译好的SecureClassLoader.class文件添加到新JAR的根目录或特定包下。这样执行mvn clean package后得到的最终JAR就是加密后的成品。3.3 SDK启动入口与类加载器绑定策略加密的JAR准备好了如何让用户的程序使用我们的类加载器呢我们不能要求用户在他们的代码里手动实例化我们的SecureClassLoader。最好的方式是在SDK内部“悄无声息”地完成切换。方案一静态初始化块适用于Spring Boot/独立应用在SDK的一个必定会被加载的入口类例如一个提供全局配置的SDKAutoConfiguration类中设置public class SDKInitializer { private static final SecureClassLoader SECURE_CLASS_LOADER; static { // 获取当前类加载器通常是AppClassLoader ClassLoader originalClassLoader Thread.currentThread().getContextClassLoader(); // 创建我们的安全类加载器并设置为新的上下文类加载器 SECURE_CLASS_LOADER new SecureClassLoader(originalClassLoader, com.yourcompany.sdk); Thread.currentThread().setContextClassLoader(SECURE_CLASS_LOADER); // 注意此方法可能无法影响所有类的加载特别是那些在Spring容器初始化早期就已加载的类 } public static void ensureInitialized() { // 显式调用此方法以确保静态块执行 } }然后在用户项目的启动类或配置中显式调用一下SDKInitializer.ensureInitialized()。这种方式简单但侵入性较强且对加载时机敏感。方案二Java Agent更优雅、更早介入这是更专业和强大的方式。我们可以创建一个Java Agent在JVM启动的premain阶段通过InstrumentationAPI添加一个ClassFileTransformer。这个转换器可以拦截所有类的加载请求当发现目标类属于我们的SDK包时就从加密资源中读取、解密并返回字节码。public class SecurityAgent { public static void premain(String agentArgs, Instrumentation inst) { inst.addTransformer(new ClassFileTransformer() { Override public byte[] transform(ClassLoader loader, String className, Class? classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) { if (className ! null className.startsWith(com/yourcompany/sdk)) { // 根据className定位加密资源解密后返回字节码 // 返回null则表示使用原始字节码对于非加密类或加载器自身 return decryptClassBytes(className); } return null; // 返回nullJVM将继续使用原始字节码 } }, true); // true表示允许重转换 } }使用Agent需要打包一个单独的jar文件并在用户启动应用时添加JVM参数-javaagent:path/to/security-agent.jar。这种方式对用户代码零侵入且控制力最强。实操心得对于内部系统或合作紧密的客户方案一足够简单有效。对于需要广泛分发的商业SDK强烈建议采用方案二Java Agent。虽然增加了用户的使用步骤加一个JVM参数但提供了最好的透明性和安全性。记得在SDK文档中清晰说明Agent的用法。4. 集成发布与客户端使用指南4.1 发布加密SDK至Maven仓库加密后的JAR本身就是一个标准的Maven构件。你可以使用maven-deploy-plugin将其发布到公司的私有Nexus/Artifactory或者Sonatype的Maven Central仓库如果是开源项目。发布过程与普通JAR无异。!-- 在pom.xml中配置分发管理 -- distributionManagement repository idyour-releases/id urlhttps://nexus.yourcompany.com/repository/maven-releases//url /repository /distributionManagement执行mvn clean deploy即可。确保你的~/.m2/settings.xml中配置了正确的服务器认证信息。4.2 客户端使用者的集成方式对于使用者而言集成分为两种情况情况A使用静态初始化块方案在项目的pom.xml中正常引入SDK依赖。在应用启动的早期如Spring Boot的main方法第一行或在一个PostConstruct方法中调用SDKInitializer.ensureInitialized()。之后便可以像使用普通库一样importSDK的类并调用其方法。情况B使用Java Agent方案在项目的pom.xml中正常引入SDK依赖。额外需要将我们提供的Agent Jar文件如my-sdk-agent.jar放到项目某个路径下。在启动应用时添加JVM参数。例如在Spring Boot应用中java -javaagent:/path/to/my-sdk-agent.jar -jar your-application.jar或者在IDE的运行时配置中加上-javaagent参数。无需在代码中做任何初始化操作。注意事项务必在SDK的官方文档中明确说明集成方式、Agent的下载位置以及可能出现的兼容性问题。对于Agent方案要提醒用户注意Agent Jar的版本需要与SDK主版本匹配。4.3 版本管理与兼容性考量加密SDK的版本管理需要格外小心接口稳定性加密主要保护实现但公开的API接口一旦发布应尽量保持向后兼容。任何不兼容的接口变更都会导致用户代码编译失败或运行时错误。类加载器隔离由于使用了自定义类加载器SDK中的类与用户应用中的类处于不同的命名空间。要特别注意跨类加载器的类型转换和资源访问。例如SDK返回的一个ListString对象在用户代码中接收时可能会因为类加载器不同而导致ClassCastException。解决方案通常是返回通用接口类型或通过序列化/反序列化来传递数据。依赖冲突你的加密SDK可能内部依赖了某些第三方库如Google Guava、Jackson。要处理好与用户项目中相同库的版本冲突问题。建议在打包时使用maven-shade-plugin对重名包进行重命名Shade避免污染用户的类路径。5. 常见问题排查与实战调试技巧在实际开发和客户支持中会遇到各种各样的问题。这里记录几个最典型的案例和排查思路。5.1 ClassNotFoundException 或 NoClassDefFoundError这是最常见的问题意味着JVM找不到某个类。排查点1加密范围是否覆盖完全检查加密插件配置确认所有需要保护的.class文件都被正确加密。有时会因为路径匹配规则错误漏掉了某些类。可以解压生成的JAR查看目标类文件是否是加密格式文件头不再是标准的CAFEBABE魔数或者文件大小/内容明显为密文。排查点2自定义类加载器的findClass逻辑是否正确在findClass方法中增加日志打印它尝试加载的类名和查找的资源路径。确认当加载com.yourcompany.sdk.internal.SomeClass时它是否正确地尝试从JAR中读取com/yourcompany/sdk/internal/SomeClass.class.enc这个资源。资源路径的拼接规则必须与加密时写入的规则完全一致。排查点3类加载器委托机制是否正确确保你的SecureClassLoader在findClass中正确地委派了非自己负责的类给父加载器。如果错误地尝试自己去加载java.lang.String或用户项目中的类肯定会失败。5.2 解密失败或解密后字节码无效加载类时抛出ClassFormatError或解密相关的异常如BadPaddingException。排查点1密钥一致性这是最可能的原因。加密打包时使用的密钥必须与运行时SecureClassLoader或Agent中使用的解密密钥完全一致包括密钥字节、加密模式、填充方式、IV向量。建议将密钥抽取到统一的配置文件中供加密工具和运行时加载器读取确保来源唯一。排查点2加密/解密算法细节确认加密端和解密端使用的是相同的算法规范字符串例如都是AES/CBC/PKCS5Padding。并且IV向量的生成和传递方式要一致。通常做法是将IV和密文一起存储解密时先分离出IV。排查点3字节码损坏检查加密工具在读取原始.class文件、加密、写入新JAR的过程中是否发生了数据损坏。可以写一个简单的测试对一个类文件加密后立即解密然后尝试用ClassLoader.defineClass加载看是否成功。5.3 性能影响评估与优化加密解密和自定义类加载会带来额外的性能开销主要体现在首次加载类时的延时。影响评估对于Spring Boot应用启动时需要加载大量类如果SDK包含很多类且全部加密可能会导致启动时间明显变慢几百毫秒到几秒不等。运行时由于每个类只加载一次并缓存后续调用几乎没有性能损失。优化策略按需加密只加密真正包含核心业务逻辑的类。公开的接口类、POJO纯数据对象、枚举等可以排除在加密之外。这能大幅减少需要解密的类数量。缓存优化在自定义类加载器中务必实现缓存如示例中的ConcurrentHashMap避免同一个类被重复解密。预热对于性能敏感的应用可以在系统启动后、正式处理请求前主动触发加载SDK中所有常用的类将解密开销提前到启动阶段消化。5.4 与Spring框架集成的特殊问题Spring框架大量使用反射、动态代理和依赖注入这在与自定义类加载器交互时容易出问题。问题Spring扫描不到SDK中的组件如果SDK中提供了Component,Service等Spring注解类并且这些类被加密了Spring默认的类路径扫描ClassPathScanning可能无法识别它们因为扫描器使用的是标准的ClassLoader无法读取加密的字节码。解决方案显式注册Bean不在SDK中使用注解扫描而是要求用户在他们的Spring配置中通过Bean方法显式地创建和配置SDK提供的服务类。这样Bean的创建由用户代码发起会通过已设置好的自定义类加载器路径。提供自动配置类在SDK中提供一个不被加密的Configuration类例如SDKAutoConfiguration。在这个配置类里使用Bean方法手动创建SDK内部的服务Bean。因为配置类本身未被加密能被Spring扫描到而它在创建Bean时会触发对加密类的加载此时自定义类加载器就会生效。使用Import在用户的配置类上使用Import(SDKAutoConfiguration.class)来引入上述配置。问题AOP代理失败Spring AOP或AspectJ为Bean创建代理时可能会因为目标类来自不同的类加载器而导致代理类生成失败。解决方案确保为AOP配置的切面表达式能正确匹配到来自安全类加载器的类。有时可能需要调整类加载器的父子关系或线程上下文类加载器的设置确保AOP框架和Bean在同一个类加载器上下文中工作。这是一个比较深的水域需要根据具体AOP用例进行调试。6. 安全加固与对抗逆向工程基础的加密和自定义加载只是第一道防线。一个有决心的攻击者仍然可以通过动态调试、内存Dump等手段来获取解密后的字节码。我们需要实施纵深防御。6.1 加固自定义类加载器本身自定义类加载器是解密的关键必须重点保护。代码混淆使用ProGuard或商业混淆器如Allatori, DashO对SecureClassLoader和相关工具类进行高强度混淆包括控制流混淆、字符串加密等。反调试检测在加载器的静态块或关键方法中加入检测调试器连接的代码如检查java.lang.management相关属性一旦发现被调试可以触发异常或执行误导性代码。完整性校验对加密JAR文件或加载器自身的字节码进行哈希校验防止被篡改。6.2 运行时保护防止内存转储虽然难度较大但可以尝试通过定时变换内存中解密后字节码的存储位置、或对字节码进行二次混淆来增加从内存中完整提取.class文件的难度。一些商业保护工具在这方面做得更深入。敏感逻辑Native化将最核心、最关键的算法逻辑如许可证校验、加解密核心用C/C实现编译成JNI本地库.so或.dll。这样破解者需要逆向本地机器码难度大大增加。但这会牺牲跨平台性。6.3 法律与技术结合技术手段总有极限。务必在SDK的许可证协议License Agreement中明确禁止逆向工程、反编译和再分发。虽然这不能阻止技术高超的破解者但为采取法律行动提供了依据。一个实用的建议是进行风险分级不是所有代码都需要同等强度的保护。识别出真正的核心竞争力和商业秘密例如独特的推荐算法、专有的数据转换流程对这些部分实施最高级别的保护如Native化白盒加密混淆。对于一般的工具类、辅助类使用标准的加密和混淆即可。这样可以在安全性和开发维护成本之间取得更好的平衡。7. 备选方案与工具链推荐除了完全自研市面上也有一些成熟的商业和开源工具可以帮助实现JAR保护。商业工具Jscrambler: 提供JavaScript和WebAssembly的混淆保护对于前后端一体的方案有特色。DashO (PreEmptive Solutions): 老牌的Java/.NET混淆和压缩工具提供运行时自检、篡改检测等高级功能。Zelix KlassMaster: 强大的Java混淆器支持多种混淆变换。Virbox Protector: 国内的一款安全工具支持Java字节码加密、虚拟机外壳等技术。这些工具通常提供图形化界面和更全面的保护方案包括资源加密、反调试、许可证管理但需要付费购买。开源方案ProGuard: 最著名的免费Java混淆器功能强大但主要侧重混淆而非加密。可以结合本文的自定义加载器方案先混淆再加密达到双重效果。yGuard: 另一个开源混淆工具是Ant任务易于集成到构建流程中。ClassFinal: 一个国人开源的项目基于Java Agent可以对JAR包进行加密。其思路与本文所述类似可以作为参考或直接使用。如何选择如果预算充足追求省心和完善的技术支持选择成熟的商业工具。如果对安全性有极高定制化需求或者希望深入理解原理并完全掌控流程自研方案即本文所述是最佳选择。如果项目是开源的或者预算有限使用ProGuard 自定义加载器是一个性价比很高的组合。我个人在多个项目中采用的是自研方案因为它提供了最大的灵活性。例如我们可以根据客户的不同授权等级动态生成不同功能的加密JAR包或者实现按模块的许可证控制这些都是标准化工具难以满足的定制需求。自研的初期投入确实较大但一旦这套构建和发布流程固化下来就成为了团队可复用的资产长期来看是值得的。

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号