当前位置:   article > 正文

对接海关总署电子口岸时遇到清单加密问题的总结_swxajce

swxajce

问题描述:

由于海关各个关区的申报都对接了总署,以前向各个关区申报的业务现在全部通过向总署申报,在向总署申报清单时遇到了报文加密问题,怎么加密也加不对。总署给的规则也不清晰,目前是第一家在做这个,所以也没人请教,特发此文用来记录下。

报文格式加密处理代码

  1. import java.io.ByteArrayOutputStream;
  2. import java.io.PrintStream;
  3. import java.util.Map;
  4. import *.CertificateInfo;
  5. import *.init.CustSwxaJm;
  6. import *.XmlElement;
  7. /**
  8. * <p>标题: 调用三未信安加密机加密</p>
  9. * <p>功能: 提供对海关总署报文加密的加密服务 </p>
  10. */
  11. public class CustSwxaJmService
  12. {
  13. private final CustSwxaJm swxaJm;
  14. private final CertificateInfo certificateInfo;
  15. public CustSwxaJmService(Map<String,Object> envParams)
  16. {
  17. this.swxaJm = new CustSwxaJm(envParams);
  18. this.certificateInfo = new CertificateInfo(envParams);
  19. }
  20. /**
  21. * @param envParams
  22. * @param index 密钥索引位置 (1:南沙,2:黄埔)
  23. */
  24. public CustSwxaJmService(Map<String,Object> envParams, int index)
  25. {
  26. this.swxaJm = new CustSwxaJm(envParams, index);
  27. this.certificateInfo = new CertificateInfo(envParams, index);
  28. }
  29. /**
  30. * 对加密节点进行预处理
  31. * @param envParams
  32. * @param ceb621Message
  33. * @param encodeStr
  34. */
  35. public void setSignatureXmlElement(XmlElement ceb621Message, String encodeStr)
  36. {
  37. String digestValueZY = swxaJm.getHash_SHAl(toHashText(ceb621Message));
  38. //增加 Signature
  39. XmlElement signature = new XmlElement("Signature");//1层
  40. signature.setAttribute("xmlns", "http://www.w3.org/2000/09/xmldsig#");
  41. ceb621Message.addSubElement(signature);
  42. //增加 Signature-SignedInfo
  43. XmlElement signedInfo = new XmlElement("SignedInfo");//2层
  44. signature.addSubElement(signedInfo);
  45. //增加 Signature-SignedInfo-CanonicalizationMethod
  46. XmlElement canonicalizationMethod = new XmlElement("CanonicalizationMethod");//3层
  47. canonicalizationMethod.setAttribute("Algorithm", "http://www.w3.org/TR/2001/REC-xml-c14n-20010315");
  48. signedInfo.addSubElement(canonicalizationMethod);
  49. //增加 Signature-SignedInfo-SignatureMethod
  50. XmlElement signatureMethod = new XmlElement("SignatureMethod");//3层
  51. signatureMethod.setAttribute("Algorithm", "http://www.w3.org/2000/09/xmldsig#rsa-sha1");
  52. signedInfo.addSubElement(signatureMethod);
  53. //增加 Signature-SignedInfo-Reference
  54. XmlElement seference = new XmlElement("Reference");//3层
  55. seference.setAttribute("URI", "");
  56. signedInfo.addSubElement(seference);
  57. //增加 Signature-SignedInfo-Reference-Transforms
  58. XmlElement transforms = new XmlElement("Transforms");//4层
  59. seference.addSubElement(transforms);
  60. //增加 Signature-SignedInfo-Reference-Transforms-Transform
  61. XmlElement transform = new XmlElement(signedInfo, "Transform");//5层
  62. transform.setAttribute("Algorithm", "http://www.w3.org/2000/09/xmldsig#enveloped-signature");
  63. transforms.addSubElement(transform);
  64. //增加 Signature-SignedInfo-Reference-DigestMethod
  65. XmlElement digestMethod = new XmlElement("DigestMethod");//4层
  66. digestMethod.setAttribute("Algorithm", "http://www.w3.org/2000/09/xmldsig#sha1");
  67. seference.addSubElement(digestMethod);
  68. //增加 Signature-SignedInfo-Reference-DigestValue
  69. XmlElement digestValue = new XmlElement("DigestValue");//4层
  70. //String digestValueJQ = "digestValue加签值";
  71. digestValue.setValue(digestValueZY);
  72. seference.addSubElement(digestValue);
  73. //增加 Signature-SignatureValue
  74. XmlElement signatureValue = new XmlElement("SignatureValue");//2层
  75. signature.addSubElement(signatureValue);
  76. String signatureValueJQ = swxaJm.getPrivateSign(toXmlText(ceb621Message, encodeStr));//getXMLStr(signedInfo, 2)
  77. //signatureValueJQ.replace("
  78. ", "\n");
  79. signatureValue.setValue(signatureValueJQ);
  80. //增加 Signature-KeyInfo
  81. XmlElement keyInfo = new XmlElement("KeyInfo");//2层
  82. signature.addSubElement(keyInfo);
  83. //增加 Signature-KeyInfo-KeyName
  84. XmlElement keyName = new XmlElement("KeyName");//3层
  85. String keyNameJQ = certificateInfo.getSerialNumber();
  86. keyName.setValue(keyNameJQ);
  87. keyInfo.addSubElement(keyName);
  88. //增加 Signature-KeyInfo-X509Data
  89. XmlElement x509Data = new XmlElement("X509Data");//3层
  90. keyInfo.addSubElement(x509Data);
  91. //增加 Signature-KeyInfo-X509Data-X509Certificate
  92. XmlElement x509Certificate = new XmlElement("X509Certificate");//4层
  93. String x509CertificateJQ = certificateInfo.getX509InfoBase64();
  94. //x509CertificateJQ.replace("
  95. ", "\n");
  96. x509Certificate.setValue(x509CertificateJQ);
  97. x509Data.addSubElement(x509Certificate);
  98. }
  99. /**
  100. * 海关格式化XML内容
  101. * @param xmlElement
  102. * @param encodeStr
  103. * @return
  104. */
  105. public String toXmlText(XmlElement xmlElement, String encodeStr)
  106. {
  107. ByteArrayOutputStream os = new ByteArrayOutputStream();
  108. PrintStream out = new PrintStream(os);
  109. String nodeName = "Signature";
  110. //添加头结点
  111. out.print(encodeStr);
  112. out.print('\n');
  113. list(xmlElement, out, 0, nodeName);
  114. return new String(os.toByteArray());
  115. }
  116. private String toHashText(XmlElement xmlElement)
  117. {
  118. ByteArrayOutputStream os = new ByteArrayOutputStream();
  119. PrintStream out = new PrintStream(os);
  120. list(xmlElement, out, 0, "Signature");
  121. String xmlTest = new String(os.toByteArray());
  122. return xmlTest;
  123. }
  124. private void list(XmlElement xmlElement, PrintStream out, int baseIndent, String nodeName)
  125. {
  126. if (!existParentElement(xmlElement, nodeName))
  127. {
  128. for (int i = 0; i < baseIndent; i++)
  129. {
  130. out.print('\t');
  131. }
  132. }
  133. out.print("<" + xmlElement.qName);
  134. if (xmlElement.localName != null && xmlElement.localName.length() > 0)
  135. {
  136. out.print(" name=\"" + xmlElement.localName + "\"");
  137. }
  138. for (final String name : xmlElement.getAttributes().keySet())
  139. {
  140. out.print(" " + name + "=\"" + XmlElement.xmlEscape(xmlElement.getAttributes().get(name), 0xffff) + "\"");
  141. }
  142. int nSub = xmlElement.subElements.size();
  143. if (nSub == 0)
  144. {
  145. if (xmlElement.value != null && xmlElement.value.length() > 0)
  146. {
  147. out.print(">" + XmlElement.xmlEscape(xmlElement.value, 31) + "</" + xmlElement.qName + ">");
  148. if (!existParentElement(xmlElement, nodeName))
  149. {
  150. out.print('\n');
  151. }
  152. } else
  153. {
  154. out.print("/>");
  155. if (!existParentElement(xmlElement, nodeName))
  156. {
  157. out.print('\n');
  158. }
  159. }
  160. } else
  161. {
  162. out.print(">");
  163. if (!existParentElement(xmlElement, nodeName))
  164. {
  165. out.print('\n');
  166. }
  167. if (xmlElement.value != null && xmlElement.value.length() > 0)
  168. {
  169. if (!existParentElement(xmlElement, nodeName))
  170. {
  171. for (int i = 0; i < baseIndent + 1; i++)
  172. {
  173. out.print('\t');
  174. }
  175. }
  176. out.print(xmlElement.value);
  177. if (!existParentElement(xmlElement, nodeName))
  178. {
  179. out.print('\n');
  180. }
  181. }
  182. for (int i = 0; i < nSub; i++)
  183. {
  184. list(xmlElement.subElements.get(i), out, baseIndent + 1, nodeName);
  185. }
  186. for (int i = 0; i < baseIndent; i++)
  187. {
  188. if (!existParentElement(xmlElement, nodeName))
  189. {
  190. out.print('\t');
  191. }
  192. }
  193. out.print("</" + xmlElement.qName + ">");
  194. if (!existParentElement(xmlElement, nodeName))
  195. {
  196. out.print('\n');
  197. }
  198. }
  199. }
  200. /**
  201. * 判断父节点中是否存在nodeName节点
  202. * @param xmlElement 节点
  203. * @param nodeName 比较节点
  204. * @return
  205. */
  206. private boolean existParentElement(XmlElement xmlElement, String nodeName)
  207. {
  208. boolean flag = false;
  209. String name = xmlElement.qName;
  210. if (nodeName.equals(name))
  211. {
  212. flag = true;
  213. } else
  214. {
  215. XmlElement pNode = xmlElement.getParentNode();
  216. if (pNode != null)
  217. {
  218. String pname = pNode.qName;
  219. if (nodeName.equals(pname))
  220. {
  221. flag = true;
  222. } else
  223. {
  224. flag = existParentElement(pNode, nodeName);
  225. }
  226. }
  227. }
  228. return flag;
  229. }
  230. }
