打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
Java docx4j 操作word 1.0

Java docx4j 操作word 1.0

Dily_Su
已于 2022-05-06 16:40:40 修改
731
收藏 1
文章标签: java
于 2021-12-16 15:45:40 首次发布
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
版权

一、简介

      本工具类复制即可使用,内附测试代码,包含以下操作:

      -- word 中 属性值替换

      -- word 中 列表动态插入数据

      -- word 转 pdf

二、环境

  1. <dependency>
  2. <groupId>com.itextpdf</groupId>
  3. <artifactId>itextpdf</artifactId>
  4. <version>5.5.13.2</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>freemarker</groupId>
  8. <artifactId>freemarker</artifactId>
  9. <version>2.3.8</version>
  10. </dependency>
  11. <dependency>
  12. <groupId>org.docx4j</groupId>
  13. <artifactId>docx4j</artifactId>
  14. <version>6.1.2</version>
  15. </dependency>
  16. <dependency>
  17. <groupId>org.docx4j</groupId>
  18. <artifactId>docx4j-export-fo</artifactId>
  19. <version>8.1.7</version> //版本号不能高于8.1.7
  20. </dependency>
  21. <!-- 条形码 -->
  22. <dependency>
  23. <groupId>net.sf.barcode4j</groupId>
  24. <artifactId>barcode4j-light</artifactId>
  25. <version>2.0</version>
  26. </dependency>
  27. <dependency>
  28. <groupId>javax.xml.bind</groupId>
  29. <artifactId>jaxb-api</artifactId>
  30. <version>2.3.1</version>
  31. </dependency>
  32. <!-- https://mvnrepository.com/artifact/javax.activation/activation -->
  33. <dependency>
  34. <groupId>javax.activation</groupId>
  35. <artifactId>activation</artifactId>
  36. <version>1.1</version>
  37. </dependency>
  38. <!-- https://mvnrepository.com/artifact/org.glassfish.jaxb/jaxb-runtime -->
  39. <dependency>
  40. <groupId>org.glassfish.jaxb</groupId>
  41. <artifactId>jaxb-runtime</artifactId>
  42. <version>2.3.5</version>
  43. </dependency>

