WSO2 ESB / EI / AM 汉化方法

一、参考文章

        最近看了文章 WSO2 ESB企业服务总线汉化源码分享和实现原理,想着能不能把 WSO2 EI 6.6.0 也给汉化了,看了这篇博客发现 ESB 和 EI 是一样的。整理了一下代码,重新写了使用百度翻译来翻译 WSO2 EI 6.6.0,同时增加了一些术语和常用词的翻译(原博客的代码使用的是谷歌翻译,该博客原源代码:updjarutils)。
        百度翻译 API :百度翻译开放平台,普通版免费,但是 QPS 只有 1,高级版每月前一百万字符免费。
        目前验证过使用该方法汉化后的 jar 包,WSO2 ESB 5.0.0、 WSO2 EI 6.6.0 和 WSO2 AM 4.0.0 可以正常运行

二、依赖

commons-codec-1.15.jar
commons-httpclient-3.1.jar
commons-logging-1.1.1.jar
fastjson-1.2.83.jar
log4j-1.2.17.jar
slf4j-api-1.7.30.jar
slf4j-log4j12-1.7.30.jar

三、代码

WSO2 EI 6.6.0 的 ui 包在 WSO2 EI 6.6.0 根目录下的 wso2ei_home\wso2\components\plugins 文件夹下

1、获取名称带有 ui 的 jar 文件列表和请求翻译 API

package com.yoodb.wso2ei;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alibaba.fastjson.JSONObject;
import com.yoodb.wso2ei.util.LoadTerminology;

/**
 * 解压jar包,读取jar包里的Resource.properties文件,读取到配置文件内容,请求百度翻译API,获取翻译后的内容
 * 把翻译后的内容更新到Resource.properties文件,然后再把解压后的文件夹压缩成jar包
 * 
 * @author mrwang
 *
 */
public class UpdJarContent {

	private static Logger log = LoggerFactory.getLogger(UpdJarContent.class);

	public JSONObject updJarContent(SignUtils sign, FileOutputStream logfile, String... proName) throws Exception {
		JSONObject tempJsonObject = new JSONObject();
		if (!isExists(sign.getJarNewPath())) {
			List<String> filePathlist = new ArrayList<String>();
			HashMap<String, String> terminology = LoadTerminology.getTerminology();
			for (int i = 0; i < proName.length; i++) {
				Decompression.uncompress(new File(sign.getJarPath()), new File(sign.getFilePath()));
				ArrayList<String> filelist = new ArrayList<String>();
				getFiles(sign.getFilePath(), filelist);
				for (String string : filelist) {
					if (string.contains(proName[i])) {
						logfile.write((string).getBytes());
						logfile.write("\r\n".getBytes());
						filePathlist.add(string);
						Properties pro = new Properties();
						InputStream in = new BufferedInputStream(new FileInputStream(string));
						pro.load(in);
						Iterator<String> it = pro.stringPropertyNames().iterator();
						FileOutputStream yfile = new FileOutputStream(string);
						FileOutputStream originalfile = new FileOutputStream(
								string.replace(proName[i], "original_file"));
						pro.store(originalfile, "file update...");
						while (it.hasNext()) {
							String key = it.next();
							String value = pro.getProperty(key);
							log.info("key-->" + key + "    value-->" + value);
							String cntext = null;
							
							if (terminology.containsKey(value.toLowerCase())) {
								cntext = terminology.get(value.toLowerCase());
							}
							else {
								try {
									if (!isEmpty(value)) {
										cntext = TranslateByBaiDuUtil.translate(value, TranslateByBaiDuUtil.ENGLISH, TranslateByBaiDuUtil.CHINA);
									}
									// Thread.sleep(1000);
								} catch (Exception e) {
									// TODO: handle exception
									log.error(e.getMessage());
									Thread.sleep(20000);
									if (!isEmpty(value)) {
										cntext = TranslateByBaiDuUtil.translate(value, TranslateByBaiDuUtil.ENGLISH, TranslateByBaiDuUtil.CHINA);
									}
								}
							}
							log.info("key-->" + key + "    value-->" + cntext);
							if (isEmpty(cntext)) {
								pro.put(key, "");
							} else {
								pro.put(key, cntext);
							}
							if(!isEmpty(value) && !isEmpty(cntext)) {
								tempJsonObject.put(value, cntext);
							}
							Thread.sleep(10);
						}
						pro.store(yfile, "file update...");
						yfile.close();
						originalfile.close();
						in.close();
					}

				}
				Compressor zc = new Compressor(sign.getJarNewPath());
				zc.setOriginalUrl(sign.getOriginalUrl());
				zc.compress(sign.getFilePath());
				log.info("恭喜修改压缩文件成功!!!!,修改文件如下:");
				for (String string : filePathlist) {
					log.info("update file path is [" + string + "]");
				}
				log.info("翻译后的值:" + tempJsonObject.toJSONString());
			}

		} else {
			log.info("jar包已经存在--->" + sign.getJarNewPath());
		}
		return tempJsonObject;
	}
	