获取证书的代码

  1. import java.io.FileInputStream;
  2. import java.io.FileNotFoundException;
  3. import java.io.IOException;
  4. import java.security.cert.CertificateEncodingException;
  5. import java.security.cert.CertificateException;
  6. import java.security.cert.CertificateFactory;
  7. import java.security.cert.X509Certificate;
  8. import java.util.Map;
  9. import org.apache.xml.security.utils.Base64;
  10. /**
  11. * <p>标题: 证书信息</p>
  12. * <p>功能: 获取证书信息</p>
  13. */
  14. public class CertificateInfo
  15. {
  16. /**系统环境变量*/
  17. private final Map<String,Object> envParams;
  18. /**证书地址*/
  19. private String x509CertFilePath;
  20. /**证书*/
  21. private X509Certificate x509Certificate;
  22. /**证书编号*/
  23. private String SerialNumber;
  24. /**证书信息Base64编码*/
  25. private String x509InfoBase64;
  26. public CertificateInfo(Map<String,Object> envParams)
  27. {
  28. this.envParams = envParams;
  29. init(1);
  30. }
  31. public CertificateInfo(Map<String,Object> envParams, int index)
  32. {
  33. this.envParams = envParams;
  34. init(index);
  35. }
  36. private void init(int index)
  37. {
  38. setX509CertFilePath(initX509CertFilePath(index));
  39. setX509Certificate(initX509Certificate());
  40. setSerialNumber(initSerialNumber());
  41. setX509InfoBase64(initX509InfoBase64());
  42. }
  43. /**
  44. * 证书地址
  45. * @return
  46. */
  47. public String initX509CertFilePath(int index)
  48. {
  49. switch (index)
  50. {
  51. case 1:
  52. return CustSysOptions.getCertificateAddress(this.envParams);
  53. case 3:
  54. return CustSysOptions.getCertificateAddress_A(this.envParams);
  55. }
  56. return null;
  57. }
  58. /**
  59. * 获取证书
  60. * @return
  61. */
  62. private X509Certificate initX509Certificate()
  63. {
  64. X509Certificate x509Cert = null;
  65. FileInputStream in = null;
  66. try
  67. {
  68. in = new FileInputStream(getX509CertFilePath());
  69. CertificateFactory cf = CertificateFactory.getInstance("X.509");
  70. x509Cert = (X509Certificate) cf.generateCertificate(in);
  71. } catch (FileNotFoundException e)
  72. {
  73. throw new RuntimeException("证书文件不存在,文件路径:" + getX509CertFilePath(), e);
  74. } catch (CertificateException e)
  75. {
  76. throw new RuntimeException("获取证书内容失败!", e);
  77. } finally
  78. {
  79. try
  80. {
  81. if (in != null)
  82. {
  83. in.close();
  84. }
  85. } catch (IOException e)
  86. {
  87. throw new RuntimeException("关闭读取证书的文件流失败!", e);
  88. }
  89. }
  90. return x509Cert;
  91. }
  92. /**
  93. * 获取证书编号
  94. * @return
  95. * 赵力
  96. */
  97. private String initSerialNumber()
  98. {
  99. String numberStr = getX509Certificate().getSerialNumber().toString(16);
  100. if (StrUtil.isNotNull(numberStr) && numberStr.length() < 16)
  101. {
  102. StringBuilder buf = new StringBuilder();
  103. int num = 16 - numberStr.length();
  104. for (int i = 0; i < num; i++)
  105. {
  106. buf.append("0");
  107. }
  108. numberStr = buf.toString() + numberStr;
  109. }
  110. return numberStr;
  111. }
  112. /**
  113. * 获取证书信息BASE64编码
  114. * @return
  115. * 赵力
  116. */
  117. private String initX509InfoBase64()
  118. {
  119. try
  120. {
  121. return Base64.encode(x509Certificate.getEncoded());
  122. } catch (CertificateEncodingException e)
  123. {
  124. throw new RuntimeException("获取证书信息BASE64编码失败", e);
  125. }
  126. }
  127. public String getX509CertFilePath()
  128. {
  129. return x509CertFilePath;
  130. }
  131. private void setX509CertFilePath(String x509CertFilePath)
  132. {
  133. this.x509CertFilePath = x509CertFilePath;
  134. }
  135. public X509Certificate getX509Certificate()
  136. {
  137. return x509Certificate;
  138. }
  139. private void setX509Certificate(X509Certificate x509Certificate)
  140. {
  141. this.x509Certificate = x509Certificate;
  142. }
  143. public String getSerialNumber()
  144. {
  145. return SerialNumber;
  146. }
  147. private void setSerialNumber(String serialNumber)
  148. {
  149. SerialNumber = serialNumber;
  150. }
  151. public String getX509InfoBase64()
  152. {
  153. return x509InfoBase64;
  154. }
  155. private void setX509InfoBase64(String x509InfoBase64)
  156. {
  157. this.x509InfoBase64 = x509InfoBase64;
  158. }
  159. }