三、工具类

  1. import com.itextpdf.text.Image;
  2. import com.itextpdf.text.pdf.PdfContentByte;
  3. import com.itextpdf.text.pdf.PdfReader;
  4. import com.itextpdf.text.pdf.PdfStamper;
  5. import org.docx4j.Docx4J;
  6. import org.docx4j.TraversalUtil;
  7. import org.docx4j.XmlUtils;
  8. import org.docx4j.convert.out.FOSettings;
  9. import org.docx4j.dml.wordprocessingDrawing.Inline;
  10. import org.docx4j.finders.ClassFinder;
  11. import org.docx4j.fonts.IdentityPlusMapper;
  12. import org.docx4j.fonts.Mapper;
  13. import org.docx4j.fonts.PhysicalFonts;
  14. import org.docx4j.jaxb.Context;
  15. import org.docx4j.model.datastorage.migration.VariablePrepare;
  16. import org.docx4j.model.table.TblFactory;
  17. import org.docx4j.openpackaging.exceptions.Docx4JException;
  18. import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
  19. import org.docx4j.openpackaging.parts.WordprocessingML.BinaryPartAbstractImage;
  20. import org.docx4j.openpackaging.parts.WordprocessingML.MainDocumentPart;
  21. import org.docx4j.wml.*;
  22. import org.springframework.core.io.ClassPathResource;
  23. import javax.xml.bind.JAXBElement;
  24. import javax.xml.bind.JAXBException;
  25. import java.io.*;
  26. import java.util.*;
  27. /**
  28. * Time: 2021/12/20 13:22
  29. * Author: Dily
  30. * Remark:
  31. */
  32. public class Docx4jUtils {
  33. /**
  34. * 创建一个空白 Docx 文档
  35. *
  36. * @param filePath 文件路径
  37. */
  38. public void createDocx(String filePath) throws Docx4JException {
  39. WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.createPackage();
  40. wordMLPackage.save(new File(filePath));
  41. }
  42. /**
  43. * 加载 Docx 文件
  44. *
  45. * @param filePath 文件地址
  46. * @return WordProcessingMLPackage操作包
  47. */
  48. public WordprocessingMLPackage loadDocx(String filePath) throws Docx4JException {
  49. return WordprocessingMLPackage.load(new File(filePath));
  50. }
  51. /**
  52. * 创建一个空白 Docx 文档
  53. *
  54. * @param outputStream 流
  55. */
  56. public void createDocx(OutputStream outputStream) throws Docx4JException {
  57. WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.createPackage();
  58. wordMLPackage.save(outputStream);
  59. }
  60. /**
  61. * 添加带样式的文本/段落
  62. *
  63. * @param wordMLPackage docx 操作包
  64. * @param styled 样式
  65. * @param conText 文本
  66. * @return WordProcessingMLPackage 操作包
  67. */
  68. public WordprocessingMLPackage addContext(WordprocessingMLPackage wordMLPackage, String styled, String conText) {
  69. wordMLPackage.getMainDocumentPart().addStyledParagraphOfText(styled, conText);
  70. return wordMLPackage;
  71. }
  72. /**
  73. * 添加文本/段落
  74. *
  75. * @param wordMLPackage docx 操作包
  76. * @param conText 正文
  77. * @return WordProcessingMLPackage 操作包
  78. */
  79. public WordprocessingMLPackage addContext(WordprocessingMLPackage wordMLPackage, String conText) {
  80. wordMLPackage.getMainDocumentPart().addParagraphOfText(conText);
  81. return wordMLPackage;
  82. }
  83. /**
  84. * 添加图片
  85. *
  86. * @param wordMLPackage 操作包
  87. * @param imagePath 图片地址
  88. * @return WordProcessingMLPackage 操作包
  89. */
  90. public WordprocessingMLPackage addImage(WordprocessingMLPackage wordMLPackage, String imagePath) throws Exception {
  91. BinaryPartAbstractImage imagePart = BinaryPartAbstractImage.createImagePart(wordMLPackage, new File(imagePath));
  92. Inline inline = imagePart.createImageInline("Filename hint", "Alternative text", 1, 2, false);
  93. ObjectFactory factory = new ObjectFactory();
  94. P paragraph = factory.createP();
  95. R run = factory.createR();
  96. paragraph.getContent().add(run);
  97. Drawing drawing = factory.createDrawing();
  98. run.getContent().add(drawing);
  99. drawing.getAnchorOrInline().add(inline);
  100. wordMLPackage.getMainDocumentPart().addObject(paragraph);
  101. return wordMLPackage;
  102. }
  103. /**
  104. * 添加空表格
  105. *
  106. * @param wordMLPackage 操作包
  107. * @param row 行数
  108. * @param col 列数
  109. * @return WordProcessingMLPackage 操作包
  110. */
  111. public WordprocessingMLPackage addTable(WordprocessingMLPackage wordMLPackage, int row, int col) {
  112. Tbl table = TblFactory.createTable(row, col, 20000 / col);
  113. wordMLPackage.getMainDocumentPart().addObject(table);
  114. return wordMLPackage;
  115. }
  116. /**
  117. * 添加带数据表格, 数据必须是整齐的
  118. * 表头为数据的 key, 表头在第一行
  119. *
  120. * @param wordMLPackage 操作包
  121. * @param list 数据
  122. * @return WordProcessingMLPackage 操作包
  123. */
  124. public WordprocessingMLPackage addTableWithDataAndTopHeader(WordprocessingMLPackage wordMLPackage, List<Map<String, String>> list) {
  125. Set<String> keySet = new HashSet<>(list.get(0).keySet());
  126. ObjectFactory factory = Context.getWmlObjectFactory();
  127. Tbl table = TblFactory.createTable(0, 0, 20000 / list.get(0).size());
  128. // 表头
  129. Tr tableHeader = factory.createTr();
  130. keySet.forEach(e -> {
  131. Tc tableCell = factory.createTc();
  132. tableCell.getContent().add(wordMLPackage.getMainDocumentPart().createParagraphOfText(e));
  133. tableHeader.getContent().add(tableCell);
  134. });
  135. table.getContent().add(tableHeader);
  136. // 数据
  137. list.forEach(e -> {
  138. Tr tableRow = factory.createTr();
  139. keySet.forEach(item -> {
  140. Tc tableCell = factory.createTc();
  141. tableCell.getContent().add(wordMLPackage.getMainDocumentPart().createParagraphOfText(e.get(item)));
  142. tableRow.getContent().add(tableCell);
  143. });
  144. table.getContent().add(tableRow);
  145. });
  146. wordMLPackage.getMainDocumentPart().addObject(table);
  147. return wordMLPackage;
  148. }
  149. /**
  150. * 添加带数据表格, 数据必须是整齐的
  151. * 表头为数据的 key, 表头在第一列
  152. *
  153. * @param wordMLPackage 操作包
  154. * @param list 数据
  155. * @return WordProcessingMLPackage 操作包
  156. */
  157. public WordprocessingMLPackage addTableWithDataAndLeftHeader(WordprocessingMLPackage wordMLPackage, List<Map<String, String>> list) {
  158. Set<String> keySet = new HashSet<>(list.get(0).keySet());
  159. ObjectFactory factory = Context.getWmlObjectFactory();
  160. Tbl table = TblFactory.createTable(0, 0, 20000 / list.get(0).size());
  161. keySet.forEach(e -> {
  162. Tr tableRow = factory.createTr();
  163. Tc tableHeader = factory.createTc();
  164. tableHeader.getContent().add(wordMLPackage.getMainDocumentPart().createParagraphOfText(e));
  165. tableRow.getContent().add(tableHeader);
  166. list.forEach(item -> {
  167. Tc tableCell = factory.createTc();
  168. tableCell.getContent().add(wordMLPackage.getMainDocumentPart().createParagraphOfText(item.get(e)));
  169. tableRow.getContent().add(tableCell);
  170. });
  171. table.getContent().add(tableRow);
  172. });
  173. wordMLPackage.getMainDocumentPart().addObject(table);
  174. return wordMLPackage;
  175. }
  176. /**
  177. * 保存到流
  178. *
  179. * @param wordMLPackage 操作包
  180. * @param outputStream 流
  181. * @throws Docx4JException 异常
  182. */
  183. public void save(WordprocessingMLPackage wordMLPackage, OutputStream outputStream) throws Docx4JException {
  184. wordMLPackage.save(outputStream);
  185. }
  186. /**
  187. * 保存到文件,文件必须为Docx
  188. *
  189. * @param wordMLPackage 操作包
  190. * @param docxPath 文件地址
  191. * @throws Docx4JException 异常
  192. */
  193. public void save(WordprocessingMLPackage wordMLPackage, String docxPath) throws Docx4JException {
  194. wordMLPackage.save(new File(docxPath));
  195. }
  196. /**
  197. * 获取文件中所有内容
  198. *
  199. * @param obj JAXBElement子类
  200. * @param toSearch 搜索的类型
  201. * @return 符合搜索类型的所有对象
  202. */
  203. public List<Object> getAllElementFromObject(Object obj, Class<?> toSearch) {
  204. List<Object> result = new ArrayList<>();
  205. if (obj instanceof JAXBElement)
  206. obj = ((JAXBElement<?>) obj).getValue();
  207. if (obj.getClass().equals(toSearch))
  208. result.add(obj);
  209. else if (obj instanceof ContentAccessor) {
  210. List<?> children = ((ContentAccessor) obj).getContent();
  211. for (Object child : children) {
  212. result.addAll(getAllElementFromObject(child, toSearch));
  213. }
  214. }
  215. return result;
  216. }
  217. /**
  218. * 加载模板并替换数据
  219. *
  220. * @param filePath 模板文件路径
  221. * @param data 数据属性map
  222. * @return 输出文件路径
  223. * @throws Exception 异常
  224. */
  225. public String replaceData(String filePath, Map<String, String> data, String out) throws Exception {
  226. // 生成文件
  227. String target = UUID.randomUUID().toString();
  228. String outPath = out + target + ".docx";
  229. //加载模板文件并创建Word processingMLPackage对象
  230. InputStream templateInputStream = new FileInputStream(filePath);
  231. WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.load(templateInputStream);
  232. MainDocumentPart documentPart = wordMLPackage.getMainDocumentPart();
  233. VariablePrepare.prepare(wordMLPackage);
  234. // 替换属性
  235. documentPart.variableReplace(data);
  236. OutputStream os = new FileOutputStream(outPath);
  237. ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
  238. wordMLPackage.save(outputStream);
  239. outputStream.writeTo(os);
  240. os.close();
  241. outputStream.close();
  242. templateInputStream.close();
  243. return outPath;
  244. }
  245. /**
  246. * 替换模板Docx中数据和表格数据动态添加
  247. *
  248. * @param filePath 文件路径
  249. * @param data 全局替换属性Map
  250. * @param tableDataList 列表属性
  251. * @return 输出文件路径
  252. * @throws Exception 异常
  253. */
  254. public String replaceData(String filePath, Map<String, String> data, List<Map<String, Object>> tableDataList, String out) throws Exception {
  255. String target = UUID.randomUUID().toString();
  256. // 输出文件名
  257. String outPath = out + target + ".docx";
  258. // 加载模板文件并创建Word processingMLPackage对象
  259. InputStream templateInputStream = new FileInputStream(filePath);
  260. WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.load(templateInputStream);
  261. // 构造循环列表的数据
  262. ClassFinder find = new ClassFinder(Tbl.class);
  263. new TraversalUtil(wordMLPackage.getMainDocumentPart().getContent(), find);
  264. // 获取第二个表格属性
  265. if (find.results.size() > 0) {
  266. Tbl table = (Tbl) find.results.get(0);
  267. // 第二行约定为模板
  268. Tr dynamicTr = (Tr) table.getContent().get(1);
  269. // 获取模板行的xml数据
  270. String dynamicTrXml = XmlUtils.marshaltoString(dynamicTr);
  271. // 循环填充模板表格行数据
  272. for (Map<String, Object> dataMap : tableDataList) {
  273. Tr newTr = (Tr) XmlUtils.unmarshallFromTemplate(dynamicTrXml, dataMap);
  274. table.getContent().add(newTr);
  275. }
  276. // 删除模板行的占位行
  277. table.getContent().remove(1);
  278. }
  279. // 设置全局的变量替换
  280. wordMLPackage.getMainDocumentPart().variableReplace(data);
  281. Docx4J.save(wordMLPackage, new File(outPath));
  282. return outPath;
  283. }
  284. /**
  285. * 替换模板Docx中数据和表格数据动态添加
  286. *
  287. * @param filePath 文件路径
  288. * @param data 全局替换属性Map
  289. * @param tableDataList 列表属性
  290. */
  291. public void replaceData(String filePath, Map<String, String> data, List<Map<String, Object>> tableDataList, OutputStream out) throws Exception {
  292. // 加载模板文件并创建Word processingMLPackage对象
  293. InputStream templateInputStream = new FileInputStream(filePath);
  294. WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.load(templateInputStream);
  295. // 构造循环列表的数据
  296. ClassFinder find = new ClassFinder(Tbl.class);
  297. new TraversalUtil(wordMLPackage.getMainDocumentPart().getContent(), find);
  298. // 获取第一个表格属性
  299. if (find.results.size() > 0) {
  300. Tbl table = (Tbl) find.results.get(0);
  301. // 第二行约定为模板
  302. Tr dynamicTr = (Tr) table.getContent().get(1);
  303. // 获取模板行的xml数据
  304. String dynamicTrXml = XmlUtils.marshaltoString(dynamicTr);
  305. // 循环填充模板表格行数据
  306. tableDataList.forEach(e -> {
  307. try {
  308. table.getContent().add((Tr) XmlUtils.unmarshallFromTemplate(dynamicTrXml, e));
  309. } catch (JAXBException jaxbException) {
  310. jaxbException.printStackTrace();
  311. }
  312. });
  313. // 删除模板行的占位行
  314. table.getContent().remove(1);
  315. }
  316. // 设置全局的变量替换
  317. wordMLPackage.getMainDocumentPart().variableReplace(data);
  318. Docx4J.save(wordMLPackage, out);
  319. templateInputStream.close();
  320. }
  321. /**
  322. * 加载模板并替换数据
  323. *
  324. * @param filePath 模板文件路径
  325. * @param data 数据属性map
  326. */
  327. public void replaceData(String filePath, Map<String, String> data, OutputStream out) throws Exception {
  328. //加载模板文件并创建Word processingMLPackage对象
  329. InputStream templateInputStream = new FileInputStream(filePath);
  330. WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.load(templateInputStream);
  331. MainDocumentPart documentPart = wordMLPackage.getMainDocumentPart();
  332. VariablePrepare.prepare(wordMLPackage);
  333. // 替换属性
  334. documentPart.variableReplace(data);
  335. wordMLPackage.save(out);
  336. templateInputStream.close();
  337. }
  338. /**
  339. * word 转 pdf
  340. *
  341. * @param wordPath word文档地址
  342. * @return pdf地址
  343. */
  344. public String Docx2Pdf(String wordPath) {
  345. OutputStream os = null;
  346. InputStream is = null;
  347. //输出pdf文件路径和名称
  348. String pdfNoMarkPath = wordPath.substring(0, wordPath.indexOf('.')) + ".pdf";
  349. try {
  350. is = new FileInputStream(wordPath);
  351. WordprocessingMLPackage mlPackage = WordprocessingMLPackage.load(is);
  352. Mapper fontMapper = new IdentityPlusMapper();
  353. fontMapper.put("等线", PhysicalFonts.get("SimSun"));
  354. fontMapper.put("等线 Light", PhysicalFonts.get("SimSun"));
  355. fontMapper.put("隶书", PhysicalFonts.get("LiSu"));
  356. fontMapper.put("微软雅黑", PhysicalFonts.get("Microsoft Yahei"));
  357. fontMapper.put("黑体", PhysicalFonts.get("SimHei"));
  358. fontMapper.put("楷体", PhysicalFonts.get("KaiTi"));
  359. fontMapper.put("宋体", PhysicalFonts.get("SimSun"));
  360. fontMapper.put("仿宋", PhysicalFonts.get("FangSong"));
  361. fontMapper.put("新宋体", PhysicalFonts.get("NSimSun"));
  362. fontMapper.put("宋体扩展", PhysicalFonts.get("simsun-extB"));
  363. fontMapper.put("仿宋_GB2312", PhysicalFonts.get("FangSong_GB2312"));
  364. fontMapper.put("幼圆", PhysicalFonts.get("YouYuan"));
  365. fontMapper.put("华文行楷", PhysicalFonts.get("STXingkai"));
  366. fontMapper.put("华文仿宋", PhysicalFonts.get("STFangsong"));
  367. fontMapper.put("华文宋体", PhysicalFonts.get("STSong"));
  368. fontMapper.put("华文中宋", PhysicalFonts.get("STZhongsong"));
  369. fontMapper.put("华文琥珀", PhysicalFonts.get("STHupo"));
  370. fontMapper.put("华文隶书", PhysicalFonts.get("STLiti"));
  371. fontMapper.put("华文新魏", PhysicalFonts.get("STXinwei"));
  372. fontMapper.put("华文彩云", PhysicalFonts.get("STCaiyun"));
  373. fontMapper.put("方正姚体", PhysicalFonts.get("FZYaoti"));
  374. fontMapper.put("方正舒体", PhysicalFonts.get("FZShuTi"));
  375. fontMapper.put("华文细黑", PhysicalFonts.get("STXihei"));
  376. fontMapper.put("新細明體", PhysicalFonts.get("SimSun"));
  377. PhysicalFonts.put("PMingLiU", PhysicalFonts.get("SimSun")); //解决宋体(正文)和宋体(标题)的乱码问题
  378. PhysicalFonts.put("新細明體", PhysicalFonts.get("SimSun"));
  379. fontMapper.put("SimSun", PhysicalFonts.get("SimSun")); //宋体&新宋体
  380. mlPackage.setFontMapper(fontMapper);
  381. os = new FileOutputStream(pdfNoMarkPath);
  382. //docx4j docx转pdf
  383. FOSettings foSettings = Docx4J.createFOSettings();
  384. foSettings.setWmlPackage(mlPackage);
  385. Docx4J.toFO(foSettings, os, Docx4J.FLAG_EXPORT_PREFER_XSL);
  386. is.close();//关闭输入流
  387. os.close();//关闭输出流
  388. return pdfNoMarkPath;
  389. } catch (Exception e) {
  390. e.printStackTrace();
  391. try {
  392. if (is != null) {
  393. is.close();
  394. }
  395. if (os != null) {
  396. os.close();
  397. }
  398. } catch (Exception ex) {
  399. ex.printStackTrace();
  400. }
  401. } finally {
  402. File file = new File(wordPath);
  403. if (file.isFile() && file.exists()) {
  404. file.delete();
  405. }
  406. }
  407. return "";
  408. }
  409. /**
  410. * 添加水印图片
  411. *
  412. * @param inPdfPath 无水印pdf路径
  413. * @param markPath 水印图片地址
  414. * @return 生成的带水印的pdf路径
  415. */
  416. public String addTextMark(String inPdfPath, String markPath) {
  417. PdfStamper stamp = null;
  418. PdfReader reader = null;
  419. try {
  420. //输出pdf带水印文件路径和名称
  421. String outPdfMarkPath = inPdfPath.substring(0, inPdfPath.indexOf('.')) + "水印.pdf";
  422. //添加水印
  423. reader = new PdfReader(inPdfPath, "PDF".getBytes());
  424. stamp = new PdfStamper(reader, new FileOutputStream(outPdfMarkPath));
  425. PdfContentByte under;
  426. int pageSize = reader.getNumberOfPages();// 原pdf文件的总页数
  427. //水印图片
  428. Image image;
  429. if (markPath.contains(":"))
  430. image = Image.getInstance(markPath);
  431. else {
  432. ClassPathResource resource = new ClassPathResource(markPath);
  433. image = Image.getInstance(resource.getFile().getPath());
  434. }
  435. for (int i = 1; i <= pageSize; i++) {
  436. under = stamp.getOverContent(i);// 水印在之前文本下
  437. for (int j = 0; j < 4; j++) {
  438. image.setAbsolutePosition(0, j * 250 + 100);//水印位置
  439. under.addImage(image);
  440. image.setAbsolutePosition(200, j * 250 + 100);//水印位置
  441. under.addImage(image);
  442. image.setAbsolutePosition(400, j * 250 + 100);//水印位置
  443. under.addImage(image);
  444. }
  445. }
  446. stamp.close();// 关闭
  447. reader.close();//关闭
  448. return outPdfMarkPath;
  449. } catch (Exception e) {
  450. e.printStackTrace();
  451. try {
  452. if (stamp != null) {
  453. stamp.close();
  454. }
  455. if (reader != null) {
  456. reader.close();//关闭
  457. }
  458. } catch (Exception ex) {
  459. ex.printStackTrace();
  460. }
  461. } finally {
  462. //删除生成的无水印pdf
  463. File file = new File(inPdfPath);
  464. if (file.exists() && file.isFile()) {
  465. file.delete();
  466. }
  467. }
  468. return "";
  469. }
  470. }

四、水印工具类

Java 生成水印https://mp.csdn.net/mp_blog/creation/editor/121976565

https://mp.csdn.net/mp_blog/creation/editor/121976565

五、docx模板及替换结果

Word模板文档
替换后的PDF文档(未加水印)
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
使用Shiro重写Session 自定义SESSION
springboot之使用redistemplate优雅地操作redis
Spring Boot整合Quartz持久化到数据库
SpringBoot之Mybatis连接MySQL进行CRUD(注解&配置文件)(简测试版)
手把手教你整合最优雅SSM框架:SpringMVC + Spring + MyBatis
aop注解方式实现全局日志管理
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服