3 Commits

Author SHA1 Message Date
aa5ad064a8 feat: 添加微软翻译接口 2026-01-23 22:51:00 +08:00
36bd572e38 fix: 更新百度翻译接口;一些小修改 2026-01-23 22:50:42 +08:00
2b8f30b72d feat: 更新依赖 2026-01-23 22:40:05 +08:00
14 changed files with 201 additions and 78 deletions

38
pom.xml
View File

@ -30,15 +30,16 @@
<junit.version>5.11.0</junit.version>
<javafx.version>21.0.4</javafx.version>
<slf4j.version>2.0.16</slf4j.version>
<logback.version>1.5.7</logback.version>
<slf4j.version>2.0.17</slf4j.version>
<logback.version>1.5.25</logback.version>
<fastjson.version>2.0.52</fastjson.version>
<common-lang3.version>3.16.0</common-lang3.version>
<common-io.version>2.17.0</common-io.version>
<common-exec.version>1.4.0</common-exec.version>
<lombok.version>1.18.32</lombok.version>
<jackson.version>2.15.4</jackson.version>
<ikonli.version>12.3.1</ikonli.version>
<common-lang3.version>3.20.0</common-lang3.version>
<common-io.version>2.21.0</common-io.version>
<common-exec.version>1.6.0</common-exec.version>
<lombok.version>1.18.42</lombok.version>
<jackson.version>3.0.3</jackson.version>
<ikonli.version>12.4.0</ikonli.version>
<jjwt.version>0.13.0</jjwt.version>
</properties>
<dependencies>
@ -66,11 +67,13 @@
<version>2.0.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/ch.qos.logback/logback-classic -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
@ -124,10 +127,12 @@
</dependency>
<!-- jackson -->
<!-- Source: https://mvnrepository.com/artifact/tools.jackson.dataformat/jackson-dataformat-yaml -->
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<groupId>tools.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
<version>${jackson.version}</version>
<scope>compile</scope>
</dependency>
<!-- Hutool -->
@ -135,7 +140,7 @@
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
<version>5.8.34</version>
<version>5.8.43</version>
</dependency>
<!-- https://kordamp.org/ikonli/ -->
@ -151,6 +156,19 @@
<groupId>org.kordamp.ikonli</groupId>
<artifactId>ikonli-feather-pack</artifactId>
</dependency>
<!-- jwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>${jjwt.version}</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>${jjwt.version}</version>
<scope>runtime</scope>
</dependency>
</dependencies>
<dependencyManagement>

View File