	/**
	 * 将文件夹下所有文件存储
	 * 
	 * @param filePath
	 * @param filelist
	 */
	public static ArrayList<String> getFiles(String filePath, ArrayList<String> filelist) {
		File root = new File(filePath);
		File[] files = root.listFiles();
		for (File file : files) {
			if (file.isDirectory()) {
				getFiles(file.getAbsolutePath(), filelist);
			} else {
				filelist.add(file.getAbsolutePath());
			}
		}
		return filelist;
	}

	/**
	 * 判断字符串是否为空
	 * @param string 输入字符串
	 * @return 为空返回true,不为空返回false
	 */
	public boolean isEmpty(String string) {
		if (null == string || "".equals(string) || string.isEmpty() || string.length() == 0) {
			return true;
		} else {
			return false;
		}
	}

	/**
	 * 将文件夹下所有jar存储
	 * 
	 * @param filePath
	 */
	public static ArrayList<File> searchJarFiles(String filePath) {
		ArrayList<File> filelist = new ArrayList<File>();
		File f = new File(filePath);
		if (!f.exists()) {
			log.info(filePath + " not exists");
			return null;
		}
		File fa[] = f.listFiles();
		for (int i = 0; i < fa.length; i++) {
			File file = fa[i];
			if (file.isDirectory()) {
				log.info(file.getName() + " [目录]");
			} else {
				String path = file.getAbsolutePath();
				if (path.contains("ui_") && path.contains(".jar") && !path.contains("_1.jar")) {
					filelist.add(file);
				}
				if (path.indexOf("org.wso2.carbon.i18n") != -1) {
					filelist.add(file);
				}
			}
		}
		return filelist;
	}

