基于Java实现在线翻译【100010578】

在线翻译

1 需求分析

前端页面选择语言,输入文字,发送给服务器端,服务器端调用翻译 API 接口,将翻译结果传递至前端页面进行显示。

1.1 前端界面案例

1.2 参考资料

有道翻译 API:http://fanyi.youdao.com/openapi

百度翻译 API:https://api.fanyi.baidu.com/

1.3 各项功能

1.3.1 下拉菜单

下拉菜单的选项和个国语言的代码都是在页面加载的时候向后端发起请求,在数据中查找的,都是动态生成的。

1.3.2 翻译

当输入中文时,可以点击不同的选项,翻译为对应的语言。

例如:

中文 == 英文

中文 == 日文

2 系统设计

2.1 用户用例图

页面加载,初始化下拉菜单。

用户点击翻译按钮,发送请求获取翻译信息

图 2-1 用户用例图

2.2 ER 图

数据库设计 1 张表,语言表,对应的 ER 图如图 3-1 所示。

LangName:语言名字

LangCode: 语言代码(对应百度 AP)

图 3-1 数据库 ER 图

2.3 UML 类图(Class Diagram)

对于翻译功能模块,共设计如下 6 个类。

Entity 实体类 Lang.java:与数据库结构进行映射的类。主要由属性,setter, getter 方法组成,Entity 类中的属性与表中的字段相对应,每一个 Entity 类的对象都表示表中的每一条记录

pojo 对象类:
·Trans.java 为用户的翻译值对象,属性 query,from,to,分别表示请求翻译,翻译源语言,翻译目标语言。
·TransData.java:为值对象,属性 ser,dst,为原文与译文
·TransResult.java:为值对象,里面包含 TransData.java,从 API 请求的内容要用此对象存

Mapper 接口 LangMapper.java:主要定义操作的接口,定义一系列数据库的原子性操作,例如增删改查(通常称为 CRUD)等。

Serivce 层:主要处理业务逻辑,对于数据层的原子操作进行整合。在 TransService 里面调用百度 API 接口。

Controller 层:前端控制器,里面都是前需要请求调用的接口,主要包括前端下拉菜单的请求接口和前端翻译按钮的点击事件请求。

各类的结构及类之间的关系如图 2-5 所示:

图 2-5 翻译功能类图

2.4 UML 时序图(Sequence Diagram)

2.4.1 翻译

2.4.2 模块

每个功能按编号往后递增绘图

2.4.3 模块

每个功能按编号往后递增绘图

3 系统实现

3.1 项目结构

3.2 百度 API

在项目下新建 Source Folder,名为 resources

3.2.1 百度 API 相关

TransApi.java

@Component
public class TransApi {
    private static final String TRANS_API_HOST = "http://api.fanyi.baidu.com/api/trans/vip/translate";


    private static String appid = "20201213000646199";
    private static String securityKey = "qgXPVgAa6onAAbtZ5NjP";

