Browse Source

add baidu translation module

fengchang_fight 1 year ago
parent
commit
fa698e58d6

+ 3 - 0
hichina-main-back/build.gradle

@@ -65,6 +65,9 @@ dependencies {
 	// https://mvnrepository.com/artifact/com.squareup.okhttp/okhttp
 	implementation 'com.squareup.okhttp:okhttp:2.7.5'
 
+	// https://mvnrepository.com/artifact/org.jsoup/jsoup
+	implementation 'org.jsoup:jsoup:1.16.1'
+
 }
 
 tasks.named('test') {

+ 2 - 26
hichina-main-back/src/main/java/com/hichina/main/back/hichinamainback/config/CustomAuthenticationProvider.java

@@ -3,6 +3,7 @@ package com.hichina.main.back.hichinamainback.config;
 import com.hichina.main.back.hichinamainback.mapper.UserMapper;
 import com.hichina.main.back.hichinamainback.model.User;
 import com.hichina.main.back.hichinamainback.utils.FacebookAccessTokenValidator;
+import com.hichina.main.back.hichinamainback.utils.Md5Util;
 import com.hichina.main.back.hichinamainback.utils.UserUtil;
 import org.apache.commons.lang3.RandomStringUtils;
 import org.apache.commons.lang3.StringUtils;
@@ -154,33 +155,8 @@ public class CustomAuthenticationProvider implements AuthenticationProvider {
         return RandomStringUtils.randomAlphanumeric(16);
     }
 
-
-    private String md5(String input){
-        MessageDigest md = null;
-        try {
-            md = MessageDigest.getInstance("MD5");
-            //calculating message digest of an input that return array of byte
-            byte[] messageDigest = md.digest(input.getBytes());
-
-            //converting byte array into signum representation
-            BigInteger no = new BigInteger(1, messageDigest);
-
-            //converting message digest into hex value
-            String hashtext = no.toString(16);
-            while (hashtext.length() < 32)
-            {
-                hashtext = "0" + hashtext;
-            }
-            return hashtext;
-        } catch (NoSuchAlgorithmException e) {
-            throw new RuntimeException(e);
-        }
-
-    }
-
-
     private Boolean validatePasswordTheOldWay(String plainText, String passwordInDB, Integer pwdCode){
-        String encrypted = md5(plainText+(pwdCode==null?"":pwdCode.toString()));
+        String encrypted = Md5Util.md5v2(plainText+(pwdCode==null?"":pwdCode.toString()));
 
         return passwordInDB.equals(encrypted);
     }

+ 85 - 0
hichina-main-back/src/main/java/com/hichina/main/back/hichinamainback/controller/BaiduTranslateController.java

@@ -0,0 +1,85 @@
+package com.hichina.main.back.hichinamainback.controller;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+import com.hichina.main.back.hichinamainback.model.DTO.BaiduTranslateRequestDTO;
+import com.hichina.main.back.hichinamainback.model.DTO.HichinaResponse;
+import com.hichina.main.back.hichinamainback.model.DTO.TranslateObj;
+import com.hichina.main.back.hichinamainback.repository.BaiduTranslateCacheRepository;
+import com.hichina.main.back.hichinamainback.service.BaiduTranslateApiService;
+import org.jsoup.Jsoup;
+import org.jsoup.nodes.Document;
+import org.jsoup.nodes.Node;
+import org.jsoup.nodes.TextNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/api/public/baidutranslate")
+public class BaiduTranslateController {
+    private static final Logger LOG = LoggerFactory.getLogger(BaiduTranslateController.class);
+
+    @Autowired
+    private BaiduTranslateApiService baiduTranslateApiService;
+
+
+
+    private static Gson gson = new Gson();
+
+    @PostMapping
+    public HichinaResponse baiduTranslate(@RequestBody BaiduTranslateRequestDTO request){
+        HichinaResponse ret = new HichinaResponse();
+
+        String translatedText = "";
+
+        String cachedText = baiduTranslateApiService.fetchTranslationValueByKey(request.getTranslationKey());
+        if(cachedText!=null){
+            translatedText = cachedText;
+        }else{
+            Document doc = Jsoup.parse(request.getQuery());
+            htmlTextTagsTranslate(doc.childNodes(), request.getFrom(), request.getTo());
+            translatedText = doc.html();
+            baiduTranslateApiService.cacheTranslation(request.getTranslationKey(), translatedText);
+        }
+        ret.setOk(true);
+        ret.setMessage("Succeed getting baidu translation");
+        ret.setData(translatedText);
+        return ret;
+    }
+
+    private void htmlTextTagsTranslate(List<Node> nodes, String from, String to) {
+        for(Node node:nodes) {
+            htmlTextTagsTranslate(node.childNodes(), from, to);
+            if(node instanceof TextNode){
+                String originalText = ((TextNode) node).text();
+
+                JsonObject result = baiduTranslateApiService.getTransResult(originalText, from, to);
+                String transString = "";
+                if(result.get("trans_result") != null){
+                    LOG.info("=====has trans result");
+
+                    JsonArray transResults = (JsonArray)result.get("trans_result");
+                    if(transResults.size()>0){
+                        LOG.info("=====has trans result 2");
+
+                        TranslateObj translateObj = gson.fromJson(transResults.get(0).getAsJsonObject(), TranslateObj.class);
+                        transString = translateObj.getDst();
+                        LOG.info("===transString==="+transString);
+                    }
+                }else{
+                    LOG.info("=====empty trans result");
+                    LOG.info(result.toString());
+                }
+                ((TextNode) node).text(transString);
+            }
+        }
+    }
+}

+ 13 - 0
hichina-main-back/src/main/java/com/hichina/main/back/hichinamainback/model/DTO/BaiduTranslateRequestDTO.java

@@ -0,0 +1,13 @@
+package com.hichina.main.back.hichinamainback.model.DTO;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+public class BaiduTranslateRequestDTO {
+    private String translationKey;
+    private String from;
+    private String to;
+    private String query;
+}

+ 11 - 0
hichina-main-back/src/main/java/com/hichina/main/back/hichinamainback/model/DTO/TranslateObj.java

@@ -0,0 +1,11 @@
+package com.hichina.main.back.hichinamainback.model.DTO;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+public class TranslateObj {
+    private String src;
+    private String dst;
+}

+ 16 - 0
hichina-main-back/src/main/java/com/hichina/main/back/hichinamainback/model/mongo/BaiduTranslateCache.java

@@ -0,0 +1,16 @@
+package com.hichina.main.back.hichinamainback.model.mongo;
+
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import org.springframework.data.mongodb.core.mapping.Document;
+
+@Document("BaiduTranslateCache")
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class BaiduTranslateCache {
+    private String translationKey;
+    private String text;
+}

+ 19 - 0
hichina-main-back/src/main/java/com/hichina/main/back/hichinamainback/repository/BaiduTranslateCacheRepository.java

@@ -0,0 +1,19 @@
+package com.hichina.main.back.hichinamainback.repository;
+
+import com.hichina.main.back.hichinamainback.model.mongo.BaiduTranslateCache;
+import com.hichina.main.back.hichinamainback.model.mongo.BlogSlideImage;
+import com.hichina.main.back.hichinamainback.model.mongo.PageObjectView;
+import org.springframework.data.mongodb.repository.MongoRepository;
+import org.springframework.data.mongodb.repository.Query;
+
+import java.util.List;
+
+public interface BaiduTranslateCacheRepository extends MongoRepository<BaiduTranslateCache, String> {
+    List<BaiduTranslateCache> findAll();
+
+    long count();
+
+    @Query(value = "{ 'translationKey' : {$eq:?0} }")
+    List<BaiduTranslateCache> queryByTranslationKey(String translationKey);
+
+}

+ 88 - 0
hichina-main-back/src/main/java/com/hichina/main/back/hichinamainback/service/BaiduTranslateApiService.java

@@ -0,0 +1,88 @@
+package com.hichina.main.back.hichinamainback.service;
+
+import com.google.gson.JsonObject;
+import com.hichina.main.back.hichinamainback.model.mongo.BaiduTranslateCache;
+import com.hichina.main.back.hichinamainback.repository.BaiduTranslateCacheRepository;
+import com.hichina.main.back.hichinamainback.utils.HttpUtils;
+import com.hichina.main.back.hichinamainback.utils.Md5Util;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Service
+public class BaiduTranslateApiService {
+    private static final Logger LOG = LoggerFactory.getLogger(BaiduTranslateApiService.class);
+
+    private static final String TRANS_API_HOST = "http://api.fanyi.baidu.com/api/trans/vip/translate";
+
+    @Autowired
+    private BaiduTranslateCacheRepository baiduTranslateCacheRepository;
+
+    @Value("${baidu.appId}")
+    private String appId;
+
+    @Value("${baidu.securityKey}")
+    private String securityKey;
+
+    public JsonObject getTransResult(String query, String from, String to) {
+        LOG.info("==========querying======"+query);
+        Map<String, String> params = buildParams(query, from, to);
+        JsonObject ret = HttpUtils.postUrlWithParams(TRANS_API_HOST, params);
+        int retryCount = 0;
+        int maxRetryCount = 3;
+        int minMilli = 300;
+        int maxMilli = 1000;
+        while(retryCount++<maxRetryCount && ret.get("error_code")!=null && "54003".equals(ret.get("error_code").getAsString())){
+            try {
+                int randomNum = minMilli + (int)(Math.random() * ((maxMilli - minMilli) + 1));
+                Thread.sleep(randomNum);
+                LOG.info("====retrying after sleep: "+ randomNum + " millisec");
+                LOG.info("====retrying sending to baidu: "+ retryCount);
+
+                ret = HttpUtils.postUrlWithParams(TRANS_API_HOST, params);
+            } catch (InterruptedException e) {
+                throw new RuntimeException(e);
+            }
+        }
+        return ret;
+    }
+
+    public String fetchTranslationValueByKey(String translationKey){
+        if(baiduTranslateCacheRepository.queryByTranslationKey(translationKey).size()>0){
+            return baiduTranslateCacheRepository.queryByTranslationKey(translationKey).get(0).getText();
+        }
+        return null;
+    }
+
+    public void cacheTranslation(String translationKey, String text){
+        BaiduTranslateCache obj = new BaiduTranslateCache();
+        obj.setText(text);
+        obj.setTranslationKey(translationKey);
+        baiduTranslateCacheRepository.save(obj);
+    }
+
+
+    private Map<String, String> buildParams(String query, String from, String to) {
+        Map<String, String> params = new HashMap<String, String>();
+        params.put("q", query);
+        params.put("from", from);
+        params.put("to", to);
+
+        params.put("appid", appId);
+
+        // 随机数
+        String salt = String.valueOf(System.currentTimeMillis());
+        params.put("salt", salt);
+
+        // 签名
+        String src = appId + query + salt + securityKey; // 加密前的原文
+        params.put("sign", Md5Util.md5v2(src));
+
+        return params;
+    }
+}

+ 104 - 4
hichina-main-back/src/main/java/com/hichina/main/back/hichinamainback/utils/HttpUtils.java

@@ -2,10 +2,7 @@ package com.hichina.main.back.hichinamainback.utils;
 
 import com.google.gson.Gson;
 import com.google.gson.JsonObject;
-import okhttp3.Call;
-import okhttp3.OkHttpClient;
-import okhttp3.Request;
-import okhttp3.Response;
+import okhttp3.*;
 import org.apache.http.client.ClientProtocolException;
 import org.apache.http.client.methods.CloseableHttpResponse;
 import org.apache.http.client.methods.HttpGet;
@@ -25,7 +22,9 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import javax.net.ssl.SSLContext;
 import java.io.IOException;
+import java.io.UnsupportedEncodingException;
 import java.net.*;
+import java.util.Map;
 
 public class HttpUtils {
     private static final Logger LOG = LoggerFactory.getLogger(HttpUtils.class);
@@ -61,6 +60,107 @@ public class HttpUtils {
         }
     }
 
+    private static String getUrlWithQueryString(String url, Map<String, String> params) {
+        if (params == null) {
+            return url;
+        }
+
+        StringBuilder builder = new StringBuilder(url);
+        if (url.contains("?")) {
+            builder.append("&");
+        } else {
+            builder.append("?");
+        }
+
+        int i = 0;
+        for (String key : params.keySet()) {
+            String value = params.get(key);
+            if (value == null) { // 过滤空的key
+                continue;
+            }
+
+            if (i != 0) {
+                builder.append('&');
+            }
+
+            builder.append(key);
+            builder.append('=');
+            builder.append(encode(value));
+
+            i++;
+        }
+
+        return builder.toString();
+    }
+
+    public static String encode(String input) {
+        if (input == null) {
+            return "";
+        }
+
+        try {
+            return URLEncoder.encode(input, "utf-8");
+        } catch (UnsupportedEncodingException e) {
+            e.printStackTrace();
+        }
+
+        return input;
+    }
+
+    public static JsonObject postUrlWithParams(String url, Map<String, String> params){
+        System.setProperty("https.protocols", "TLSv1.2");
+
+        FormBody.Builder builder = new FormBody.Builder();
+
+        for(String key : params.keySet()){
+            builder.add(key, params.get(key));
+        }
+
+        RequestBody formBody = builder.build();
+
+        Request request = new Request.Builder()
+                .url(url).post(formBody)
+                .build();
+        OkHttpClient client = new OkHttpClient.Builder()
+                .build();
+
+        Call call = client.newCall(request);
+        try {
+            Response response = call.execute();
+            LOG.info("===ok client response code with post: "+response.code());
+            Gson gson = new Gson();
+            JsonObject entity = gson.fromJson(response.body().string(), JsonObject.class);
+            return entity;
+        } catch (IOException e) {
+            LOG.error("===okclient exception in postUrlWithParams: "+ e.getMessage());
+            return null;
+        }
+    }
+
+    public static JsonObject getUrlWithParams(String url, Map<String, String> params){
+        System.setProperty("https.protocols", "TLSv1.2");
+
+        url = getUrlWithQueryString(url, params);
+
+        Request request = new Request.Builder()
+                .url(url)
+                .build();
+        OkHttpClient client = new OkHttpClient.Builder()
+                .build();
+
+        Call call = client.newCall(request);
+        try {
+            Response response = call.execute();
+            LOG.info("===ok client response code: "+response.code());
+            Gson gson = new Gson();
+            JsonObject entity = gson.fromJson(response.body().string(), JsonObject.class);
+            return entity;
+        } catch (IOException e) {
+            LOG.error("===okclient exception in getUrlWithParams: "+ e.getMessage());
+            return null;
+        }
+    }
+
     public static String sendToWithProxy(String url, String proxyHost, Integer proxyPort) throws IOException {
         System.setProperty("https.protocols", "TLSv1.2");
         Registry<ConnectionSocketFactory> reg = RegistryBuilder.<ConnectionSocketFactory>create()

+ 48 - 0
hichina-main-back/src/main/java/com/hichina/main/back/hichinamainback/utils/Md5Util.java

@@ -0,0 +1,48 @@
+package com.hichina.main.back.hichinamainback.utils;
+
+import java.io.UnsupportedEncodingException;
+import java.math.BigInteger;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+public class Md5Util {
+    private static final char[] hexDigits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd',
+            'e', 'f' };
+
+    public static String md5v2(String input) {
+        if (input == null)
+            return null;
+
+        try {
+            // 拿到一个MD5转换器(如果想要SHA1参数换成”SHA1”)
+            MessageDigest messageDigest = MessageDigest.getInstance("MD5");
+            // 输入的字符串转换成字节数组
+            byte[] inputByteArray = input.getBytes("utf-8");
+            // inputByteArray是输入字符串转换得到的字节数组
+            messageDigest.update(inputByteArray);
+            // 转换并返回结果,也是字节数组,包含16个元素
+            byte[] resultByteArray = messageDigest.digest();
+            // 字符数组转换成字符串返回
+            return byteArrayToHex(resultByteArray);
+        } catch (NoSuchAlgorithmException e) {
+            return null;
+        } catch (UnsupportedEncodingException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private static String byteArrayToHex(byte[] byteArray) {
+        // new一个字符数组,这个就是用来组成结果字符串的(解释一下:一个byte是八位二进制,也就是2位十六进制字符(2的8次方等于16的2次方))
+        char[] resultCharArray = new char[byteArray.length * 2];
+        // 遍历字节数组,通过位运算(位运算效率高),转换成字符放到字符数组中去
+        int index = 0;
+        for (byte b : byteArray) {
+            resultCharArray[index++] = hexDigits[b >>> 4 & 0xf];
+            resultCharArray[index++] = hexDigits[b & 0xf];
+        }
+
+        // 字符数组组合成字符串返回
+        return new String(resultCharArray);
+
+    }
+}

+ 4 - 1
hichina-main-back/src/main/resources/application-dev.properties

@@ -63,4 +63,7 @@ hichina.email.sender=register@hichinatrip.com
 hichina.email.authcode=**
 hichina.mail.host=smtp.qiye.aliyun.com
 
-gfw.proxy.port=1083
+gfw.proxy.port=1083
+
+baidu.appId=20171207000102933
+baidu.securityKey=wF1ETdaKHUUUijlmS7OA

+ 3 - 2
hichina-main-front-mobile-first/src/layouts/MainLayout.vue

@@ -67,7 +67,7 @@
             map-options
             options-dense
             style="min-width: 150px"
-            @click="setLanguage"
+            @update:model-value="setLanguage"
             ><template v-slot:append> <q-icon name="public" /> </template
           ></q-select>
         </div>
@@ -378,7 +378,8 @@ export default defineComponent({
     const $q = useQuasar();
 
     function setLanguage() {
-      $q.lang.set(locale);
+      // $q.lang.set(locale);
+      console.log(locale);
     }
 
     function whoami() {

+ 101 - 2
hichina-main-front-mobile-first/src/pages/BlogDetailPage.vue

@@ -3,7 +3,7 @@
     <div class="row justify-center">
       <div class="col-12 col-md-9" style="min-height: 500px">
         <div class="text-weight-bold text-h3 q-pa-xl">
-          {{ title }}
+          {{ removeHtmlTag(title) }}
         </div>
         <div class="row col-12" style="min-height: 50px">
           <div class="q-ml-xl">
@@ -11,10 +11,22 @@
               <img :src="authorProfileImageUrl" />
             </q-avatar>
           </div>
-          <div class="col-8 q-ml-md">
+          <div class="col-3 q-ml-md">
             <div class="text-weight-bold text-h6">{{ username }}</div>
             <div><q-btn color="primary" icon="add" label="Follow" /></div>
           </div>
+          <div class="col q-mt-md">
+            <q-btn
+              icon="g_translate"
+              @click="translateThisArticle(content)"
+              label="Translate"
+              dense
+              rounded
+              stack
+              glossy
+              color="primary"
+            />
+          </div>
         </div>
         <div class="row q-px-xl q-py-md text-h5">
           <div class="col-5">{{ createdTime }}</div>
@@ -44,12 +56,17 @@
 <script>
 import { ref, onMounted, getCurrentInstance, reactive } from "vue";
 import { api } from "boot/axios";
+import { apiRaw } from "boot/axiosraw";
 import { useRoute } from "vue-router";
 import { useQuasar } from "quasar";
 import { useSeoMeta } from "unhead";
+import { useI18n } from "vue-i18n";
+
 export default {
   name: "BlogDetail",
   setup() {
+    const { locale } = useI18n({ useScope: "global" });
+
     const route = useRoute();
     const instance = getCurrentInstance();
     const app = getCurrentInstance().appContext.app;
@@ -80,6 +97,87 @@ export default {
           console.error("Error:", err);
         });
     }
+
+    function toBaiduLanguage(input) {
+      if (input === "ko-KR") {
+        return "kor";
+      } else if (input === "en-US") {
+        return "en";
+      } else if (input === "ru-RU") {
+        return "ru";
+      } else if (input === "th-TH") {
+        return "th";
+      } else {
+        return "auto";
+      }
+    }
+
+    function translateTitle() {
+      const params = {};
+      // key format: type_guid_part_tolang example: blog_f4128363-84bb-4169-a30c-a3d6fbc0a3ef_title_th
+      params.translationKey =
+        "blog_" +
+        route.params.blogId +
+        "_title_" +
+        toBaiduLanguage(locale.value);
+      params.query = title.value;
+      params.from = "auto";
+      params.to = toBaiduLanguage(locale.value);
+      gp.$showLoading($q);
+      api
+        .post("/api/public/baidutranslate", params)
+        .then((res) => {
+          console.log("baidu translate result title: ");
+          console.log(res.data);
+          if (res.data.ok == true) {
+            title.value = res.data.data;
+          } else {
+            console.log("translate title failed");
+          }
+
+          gp.$hideLoading($q);
+        })
+        .catch((err) => {
+          console.log("err baidu translate title");
+          gp.$hideLoading($q);
+        });
+    }
+
+    function translateContent() {
+      const params = {};
+      // key format: type_guid_part_tolang example: blog_f4128363-84bb-4169-a30c-a3d6fbc0a3ef_content_th
+      params.translationKey =
+        "blog_" +
+        route.params.blogId +
+        "_content_" +
+        toBaiduLanguage(locale.value);
+      params.query = content.value;
+      params.from = "auto";
+      params.to = toBaiduLanguage(locale.value);
+      gp.$showLoading($q);
+      api
+        .post("/api/public/baidutranslate", params)
+        .then((res) => {
+          console.log("baidu translate result: ");
+          console.log(res.data);
+          if (res.data.ok == true) {
+            content.value = res.data.data;
+            translateTitle();
+          } else {
+            console.log("translate failed");
+          }
+
+          gp.$hideLoading($q);
+        })
+        .catch((err) => {
+          console.log("err baidu translate");
+          gp.$hideLoading($q);
+        });
+    }
+    function translateThisArticle(textToTranslate) {
+      translateContent();
+    }
+
     function getAuthorPrincipal() {
       api
         .get(
@@ -151,6 +249,7 @@ export default {
       createdTime,
       pageViewCnt,
       content,
+      translateThisArticle,
     };
   },
 };