	public static void createFile(String path, String fileName) {
		File f = new File(path);
		if (!f.exists()) {
			f.mkdirs();
		}
		File file = new File(f, fileName);
		if (!file.exists()) {
			try {
				file.createNewFile();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	public static boolean isExists(String jarNewPath) {
		File file = new File(jarNewPath);
		if (file.exists()) {
			return true;
		} else {
			return false;
		}
	}

}

2、解压 jar包

package com.yoodb.wso2ei;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.util.Enumeration;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 解压工具
 * 
 * @author mrwang
 */
public class Decompression {

	protected static Logger log = LoggerFactory.getLogger(Decompression.class);

	@SuppressWarnings("resource")
	public static void uncompress(File jarFile, File tarDir) throws IOException {
		JarFile jfInst = new JarFile(jarFile);
		Enumeration<JarEntry> enumEntry = jfInst.entries();
		while (enumEntry.hasMoreElements()) {
			JarEntry jarEntry = enumEntry.nextElement();
			File tarFile = new File(tarDir, jarEntry.getName());
			/*
			 * if(jarEntry.getName().contains("META-INF")){ File miFile = new File(tarDir,
			 * "META-INF"); if(!miFile.exists()){ miFile.mkdirs(); }
			 * 
			 * }
			 */
			log.info("原文件路径 -->" + tarFile.getPath());
			String path = tarFile.getPath().substring(0, tarFile.getPath().lastIndexOf("\\") + 1);
			log.info("原文件所属目录 -->" + tarFile.getPath());
			File file = new File(path);
			if ((!file.exists() && !file.isDirectory())) {
				file.mkdirs();
			}
			makeFile(jarEntry, tarFile);
			if (jarEntry.isDirectory()) {
				continue;
			}
			FileChannel fileChannel = new FileOutputStream(tarFile).getChannel();
			InputStream ins = jfInst.getInputStream(jarEntry);
			transferStream(ins, fileChannel);
		}
	}

	/**
	 * 流交换操作
	 * 
	 * @param ins
	 *            输入流
	 * @param channel
	 *            输出流
	 */
	private static void transferStream(InputStream ins, FileChannel channel) {
		ByteBuffer byteBuffer = ByteBuffer.allocate(1024 * 10);
		ReadableByteChannel rbcInst = Channels.newChannel(ins);
		try {
			while (-1 != (rbcInst.read(byteBuffer))) {
				byteBuffer.flip();
				channel.write(byteBuffer);
				byteBuffer.clear();
			}
		} catch (IOException ioe) {
			ioe.printStackTrace();
		} finally {
			if (null != rbcInst) {
				try {
					rbcInst.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			if (null != channel) {
				try {
					channel.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

	/**
	 * 打印jar文件内容信息
	 * 
	 * @param file
	 *            jar文件
	 */
	public static void printJarEntry(File file) {
		JarFile jfInst = null;
		;
		try {
			jfInst = new JarFile(file);
		} catch (IOException e) {
			e.printStackTrace();
		}
		Enumeration<JarEntry> enumEntry = jfInst.entries();
		while (enumEntry.hasMoreElements()) {
			log.info(enumEntry.nextElement().toString());
		}
	}

	/**
	 * 创建文件
	 * 
	 * @param jarEntry
	 *            jar实体
	 * @param fileInst
	 *            文件实体
	 * @throws IOException
	 *             抛出异常
	 */
	public static void makeFile(JarEntry jarEntry, File fileInst) {
		if (!fileInst.exists()) {
			if (jarEntry.isDirectory()) {
				fileInst.mkdirs();
			} else {
				try {
					fileInst.createNewFile();
				} catch (IOException e) {
					log.error("创建文件失败>>>".concat(fileInst.getPath()));
				}
			}
		}
	}

	public static void main(String[] args) {
		File jarFile = new File(
				"D:\\WSO2 EI 6.6.0\\wso2ei-6.6.0\\wso2\\components\\plugins\\org.wso2.carbon.service.mgt.ui_4.9.15.jar");
		File targetDir = new File("D:\\WSO2 EI 6.6.0\\org.wso2.carbon.service.mgt.ui_4.9.15");
		try {
			Decompression.uncompress(jarFile, targetDir);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

3、请求百度翻译 API

package com.yoodb.wso2ei;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.UUID;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alibaba.fastjson.JSONObject;

/**
 * 翻译帮助工具 - 百度
 * 
 * @author wxhntmy
 *
 */
public class TranslateByBaiDuUtil {

	private static Logger log = LoggerFactory.getLogger(TranslateByBaiDuUtil.class);

	protected static final String URL_TEMPLATE = "https://fanyi-api.baidu.com/api/trans/vip/translate";

	protected static final String APP_ID = "";
	protected static final String APP_KEY = "";

	protected static final String ENCODING = "UTF-8";

	public static final String AUTO = "auto";

	public static final String CHINA = "zh";

	public static final String ENGLISH = "en";

	public static final String JAPAN = "ja";

	/**
	 * 百度自动判断识别语言体系
	 * 
	 * @param text
	 * @param target_lang
	 * @return
	 * @throws Exception
	 */
	public static String translate(final String text, final String target_lang) throws Exception {
		return translate(text, AUTO, target_lang);
	}

	/**
	 * 获取md5加密的字符串
	 * 
	 * @param string 输入字符串
	 * @return 加密后的字符串
	 */
	private static String getMd5(String string) {

		MessageDigest md5 = null;
		try {
			md5 = MessageDigest.getInstance("MD5");
		} catch (NoSuchAlgorithmException e) {
			// TODO 自动生成的 catch 块
			e.printStackTrace();
		}
		byte[] md5Bytes = null;
		try {
			md5Bytes = md5.digest(string.getBytes("UTF8"));
		} catch (UnsupportedEncodingException e) {
			// TODO 自动生成的 catch 块
			e.printStackTrace();
		}
		String md5Str = "";
		for (int i = 0; i < md5Bytes.length; i++) {
			md5Str += Integer.toHexString(md5Bytes[i] | 0xFFFFFF00).substring(6);
		}
		return md5Str;
	}

	/**
	 * 百度翻译
	 * 
	 * @param text
	 * @param src_lang
	 * @param target_lang
	 * @return
	 * @throws Exception
	 */
	public static String translate(final String text, final String src_lang, final String target_lang)
			throws Exception {

		String salt = UUID.randomUUID().toString().replace("-", "").substring(0, 8);
		String qString = URLEncoder.encode(text, ENCODING).replace("+", "%20");
		String sign = APP_ID + text + salt + APP_KEY;
		sign = getMd5(sign);
		String urlString = "http://api.fanyi.baidu.com/api/trans/vip/translate?q=" + qString + "&from=" + src_lang
				+ "&to=" + target_lang + "&appid=" + APP_ID + "&salt=" + salt + "&sign=" + sign;

		//log.info("url:---->" + urlString);

		String resultJson = RestMock.doGet(urlString);
		JSONObject jsonObject = JSONObject.parseObject(resultJson);

		//log.info("jsonObject:---->" + jsonObject.toJSONString());

		JSONObject trans_result = jsonObject.containsKey("trans_result")?jsonObject.getJSONArray("trans_result").getJSONObject(0):new JSONObject();

		String result = trans_result.containsKey("dst")?trans_result.getString("dst"):"";

		log.info("dst:---->" + result);
		return result;
	}

	/**
	 * 百度翻译: 英文-->简体中文
	 * 
	 * @param text
	 * @return
	 * @throws Exception
	 */
	public static String en2tw(final String text) throws Exception {
		return translate(text, ENGLISH, CHINA);
	}

	/**
	 * 百度翻译: 简体中文-->英文
	 * 
	 * @param text
	 * @return
	 * @throws Exception
	 */
	public static String tw2en(final String text) throws Exception {
		return translate(text, CHINA, ENGLISH);
	}

	/**
	 * 百度翻译: 日文-->简体中文
	 * 
	 * @param text
	 * @return
	 * @throws Exception
	 */
	public static String jp2tw(final String text) throws Exception {
		return translate(text, JAPAN, CHINA);
	}

	/**
	 * 百度翻译: 简体中文-->日文
	 * 
	 * @param text
	 * @return
	 * @throws Exception
	 */
	public static String tw2jp(final String text) throws Exception {
		return translate(text, CHINA, JAPAN);
	}

}

4、把文件夹压缩成 jar 包

package com.yoodb.wso2ei;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.zip.CRC32;
import java.util.zip.CheckedOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 压缩文件工具
 * 
 * @author mrwang
 *
 */
public class Compressor {

	private static Logger log = LoggerFactory.getLogger(Compressor.class);

	private static final int BUFFER = 8192;

	private File fileName;

	private String originalUrl;

	public Compressor(String pathName) {
		fileName = new File(pathName);
	}

	public void compress(String... pathName) {
		ZipOutputStream out = null;
		try {
			FileOutputStream fileOutputStream = new FileOutputStream(fileName);
			CheckedOutputStream cos = new CheckedOutputStream(fileOutputStream, new CRC32());
			out = new ZipOutputStream(cos);
			String basedir = "";
			for (int i = 0; i < pathName.length; i++) {
				compress(new File(pathName[i]), out, basedir);
			}
			out.close();
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	public void compress(String srcPathName) {
		File file = new File(srcPathName);
		if (!file.exists())
			throw new RuntimeException(srcPathName + "不存在!");
		try {
			FileOutputStream fileOutputStream = new FileOutputStream(fileName);
			CheckedOutputStream cos = new CheckedOutputStream(fileOutputStream, new CRC32());
			ZipOutputStream out = new ZipOutputStream(cos);
			String basedir = "";
			compress(file, out, basedir);
			out.close();
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	private void compress(File file, ZipOutputStream out, String basedir) {
		/* 判断是目录还是文件 */
		if (file.isDirectory()) {
			this.compressDirectory(file, out, basedir);
		} else {
			this.compressFile(file, out, basedir);
		}
	}

	/**
	 * 压缩目录
	 * 
	 * @param dir
	 * @param out
	 * @param basedir
	 */
	private void compressDirectory(File dir, ZipOutputStream out, String basedir) {
		if (!dir.exists())
			return;

		File[] files = dir.listFiles();
		for (int i = 0; i < files.length; i++) {
			/* 递归 */
			compress(files[i], out, basedir + dir.getName() + "/");
		}
	}

	/**
	 * 压缩文件
	 * 
	 * @param file
	 * @param out
	 * @param basedir
	 */
	private void compressFile(File file, ZipOutputStream out, String basedir) {
		if (!file.exists()) {
			return;
		}
		try {
			BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
			String filePath = (basedir + file.getName()).replaceAll(getOriginalUrl() + "/", "");
			//log.info("压缩文件:" + filePath);
			ZipEntry entry = new ZipEntry(filePath);
			out.putNextEntry(entry);
			int count;
			byte data[] = new byte[BUFFER];
			while ((count = bis.read(data, 0, BUFFER)) != -1) {
				out.write(data, 0, count);
			}
			bis.close();
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	public static void main(String[] args) {
		Compressor zc = new Compressor("E:\\org.wso2.carbon.service.mgt.ui_4.7.1.jar");
		zc.compress("E:\\org.wso2.carbon.service.mgt.ui_4.7.0");
	}

	public String getOriginalUrl() {
		return originalUrl;
	}

	public void setOriginalUrl(String originalUrl) {
		this.originalUrl = originalUrl;
	}

}

5、Main 方法

package com.yoodb.wso2ei;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.ArrayList;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.yoodb.wso2ei.util.LoadLog4jProperties;
import com.yoodb.wso2ei.util.LogLevel;

public class Main {

	private static Logger log = LoggerFactory.getLogger(Main.class);

	/**
	 * 
	 * @param args
	 * @throws Exception
	 */
	public static void main(String[] args) throws Exception {

		LoadLog4jProperties.init(false, LogLevel.info);
		Long t1 = System.currentTimeMillis();
		try {
			start();
		} catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
		}
		Long t2 = System.currentTimeMillis();
		log.info("汉化成功,耗时:{} 秒", (t2-t1)/1000.00);
	}
	
	public static void start() throws Exception {
		String infoPath = "D:\\WSO2_ESB\\be assimilated by the Chinese";
		String logFilePath = "info.log";
		UpdJarContent.createFile(infoPath, logFilePath);
		FileOutputStream logfile = new FileOutputStream(infoPath + logFilePath);
		String proName = "Resources";
		ArrayList<File> filelist = UpdJarContent
				.searchJarFiles("D:\\WSO2_ESB\\wso2ei-6.6.0-en\\wso2\\components\\plugins");
		log.info("文件列表大小:" + filelist.size());
		
		JSONArray baiduJsonArray = new JSONArray();
		
		for (File file : filelist) {
			UpdJarContent ut = new UpdJarContent();
			SignUtils ss = new SignUtils();
			String path = file.getAbsolutePath();
			ss.setJarPath(path);
			logfile.write(("------" + path + "------").getBytes());
			logfile.write("\r\n".getBytes());
			log.info("需要解压的压缩包路径-->" + path);
			String fileName = file.getName().replace(".jar", "");
			ss.setOriginalUrl(fileName);
			log.info("用于压缩包删除前缀目录的名称-->" + fileName);
			String filePath = infoPath + "\\file\\" + fileName;
			ss.setFilePath(filePath);
			log.info("解压后的文件夹目录路径-->" + filePath);
			String newFilePath = infoPath + "\\jar\\" + file.getName();
			ss.setJarNewPath(newFilePath);
			log.info("新打包后的压缩包路径-->" + newFilePath);
			JSONObject tempJsonObject = ut.updJarContent(ss, logfile, proName);
			logfile.write(("------" + path + "------").getBytes());
			logfile.write("\r\n".getBytes());
			// DeleteDirectory.deleteDir(new File(filePath));
			// log.info("删除临时解压文件 -->" + filePath + "<-- 成功!");
			
			baiduJsonArray.add(tempJsonObject);
		}
		logfile.close();
		JSONObject testJsonObject = new JSONObject();
		for (int i = 0; i < baiduJsonArray.size(); i++) {
			JSONObject indexJsonObject = baiduJsonArray.getJSONObject(i);
			for (String key:indexJsonObject.keySet()) {
				testJsonObject.put(key, indexJsonObject.getString(key));
			}
		}
		writeFile(testJsonObject.toJSONString());
	}
	
	/**
	 * 写入文件
	 */
	public static void writeFile(String content) {
		File pathFile = new File("1.txt");
		String path = pathFile.getAbsolutePath().replace("1.txt", "");
		log.info(path);
		File file = new File(path + "src\\com\\yoodb\\blog\\util\\baiduApiTranlate.json");
		Writer writer = null;
		try {
			if (!file.exists()) {
				file.createNewFile();
			}
			writer = new OutputStreamWriter(new FileOutputStream(file), "UTF-8");
			writer.write(content + "\r\n");
		} catch (IOException e) {
			// TODO 自动生成的 catch 块
			e.printStackTrace();
		}
		finally {
			try {
				if (null != writer) {
					writer.close();
				}
			} catch (Exception e2) {
				// TODO: handle exception
				e2.printStackTrace();
			}
		}
		
	}
}

6、完整代码

wso2-sinicization

四、运行测试

把生成的 jar 包全部复制到 WSO2 EI 6.6.0 根目录下的 wso2ei_home\wso2\components\plugins 文件夹下,并覆盖原来的 jar 包。运行 WSO2 EI 6.6.0
在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

菠萝蚊鸭

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值