三未信安加密对象代码

  1. import java.io.IOException;
  2. import java.io.StringReader;
  3. import java.security.KeyPair;
  4. import java.security.KeyPairGenerator;
  5. import java.security.MessageDigest;
  6. import java.security.PrivateKey;
  7. import java.security.Security;
  8. import java.security.Signature;
  9. import java.util.Map;
  10. import javax.xml.parsers.DocumentBuilder;
  11. import javax.xml.parsers.DocumentBuilderFactory;
  12. import javax.xml.parsers.ParserConfigurationException;
  13. import org.apache.xml.security.Init;
  14. import org.apache.xml.security.c14n.CanonicalizationException;
  15. import org.apache.xml.security.c14n.Canonicalizer;
  16. import org.apache.xml.security.c14n.InvalidCanonicalizerException;
  17. import org.apache.xml.security.utils.Base64;
  18. import org.apache.xml.security.utils.IgnoreAllErrorHandler;
  19. import org.w3c.dom.Document;
  20. import org.w3c.dom.Node;
  21. import org.w3c.dom.NodeList;
  22. import org.xml.sax.InputSource;
  23. import org.xml.sax.SAXException;
  24. import com.sansec.jce.provider.SwxaProvider;
  25. /**
  26. * <p>标题:三未信安加密对象 </p>
  27. * <p>功能:对发送总署的报文进行加密 </p>
  28. */
  29. public class CustSwxaJm
  30. {
  31. /**配置文件地址*/
  32. private String swxaConfigFilePath;
  33. /**内部密钥位置*/
  34. private int keyIndex;
  35. /**内部密钥*/
  36. private KeyPair inKeyPair;
  37. /**外部密钥*/
  38. private KeyPair outKeyPair;
  39. public CustSwxaJm(Map<String,Object> envParams)
  40. {
  41. init(envParams, 1);
  42. }
  43. public CustSwxaJm(Map<String,Object> envParams, int keyIndex)
  44. {
  45. init(envParams, keyIndex);
  46. }
  47. /**
  48. * 初始化加密对象
  49. */
  50. private void init(Map<String,Object> envParams, int keyIndex)
  51. {
  52. setSwxaConfigFilePath(StrUtil.obj2str(DataConfig.getDataConfig(envParams, "swxaConfigFilePath")));
  53. initProvider();
  54. setKeyIndex(keyIndex);
  55. setInKeyPair(initInKeyPair());
  56. setOutKeyPair(initOutKeyPair());
  57. }
  58. /**
  59. * 初始化配置文件
  60. * 功能:加载配置文件并进行初始化
  61. */
  62. private void initProvider()
  63. {
  64. SwxaProvider provider = new SwxaProvider(getSwxaConfigFilePath());
  65. Security.addProvider(provider);
  66. }
  67. /**
  68. * 生成内部密钥
  69. */
  70. private KeyPair initInKeyPair()
  71. {
  72. KeyPair kp = null;
  73. try
  74. {
  75. KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA", "SwxaJCE");
  76. kpg.initialize(keyIndex << 16);
  77. kp = kpg.genKeyPair();
  78. if (kp == null)
  79. {
  80. throw new RuntimeException();
  81. }
  82. } catch (Exception e)
  83. {
  84. throw new RuntimeException("获取内部密钥失败");
  85. }
  86. return kp;
  87. }
  88. /**
  89. * 生成外部密钥
  90. */
  91. private static KeyPair initOutKeyPair()
  92. {
  93. KeyPair kp = null;
  94. try
  95. {
  96. int keylength = 1024;
  97. KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA", "SwxaJCE");
  98. kpg.initialize(keylength);
  99. kp = kpg.genKeyPair();
  100. if (kp == null)
  101. {
  102. throw new RuntimeException();
  103. }
  104. } catch (Exception e)
  105. {
  106. throw new RuntimeException("生成外部密钥失败");
  107. }
  108. return kp;
  109. }
  110. /**
  111. * 生成HASH摘要
  112. */
  113. public String getHash_SHAl(String xmlInfo)
  114. {
  115. byte[] plain_bytes;
  116. try
  117. {
  118. Init.init();
  119. Canonicalizer canonicalizer = Canonicalizer.getInstance(Canonicalizer.ALGO_ID_C14N_OMIT_COMMENTS);
  120. plain_bytes = canonicalizer.canonicalize(xmlInfo.getBytes());
  121. } catch (RuntimeException e)
  122. {
  123. throw new RuntimeException("报错内容", e);
  124. } catch (CanonicalizationException e)
  125. {
  126. throw new RuntimeException("报错内容", e);
  127. } catch (ParserConfigurationException e)
  128. {
  129. throw new RuntimeException("报错内容", e);
  130. } catch (IOException e)
  131. {
  132. throw new RuntimeException("报错内容", e);
  133. } catch (SAXException e)
  134. {
  135. throw new RuntimeException("报错内容", e);
  136. } catch (InvalidCanonicalizerException e)
  137. {
  138. throw new RuntimeException("报错内容", e);
  139. }
  140. String hashShal;
  141. MessageDigest md;
  142. try
  143. {
  144. md = MessageDigest.getInstance("SHA1", "SwxaJCE");
  145. md.update(plain_bytes);
  146. byte[] data = md.digest();
  147. hashShal = Base64.encode(data);
  148. } catch (Exception e)
  149. {
  150. throw new RuntimeException("加密机生成hash摘要失败");
  151. }
  152. return hashShal;
  153. }
  154. /**
  155. * 进行加签
  156. * @param strSignedInfo XML报文不包含头的文本
  157. */
  158. public String getPrivateSign(String xmlText)
  159. {
  160. String privateSignatue;
  161. DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
  162. factory.setNamespaceAware(true);
  163. factory.setValidating(true);
  164. byte[] bytes = null;
  165. try
  166. {
  167. DocumentBuilder builder = factory.newDocumentBuilder();
  168. builder.setErrorHandler(new IgnoreAllErrorHandler());
  169. //signedInfo
  170. Document doc = builder.parse(new InputSource(new StringReader(xmlText)));
  171. NodeList nodeList = doc.getElementsByTagName("SignedInfo");
  172. Node node = nodeList.item(0);
  173. Init.init();
  174. Canonicalizer canon = Canonicalizer.getInstance(Canonicalizer.ALGO_ID_C14N_OMIT_COMMENTS);
  175. //格式化
  176. bytes = canon.canonicalizeSubtree(node);
  177. PrivateKey privateKey = getInKeyPair().getPrivate();
  178. Signature signatue = null;
  179. signatue = Signature.getInstance("SHA1WithRSA", "SwxaJCE");
  180. signatue.initSign(privateKey);
  181. signatue.update(bytes);
  182. privateSignatue = Base64.encode(signatue.sign());
  183. } catch (Exception e)
  184. {
  185. throw new RuntimeException("加密机进行内部签名失败");
  186. }
  187. return privateSignatue;
  188. }
  189. private String getSwxaConfigFilePath()
  190. {
  191. return swxaConfigFilePath;
  192. }
  193. private void setSwxaConfigFilePath(String swxaConfigFilePath)
  194. {
  195. this.swxaConfigFilePath = swxaConfigFilePath;
  196. }
  197. public KeyPair getInKeyPair()
  198. {
  199. return inKeyPair;
  200. }
  201. private void setInKeyPair(KeyPair inKeyPair)
  202. {
  203. this.inKeyPair = inKeyPair;
  204. }
  205. public int getKeyIndex()
  206. {
  207. return keyIndex;
  208. }
  209. private void setKeyIndex(int keyIndex)
  210. {
  211. this.keyIndex = keyIndex;
  212. }
  213. public KeyPair getOutKeyPair()
  214. {
  215. return outKeyPair;
  216. }
  217. private void setOutKeyPair(KeyPair outKeyPair)
  218. {
  219. this.outKeyPair = outKeyPair;
  220. }
  221. }