@ -12,14 +12,15 @@ import cn.octopusyan.dmt.model.Translate;
import cn.octopusyan.dmt.model.UpgradeConfig;
import cn.octopusyan.dmt.translate.TranslateApi;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import javafx.application.Platform;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import tools.jackson.core.JacksonException;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.dataformat.yaml.YAMLMapper;
import java.io.File;
import java.io.IOException;
@ -38,7 +39,12 @@ import java.util.stream.Collectors;
*/
public class ConfigManager {
private static final Logger logger = LoggerFactory.getLogger(ConfigManager.class);
public static ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory());
private static final ObjectMapper objectMapper = YAMLMapper.builder()
//对象的所有字段全部列入序列化
.changeDefaultPropertyInclusion(incl -> incl.withValueInclusion(JsonInclude.Include.NON_NULL))
.changeDefaultPropertyInclusion(incl -> incl.withContentInclusion(JsonInclude.Include.NON_NULL))
.build();
public static final UpgradeConfig upgradeConfig = new UpgradeConfig();
public static final String DEFAULT_THEME = new PrimerLight().getName();
@ -53,11 +59,6 @@ public class ConfigManager {
private static ConfigModel configModel;
static {
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
}
public static void load() {
configModel = loadConfig(Constants.CONFIG_FILE_PATH, ConfigModel.class);
if (configModel == null)
@ -89,7 +90,7 @@ public class ConfigManager {
public static void save() {
try {
objectMapper.writeValue(new File(Constants.CONFIG_FILE_PATH), configModel);
} catch (IOException e) {
} catch (JacksonException e) {
logger.error("save config error", e);
}
}

View File

@ -4,7 +4,7 @@ import cn.octopusyan.dmt.common.enums.ProxySetup;
import cn.octopusyan.dmt.common.manager.http.response.DownloadBodyHandler;
import cn.octopusyan.dmt.common.util.JsonUtil;
import cn.octopusyan.dmt.model.ProxyInfo;
import com.fasterxml.jackson.databind.JsonNode;
import tools.jackson.databind.JsonNode;
import lombok.extern.slf4j.Slf4j;
import java.io.File;
@ -111,8 +111,15 @@ public class HttpUtil {
}
public String postForm(String uri, JsonNode header, JsonNode param) throws IOException, InterruptedException {
return postForm(uri, header, param, null);
}
public String postForm(String uri, JsonNode header, JsonNode param, JsonNode body) throws IOException, InterruptedException {
HttpRequest.Builder request = getRequest(uri + createFormParams(param), header)
.POST(HttpRequest.BodyPublishers.noBody());
.POST(body == null ?
HttpRequest.BodyPublishers.noBody() :
HttpRequest.BodyPublishers.ofString(JsonUtil.toJsonString(body))
);
HttpResponse<String> response = httpClient.send(request.build(), HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));
return response.body();
@ -172,8 +179,8 @@ public class HttpUtil {
for (Map.Entry<String, JsonNode> property : params.properties()) {
String key = property.getKey();
JsonNode value = params.get(key);
if (value.isTextual()) {
String value_ = URLEncoder.encode(String.valueOf(value.asText()), StandardCharsets.UTF_8);
if (value.isString()) {
String value_ = URLEncoder.encode(String.valueOf(value.asString()), StandardCharsets.UTF_8);
formParams.append("&").append(key).append("=").append(value_);
} else if (value.isNumber()) {
formParams.append("&").append(key).append("=").append(value);

View File

@ -1,18 +1,20 @@
package cn.octopusyan.dmt.common.util;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import tools.jackson.core.JacksonException;
import tools.jackson.core.type.TypeReference;
import tools.jackson.databind.DeserializationFeature;
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.SerializationFeature;
import tools.jackson.databind.cfg.DateTimeFeature;
import tools.jackson.databind.json.JsonMapper;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.TimeZone;
/**
* Jackson 封装工具类
@ -21,25 +23,26 @@ import java.text.SimpleDateFormat;
*/
public class JsonUtil {
private static final Logger log = LoggerFactory.getLogger(JsonUtil.class);
private static final ObjectMapper objectMapper = new ObjectMapper();
/**
* 时间日期格式
*/
private static final String STANDARD_FORMAT = "yyyy-MM-dd HH:mm:ss";
static {
//对象的所有字段全部列入序列化
objectMapper.setSerializationInclusion(JsonInclude.Include.ALWAYS);
//取消默认转换timestamps形式
objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
//忽略空Bean转json的错误
objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
//所有的日期格式都统一为以下的格式即yyyy-MM-dd HH:mm:ss
objectMapper.setDateFormat(new SimpleDateFormat(STANDARD_FORMAT));
//忽略 在json字符串中存在但在java对象中不存在对应属性的情况。防止错误
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
private static final ObjectMapper objectMapper = JsonMapper.builder()
//对象的所有字段全部列入序列化
.changeDefaultPropertyInclusion(incl -> incl.withValueInclusion(JsonInclude.Include.NON_NULL))
.changeDefaultPropertyInclusion(incl -> incl.withContentInclusion(JsonInclude.Include.NON_NULL))
//取消默认转换timestamps形式
.disable(DateTimeFeature.WRITE_DATES_AS_TIMESTAMPS)
//忽略空Bean转json的错误
.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)
//忽略 在json字符串中存在但在java对象中不存在对应属性的情况。防止错误
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
//所有的日期格式都统一为以下的格式即yyyy-MM-dd HH:mm:ss
.defaultDateFormat(new SimpleDateFormat(STANDARD_FORMAT))
.defaultTimeZone(TimeZone.getDefault())
.build();
/**
* Json字符串 转 JavaBean
@ -53,7 +56,7 @@ public class JsonUtil {
T t = null;
try {
t = objectMapper.readValue(jsonString, clazz);
} catch (JsonProcessingException e) {
} catch (JacksonException e) {
log.error("失败:{}", e.getMessage());
}
return t;
@ -71,7 +74,7 @@ public class JsonUtil {
T t = null;
try {
t = objectMapper.readValue(file, clazz);
} catch (IOException e) {
} catch (JacksonException e) {
log.error("失败:{}", e.getMessage());
}
return t;
@ -89,7 +92,7 @@ public class JsonUtil {
T t = null;
try {
t = objectMapper.readValue(jsonArray, reference);
} catch (JsonProcessingException e) {
} catch (JacksonException e) {
log.error("失败:{}", e.getMessage());
}
return t;
@ -106,7 +109,7 @@ public class JsonUtil {
String jsonString = null;
try {
jsonString = objectMapper.writeValueAsString(object);
} catch (JsonProcessingException e) {
} catch (JacksonException e) {
log.error("失败:{}", e.getMessage());
}
return jsonString;
@ -122,7 +125,7 @@ public class JsonUtil {
byte[] bytes = null;
try {
bytes = objectMapper.writeValueAsBytes(object);
} catch (JsonProcessingException e) {
} catch (JacksonException e) {
log.error("失败:{}", e.getMessage());
}
return bytes;
@ -137,7 +140,7 @@ public class JsonUtil {
public static void objectToFile(File file, Object object) {
try {
objectMapper.writeValue(file, object);
} catch (Exception e) {
} catch (JacksonException e) {
log.error("失败:{}", e.getMessage());
}
}
@ -153,7 +156,7 @@ public class JsonUtil {
JsonNode jsonNode = null;
try {
jsonNode = objectMapper.readTree(jsonString);
} catch (JsonProcessingException e) {
} catch (JacksonException e) {
log.error("失败:{}", e.getMessage());
}
return jsonNode;
@ -179,7 +182,7 @@ public class JsonUtil {
String jsonString = null;
try {
jsonString = objectMapper.writeValueAsString(jsonNode);
} catch (JsonProcessingException e) {
} catch (JacksonException e) {
log.error("失败:{}", e.getMessage());
}
return jsonString;

View File

@ -6,8 +6,8 @@ import cn.octopusyan.dmt.common.util.JsonUtil;
import cn.octopusyan.dmt.model.UpgradeConfig;
import cn.octopusyan.dmt.task.base.BaseTask;
import cn.octopusyan.dmt.task.listener.DefaultTaskListener;
import com.fasterxml.jackson.databind.JsonNode;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Strings;
import tools.jackson.databind.JsonNode;
/**
* 检查更新任务
@ -28,10 +28,10 @@ public class UpgradeTask extends BaseTask<UpgradeTask.UpgradeListener> {
JsonNode response = JsonUtil.parseJsonObject(responseStr);
// TODO 校验返回内容
String newVersion = response.get("tag_name").asText();
String newVersion = response.get("tag_name").asString();
if (listener != null)
listener.onChecked(!StringUtils.equals(upgradeConfig.getVersion(), newVersion), newVersion);
listener.onChecked(!Strings.CS.equals(upgradeConfig.getVersion(), newVersion), newVersion);
}
/**

View File

@ -12,6 +12,7 @@ import lombok.Getter;
public enum TranslateApi {
FREE_BAIDU("free_baidu", "百度", false),
FREE_GOOGLE("free_google", "谷歌", false),
FREE_MICROSOFT("free_microsoft", "微软", false),
BAIDU("baidu", "百度(需认证)", true),
;

View File

@ -3,10 +3,7 @@ package cn.octopusyan.dmt.translate.factory;
import cn.octopusyan.dmt.model.WordItem;
import cn.octopusyan.dmt.translate.DelayWord;
import cn.octopusyan.dmt.translate.TranslateApi;
import cn.octopusyan.dmt.translate.processor.AbstractTranslateProcessor;
import cn.octopusyan.dmt.translate.processor.BaiduTranslateProcessor;
import cn.octopusyan.dmt.translate.processor.FreeBaiduTranslateProcessor;
import cn.octopusyan.dmt.translate.processor.FreeGoogleTranslateProcessor;
import cn.octopusyan.dmt.translate.processor.*;
import lombok.extern.slf4j.Slf4j;
import java.util.*;
@ -38,6 +35,7 @@ public class TranslateFactoryImpl implements TranslateFactory {
private void initProcessor() {
processorList.addAll(Arrays.asList(
new FreeGoogleTranslateProcessor(),
new FreeMicrosoftTranslateProcessor(),
new FreeBaiduTranslateProcessor(),
new BaiduTranslateProcessor()
));

View File

@ -3,7 +3,7 @@ package cn.octopusyan.dmt.translate.processor;
import cn.octopusyan.dmt.common.util.JsonUtil;
import cn.octopusyan.dmt.translate.ApiKey;
import cn.octopusyan.dmt.translate.TranslateApi;
import com.fasterxml.jackson.databind.JsonNode;
import tools.jackson.databind.JsonNode;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
@ -59,7 +59,7 @@ public class BaiduTranslateProcessor extends AbstractTranslateProcessor {
throw new RuntimeException(String.valueOf(errorMsg));
}
return json.get("trans_result").get(0).get("dst").asText();
return json.get("trans_result").get(0).get("dst").asString();
}
private String getSign(String appid, String q, String salt) {

View File

@ -3,7 +3,7 @@ package cn.octopusyan.dmt.translate.processor;
import cn.octopusyan.dmt.common.manager.http.CookieManager;
import cn.octopusyan.dmt.common.util.JsonUtil;
import cn.octopusyan.dmt.translate.TranslateApi;
import com.fasterxml.jackson.databind.JsonNode;
import tools.jackson.databind.JsonNode;
import java.io.IOException;
import java.net.HttpCookie;
@ -23,6 +23,7 @@ public class FreeBaiduTranslateProcessor extends AbstractTranslateProcessor {
static {
header.put("Origin", "https://fanyi.baidu.com");
header.put("acs-token", "");
header.put("Referer", "https://fanyi.baidu.com");
header.put("User-Agent", "Apifox/1.0.0 (https://apifox.com)");
header.put("Accept","*/*");
@ -56,7 +57,7 @@ public class FreeBaiduTranslateProcessor extends AbstractTranslateProcessor {
JsonNode json = JsonUtil.parseJsonObject(resp);
if (!json.has("data")) {
String errorMsg = json.get("errmsg").asText();
String errorMsg = json.get("errmsg").asString();
if("访问出现异常,请刷新后重试!".equals(errorMsg) && !header.containsKey("Cookie")) {
checkCookie();
return customTranslate(source);
@ -64,14 +65,12 @@ public class FreeBaiduTranslateProcessor extends AbstractTranslateProcessor {
throw new RuntimeException(errorMsg);
}
return json.get("data").get(0).get("dst").asText();
return json.get("data").get(0).get("dst").asString();
}
private void checkCookie() throws IOException, InterruptedException {
// 短时大量请求会被ban需要添加验证cookie
if (header.containsKey("Cookie")) return;
List<HttpCookie> cookieList = CookieManager.getStore().get(URI.create("https://baidu.com"));
boolean noneMatch = cookieList.stream()
.filter(cookie -> "ab_sr".equals(cookie.getName()) || "BAIDUID".equals(cookie.getName()))

View File

@ -3,7 +3,7 @@ package cn.octopusyan.dmt.translate.processor;
import cn.octopusyan.dmt.common.util.JsonUtil;
import cn.octopusyan.dmt.translate.TranslateApi;
import com.fasterxml.jackson.databind.JsonNode;
import tools.jackson.databind.JsonNode;
import java.io.IOException;
import java.util.HashMap;
@ -34,22 +34,28 @@ public class FreeGoogleTranslateProcessor extends AbstractTranslateProcessor {
@Override
public String customTranslate(String source) throws IOException, InterruptedException {
Map<String, Object> header = new HashMap<>();
header.put("User-Agent", "Mozilla/5.0 (Windows NT 11.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36 Edg/130.0.0.0");
header.put("Referer", "https://translate.google.com/");
header.put("Host", "translate.googleapis.com");
Map<String, Object> form = new HashMap<>();
form.put("client", "gtx");
form.put("dt", "t");
form.put("sl", "auto");
form.put("tl", "zh-CN");
form.put("q", source);
Map<String, Object> header = new HashMap<>();
Map<String, Object> body = new HashMap<>();
body.put("q", source);
StringBuilder retStr = new StringBuilder();
// TODO 短时大量请求会被ban需要浏览器验证添加cookie
String resp = httpUtil.get(url(), JsonUtil.parseJsonObject(header), JsonUtil.parseJsonObject(form));
String resp = httpUtil.postForm(url(), JsonUtil.parseJsonObject(header), JsonUtil.parseJsonObject(form), JsonUtil.parseJsonObject(body));
JsonNode json = JsonUtil.parseJsonObject(resp);
for (JsonNode o : json.get(0)) {
retStr.append(o.get(0).asText());
retStr.append(o.get(0).asString());
}
return retStr.toString();

View File

@ -0,0 +1,89 @@
package cn.octopusyan.dmt.translate.processor;
import cn.hutool.core.date.DateUtil;
import cn.octopusyan.dmt.common.util.JsonUtil;
import cn.octopusyan.dmt.translate.TranslateApi;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import tools.jackson.databind.JsonNode;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* 微软 免费翻译接口
*
* @author octopus_yan@foxmail.com
*/
public class FreeMicrosoftTranslateProcessor extends AbstractTranslateProcessor {
private String token;
public FreeMicrosoftTranslateProcessor() {
super(TranslateApi.FREE_MICROSOFT);
}
@Override
public String url() {
return "https://api.cognitive.microsofttranslator.com/translate";
}
/**
* 翻译处理
*
* @param source 待翻译单词
* @return 翻译结果
*/
@Override
public String customTranslate(String source) throws IOException, InterruptedException {
Map<String, Object> form = new HashMap<>();
form.put("api-version", "3.0");
form.put("to", "zh-Hans");
form.put("textType", "plain");
Map<String, Object> header = new HashMap<>();
header.put("Authorization", STR."Bearer \{oauth()}");
String body = STR."""
[
{
"Text": "\{source}"
}
]
""";
StringBuilder retStr = new StringBuilder();
String resp = httpUtil.postForm(url(), JsonUtil.parseJsonObject(header), JsonUtil.parseJsonObject(form), JsonUtil.parseJsonObject(body));
JsonNode json = JsonUtil.parseJsonObject(resp);
for (JsonNode o : json.get(0)) {
retStr.append(o.get(0).asString());
}
return retStr.toString();
}
private synchronized String oauth() throws IOException, InterruptedException {
if (token == null) {
token = httpUtil.get("https://edge.microsoft.com/translate/auth", null, null);
} else {
// 解析,对应的签名或者加密方式
Claims claims = Jwts.parser()
// .verifyWith(key) // 指定用于验证的密钥
// .decryptWith(key) // 加密方式,指定用于解密的密钥。
.build()
.parseSignedClaims(token) // 签名方式
// .parseEncryptedClaims(token) // 加密方式,对 JWT 进行解密,并获取其声明部分。
.getPayload(); // 获取解密后的声明claims内容。
if(DateUtil.compare(DateUtil.date(), claims.getExpiration()) > 0) {
token = null;
return oauth();
}
}
return token;
}
}

View File

@ -37,8 +37,8 @@ public class PBOUtil {
private static final ProcessesUtil processesUtil = ProcessesUtil.init(Constants.BIN_DIR_PATH);
private static final String UNPACK_COMMAND = STR."\"\{Constants.PBOC_FILE}\" unpack -o \"\{Constants.TMP_DIR_PATH}\" {}";
private static final String PACK_COMMAND = STR."\"\{Constants.PBOC_FILE}\" pack -o {} {}";
private static final String UNPACK_COMMAND = STR."\"\{Constants.PBOC_FILE}\" unpack {} -o \"\{Constants.TMP_DIR_PATH}\"";
private static final String PACK_COMMAND = STR."\"\{Constants.PBOC_FILE}\" pack {} -o {}";
private static final String CFG_COMMAND = STR."\"\{Constants.CFG_CONVERT_FILE}\" {} -dst {} {}";
private static final String FILE_NAME_STRING_TABLE = "stringtable.csv";
@ -122,7 +122,7 @@ public class PBOUtil {
FileUtils.deleteQuietly(packFile);
}
String command = ProcessesUtil.format(PACK_COMMAND, Constants.TMP_DIR_PATH, unpackPath);
String command = ProcessesUtil.format(PACK_COMMAND, unpackPath, Constants.TMP_DIR_PATH);
consoleLog.debug(STR."pack command ==> [\{command}]");
boolean exec = processesUtil.exec(command);

View File

@ -12,16 +12,17 @@ module cn.octopusyan.dmt {
requires static lombok;
requires atlantafx.base;
requires com.fasterxml.jackson.annotation;
requires com.fasterxml.jackson.databind;
requires com.fasterxml.jackson.dataformat.yaml;
requires tools.jackson.databind;
requires tools.jackson.dataformat.yaml;
requires java.prefs;
requires org.kordamp.ikonli.javafx;
requires org.kordamp.ikonli.feather;
requires java.management;
requires cn.hutool.core;
requires jjwt.api;
exports cn.octopusyan.dmt;
exports cn.octopusyan.dmt.model to com.fasterxml.jackson.databind;
exports cn.octopusyan.dmt.model to tools.jackson.databind;
opens cn.octopusyan.dmt.model to javafx.base;
opens cn.octopusyan.dmt.common.base to javafx.fxml;
opens cn.octopusyan.dmt.controller to javafx.fxml;