    public TransApi(){
    }
    public TransApi(String appid, String securityKey) {
        this.appid = appid;
        this.securityKey = securityKey;
    }
    /**
     * 获得翻译结果
     * @param query
     * @param from
     * @param to
     * @return
     */
    public String getTransResult(String query, String from, String to) throws IOException {
        Map<String, String> params = buildParams(query, from, to);
        JSONObject jsonObject = null;
        //当请求翻译内容过长 用post
        if (query.length() >= 2000){
            //post请求方式
            jsonObject = HttpUtil.doPostStr(TRANS_API_HOST, params);
        }else {
            //get请求方式
            String url = getUrlWithQueryString(TRANS_API_HOST, params);
            jsonObject = HttpUtil.doGetStr(url);
        }

        if (jsonObject.get("error_code")!=null) {
            return "翻译失败,原因:"+jsonObject.get("error_msg");
        }else{
            TransResult transResult = JSON.parseObject(jsonObject.toString(), TransResult.class);
            String s = JSON.toJSONString(transResult);
            return s;
//            System.out.println("transResult:"+s);
//            return " 翻译结果 "+transResult.getTrans_result().get(0).getDst();
        }
    }
    /**
     * 构建参数map
     *
     * @param query
     * @param from
     * @param to
     * @return
     * @throws UnsupportedEncodingException
     */
    private static 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", MD5.md5(src));
        return params;
    }
    /**
     * 拼接url get方式拼接参数  返回url
     *
     * @param url
     * @param params
     * @return
     */
    public 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();
    }

    /**
     * 对输入的字符串进行URL编码, 即转换为%20这种形式
     *
     * @param input 原文
     * @return URL编码. 如果编码失败, 则返回原文
     */
    public static String encode(String input) {
        if (input == null) {
            return "";
        }
        try {
            return URLEncoder.encode(input, "utf-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return input;
    }
}

MD5.java

/**
 * MD5编码相关的类
 * 
 * @author wangjingtao
 * 
 */
public class MD5 {
    // 首先初始化一个字符数组,用来存放每个16进制字符
    private static final char[] hexDigits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd',
            'e', 'f' };
    /**
     * 获得一个字符串的MD5值
     * 
     * @param input 输入的字符串
     * @return 输入字符串的MD5值
     * @throws UnsupportedEncodingException 
     * 
     */
    public static String md5(String input){
        if (input == null)
            return null;
        try {
            // 拿到一个MD5转换器(如果想要SHA1参数换成”SHA1”)
            MessageDigest messageDigest = MessageDigest.getInstance("MD5");
            // 输入的字符串转换成字节数组
            byte[] inputByteArray = input.getBytes(StandardCharsets.UTF_8);
            // inputByteArray是输入字符串转换得到的字节数组
            messageDigest.update(inputByteArray);
            // 转换并返回结果,也是字节数组,包含16个元素
            byte[] resultByteArray = messageDigest.digest();
            // 字符数组转换成字符串返回
            return byteArrayToHex(resultByteArray);
        } catch (NoSuchAlgorithmException e) {
            return null;
        }
    }
    /**
     * 获取文件的MD5值
     * 
     * @param file
     * @return
     */
    public static String md5(File file) {
        try {
            if (!file.isFile()) {
                System.err.println("文件" + file.getAbsolutePath() + "不存在或者不是文件");
                return null;
            }
            FileInputStream in = new FileInputStream(file);
            String result = md5(in);
            in.close();
            return result;
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
    public static String md5(InputStream in) {
        try {
            MessageDigest messagedigest = MessageDigest.getInstance("MD5");

            byte[] buffer = new byte[1024];
            int read = 0;
            while ((read = in.read(buffer)) != -1) {
                messagedigest.update(buffer, 0, read);
            }
            in.close();
            String result = byteArrayToHex(messagedigest.digest());
            return result;
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
    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);
    }

}

HttpUtil.java

/**
 * 请求工具类
 */
public class HttpUtil {
    /**
     *  get 请求
     * @param url
     * @return
     * @throws IOException
     */
    public static JSONObject doGetStr(String url) throws IOException {
        CloseableHttpClient httpClient = HttpClients.createDefault();
        HttpGet httpGet = new HttpGet(url);
        CloseableHttpResponse response = httpClient.execute(httpGet);
        HttpEntity entity = response.getEntity();
        if (entity != null) {
            String content = EntityUtils.toString(entity,StandardCharsets.UTF_8) ;
            return JSONObject.parseObject(content);
        }
        return null;
    }

    /**
     * post 请求 String装填
     * @param url
     * @param reqContent
     * @return
     * @throws IOException
     */
    public static JSONObject doPostStr(String url,String reqContent) throws IOException {
        CloseableHttpClient httpClient = HttpClients.createDefault();
        HttpPost httpPost = new HttpPost(url);
        if (!StringUtils.isEmpty(reqContent)) {
            httpPost.setEntity(new StringEntity(reqContent,"UTF-8"));
        }
        CloseableHttpResponse response = httpClient.execute(httpPost);
        HttpEntity entity = response.getEntity();
        if (entity != null) {
            String resContent = EntityUtils.toString(entity,"UTF-8") ;
            return JSONObject.parseObject(resContent);
        }
        return null;
    }
    /**
     * post 请求 map装填
     * @param url
     * @param reqContent
     * @return
     * @throws IOException
     */
    public static JSONObject doPostStr(String url,Map<String,String> reqContent) throws IOException {
        CloseableHttpClient httpClient = HttpClients.createDefault();
        HttpPost httpPost = new HttpPost(url);

        //装填参数
        List<NameValuePair> nvps = new ArrayList<NameValuePair>();
        if (reqContent != null) {
            for (Map.Entry<String, String> entry : reqContent.entrySet()) {
                nvps.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
            }
        }
        //设置参数到请求对象中
        httpPost.setEntity(new UrlEncodedFormEntity(nvps, StandardCharsets.UTF_8));

        CloseableHttpResponse response = httpClient.execute(httpPost);
        HttpEntity entity = response.getEntity();
        if (entity != null) {
            String resContent = EntityUtils.toString(entity, StandardCharsets.UTF_8);
            return JSONObject.parseObject(resContent);
        }
        return null;
    }
}

3.3 common(公共部分)相关

R.java(返回结果集)

描述该文件作用,并对里面的配置进行解释

/**
 * 这里我们用到了一个R的类,这个用于我们的异步统一返回的结果封装。
 * 一般来说,结果里面有几个要素必要的
 * <p>
 * 是否成功,可用code表示(如200表示成功,400表示异常)
 * 结果消息
 * 结果数据
 */
@Data
@Accessors(chain = true)
@AllArgsConstructor
@NoArgsConstructor
public class R implements Serializable {
    private int code; //200是正常  非200表示异常
    private String msg;
    private Object data;

    public static R succ(Object data) {
        R r = new R()
                .setCode(200)
                .setData(data)
                .setMsg("操作成功");
        return r;
    }
    public static R succ(String msg,Object data) {
        R r = new R()
                .setCode(200)
                .setData(data)
                .setMsg(msg);
        return r;
    }
    public static R succ(int code,String msg, Object data){
        R r = new R()
                .setCode(code)
                .setData(data)
                .setMsg(msg);
        return r;
    }

    public static R fail(String msg) {
        R r = new R()
                .setCode(-1)
                .setData(null)
                .setMsg(msg);
        return r;
    }

    public static R fail(String msg,Object data) {
        R r = new R()
                .setCode(-1)
                .setData(data)
                .setMsg(msg);
        return r;
    }

    public static R fail(int i, String message, Object data) {
        R r = new R()
                .setCode(i)
                .setData(data)
                .setMsg(message);
        return r;
    }
}

3.4 JavaConfig 相关

描述该文件作用,并对里面的配置进行解释

MybatisPlusConfig.java

@Configuration
@EnableTransactionManagement // 开启事务管理
@MapperScan("com.shm.mapper")
public class MybatisPlusConfig {

}

swaggerConfig.java

@Configuration
@EnableSwagger2
public class swaggerConfig {

    @Bean
    public ApiInfo apiInfo(){
        //作者信息
        Contact contact = new Contact("小明", "", "[email protected]");
        return new ApiInfo(
                "SwaggerAPI文档",
                "小明学长",
                "1.0",
                "urn:tos",
                contact,
                null,
                null,
                new ArrayList<VendorExtension>());
    }
}

3.5 application.yml 配置

描述该文件作用,并对里面的关键代码进行解释

# 配置数据源
spring:
  application:
    name: mybatis-plus
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/translate-api?useUnicode=true&useSSL=false&characterEncoding=utf8&serverTimezone=Asia/Shanghai
    username: root
    password: root
# mybatis 配置
mybatis-plus:
  mapper-locations: classpath:mapper/*.xml
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #日志实现类
    map-underscore-to-camel-case: true                     # 驼峰转换
  type-aliases-package: com.shm.entity   

3.6 实体类相关

Lang.java

@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("t_lang")
public class Lang implements Serializable {

    private static final long serialVersionUID = 1L;

    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    @TableField("langName")
    private String langName;

    @TableField("langCode")
    private String langCode;


}

Trans.java:

描述该文件作用,并对里面的关键代码进行解释

@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class Trans {
    private String query;
    private String from;
    private String to;
}

transData.java:

描述该文件作用,并对里面的关键代码进行解释

@Data
public class TransData {
    /**
     * 原文
     */
    private String src;
    /**
     * 译文
     */
    private String dst;
}

TransResult.java:

@Data
public class TransResult {
    /**
     *翻译源语言
     */
    private String from;
    /**
     *译文语言
     */
    private String to;
    /**
     *翻译结果
     */
    private List<TransData> trans_result;
}

3.6 Mapper 接口与 Mapper.xml

描述该文件作用,并对里面的关键代码进行解释

这里我使用的是 Mybatis-plus ,基本的 CRUD 都不用自己手写,所以文件里面是空的,但是不可以没有。

LanngMapper.java

public interface LangMapper extends BaseMapper<Lang> {

}

LangMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.shm.mapper.LangMapper">

</mapper>

3.7 Serivce 接口与实现

LangService.java

public interface LangService extends IService<Lang> {

}

LangServiceImpl.java

@Service
public class LangServiceImpl extends ServiceImpl<LangMapper, Lang> implements LangService {

}

TrangService.java

@Service
public class TransService {
  @Autowired
  TransApi transApi;
  public TransResult result(Trans trans) {
    TransResult transResult = null;
    try {
      String str = transApi.getTransResult(trans.getQuery(), trans.getFrom(), trans.getTo());
      System.*out*.println(str);
      transResult = JSON.*parseObject*(str, TransResult.class);
    } catch (IOExceptioe) {
      e.printStackTrace();
    }
     returtransResult;
  }
}

3.8 Controller 控制器层

LangController.java

@RestController
@RequestMapping("/lang")
@CrossOrigin
public class LangController {

    @Autowired
    private LangService langService;

    /**
     * 获取全部语言名称与代码
     * @return
     */
    @GetMapping("all")
    public R langAll(){
        List<Lang> langs = langService.getBaseMapper().selectList(null);
        return R.succ(200,"操作成功",langs);
    }

}

TranslateController.java

@RestController
@RequestMapping("/lang")
@CrossOrigin
public class LangController {

    @Autowired
    private LangService langService;

    /**
     * 获取全部语言名称与代码
     * @return
     */
    @GetMapping("all")
    public R langAll(){
        List<Lang> langs = langService.getBaseMapper().selectList(null);
        return R.succ(200,"操作成功",langs);
    }

}

3.8 用户界面 vue.js

描述该文件作用,并对里面的关键代码进行解释

<script>
	let vue = new Vue({
		el:'#app',
		data:{
			/* 翻译处理
			 query: 被翻译的内容
			 from:语言本来类型
			 to: 要翻译的类型
			 
			 */
			trans:{
				query: '',
				from: 'auto',
				to:'en'
			},
			/*  */
			text: '',
			btn_group: '中文 == 英语',
			langs:[],
			textVal: 0
		}
		,
		methods:{
			/* 获取翻译内容 */
			translate(){
				if(this.trans.query == null || this.trans.query === ''){
					alert('请输入翻译内容')
				}else{
					axios.post('http://localhost:8080/trans',this.trans)
					.then(response=>{
						this.text = response.data.data.trans_result[0].dst
					})
					.catch(error=>{
						alert('发生未知错误!'+error )
						this.text=''
					})
				}
			},
			/* 页面加载时,获取数据库内容 */
			getLang(){
				const _this = this
				axios.get('http://localhost:8080/lang/all')
				.then(
					res=>{
						//赋值
						this.langs = res.data.data
					}
				).catch(
					error=>{
						console.log('发生未知错误!'+error)
						this.text=''
					}
				)
			},
			/* 获取下拉框信息 */
			getLangCode(code){
				this.btn_group = '中文 ==' + code.langName
				this.trans.to = code.langCode
			}
		},
		created() {
			this.getLang()
		}
	})
</script>

4 系统测试

设计测试用例,给出程序每个功能模块的运行结果截图。

5 系统总结

主要问题的解决过程、系统存在的不足,本门课程的收获,课程的建议,以后的发展方向,自评成绩。

主要是前端的问题,对于前端 bootStrop,Vue.js 运用还是有些生疏,请求到数据之后,对于数据在页面上的渲染,还有数据在 js 里面使用存在一些问题。

目前此系统的下拉菜单只显示中文转换为其他国家的语言,但是默认我是使用的自动检测语言,在不点击下拉框的情况下,只要是百度 API 上面有的语言都能转换为英文,但是点击了之后,在 vue 里面我把自动检测语言的默认值替代了,我没有在数据库里面加自动检测的代码,默认值我是写在 vue 里面写死的,这是问题之一。

下拉菜单还有一个问题,在页面加载的时候就会像服务器发送请求,然后去数据库中进行查询,如果频繁的刷新页面的时候,就会频繁的对数据库进行操纵,这无疑加大了开销。其实针对此问题我已经想到了解决方案:我们可以把菜单数据从数据库读取之后存到 Redis 缓存中,我们发出请求操作的时候,直接对缓存进行操作,然后设置 Spring boot 定时任务,定期的在数据中查找数据,存入 Redis。这样大大的减少了服务器的开销。

还有就是此项目数据前后端分离项目,其实只能算是半个,对于跨域的问题,我也只是一知半解,这个问题发生的时候我没有查资料,以为是请求有问题,一直报错。之后我查阅资料之后,有几套解决问题的方案,时间关系我用的是最简单的,在 controller 控制器中加入“@CrossOrigin”注解,然后请求就成功了,我对于这个原理还不是很懂。

前端项目我还不懂如何部署到 nginx 服务器上面。

上面的两个问题,由于时间和技术的关系,目前还没有实施在项目中,只是有了大概的思路。在后续我一定多多补充这方面知识。

以后我会主要学习 Spring boot 相关内容,传统的 SSM 也像之前的 SSH 一样,会过时。JSP 也过时了,可以使用 thymeleaf 模板引擎替代 JSP 模板。其实对于前后端分离和跟 vue.js 集成效果最好的还是使用 vue cli,后面我也会重点学习的。还有 Java 基础部分,很多都忘了,例如反射,线程,网络编程,IO,也需要补。

♻️ 资源

在这里插入图片描述

大小: 1.55MB
➡️ 资源下载:https://download.csdn.net/download/s1t16/87404249

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>