实际调用

  1. package snsoft.customs.custentryelist.serv;
  2. import java.math.BigDecimal;
  3. import java.util.Date;
  4. import java.util.HashMap;
  5. import java.util.List;
  6. import java.util.Map;
  7. /**
  8. * <p>标题: 海关总署进境清单导出监听</p>
  9. */
  10. public class CustentryelistZSXmlListener
  11. {
  12. protected final String DATATYPE1 = "YYYYMMDDhhmmss";
  13. protected final String DATATYPE2 = "YYYYMMDD";
  14. private static final String MTABLE = "custentryelist";
  15. private final CustSwxaJmService swxaJmService;
  16. /**
  17. * @param envParams
  18. * @param parameter
  19. * @param xmlObj
  20. */
  21. public CustentryelistZSXmlListener(Map<String,Object> envParams, Map<String,Object> parameter, XmlExObject xmlObj)
  22. {
  23. super(envParams, parameter, xmlObj);
  24. this.swxaJmService = new CustSwxaJmService(envParams);
  25. }
  26. /**
  27. * 加密对节点进行预处理
  28. */
  29. @Override
  30. public void onXmlElementCreated(XmlElement xmlElement)
  31. {
  32. super.onXmlElementCreated(xmlElement);
  33. XmlElement ceb621Message = xmlElement.getSubElement("ceb:CEB621Message");
  34. String encodeStr = xmlObj.getXmlencoding();
  35. swxaJmService.setSignatureXmlElement(ceb621Message, encodeStr);
  36. }
  37. /**
  38. * 海关格式化xml内容
  39. */
  40. @Override
  41. public String toXml(XmlElement xmlElement)
  42. {
  43. XmlElement ceb621Message = xmlElement.getSubElement("ceb:CEB621Message");
  44. String encodeStr = xmlObj.getXmlencoding();
  45. return swxaJmService.toXmlText(ceb621Message, encodeStr);
  46. }
  47. }






声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/空白诗007/article/detail/811321?site
推荐阅读
相关标签
  

闽ICP备14008679号