springmvc高级
1.springmvc拦截器
1.1 什么是拦截器
-
回顾JavaWeb中的过滤器Filter:
过滤器实际上就是对web资源进行拦截,做一些处理后再交给下一个过滤器或servlet处理,
通常都是用来拦截request进行处理的,也可以对返回的response进行拦截处理。典型使用场景:在Servlet中我们一般都会对request和response中的字符集编码进行配置,如果Servlet过多字符集编码发生变化时修改会很麻烦,这些通用的字符集编码配置等工作我们可以放到Filter中来实现。
– 我们在实现spring中文乱码时也使用了过滤器。
-
过滤器(Filter)与拦截器(Interceptor)异同:
Spring的拦截器与Servlet的Filter有相似之处:
二者都是AOP编程思想的体现,都能实现权限检查、日志记录等。
不同的是:
1.使用范围不同:Filter是Servlet规范规定的,只能用于Web程序中。而拦截器既可以用于Web程序,也可以用于Application、Swing程序中。
2.规范不同:Filter是在Servlet规范中定义的,是Servlet容器支持的。而拦截器是在Spring容器内的,是Spring框架支持的。
3.使用的资源不同:同其他的代码块一样,拦截器也是一个Spring的组件,归Spring管理,配置在Spring文件中,因此能使用Spring里的任何资源、对象,例如Service对象、数据源、事务管理等,通过IoC注入到拦截器即可;而Filter则不能。
4.深度不同:Filter只在Servlet前后起作用。而拦截器能够深入到方法前后、异常抛出前后等,因此拦截器的使用具有更大的弹性。所以在Spring构架的程序中,要优先使用拦截器。
拦截器是spring提供的一个特殊的组件,当DispatcherServlet收到请求之后,如果有拦截器,会先调用拦截器,然后调用响应的处理器(Handler)
-
拦截器的使用场景:
1、日志记录:记录请求信息的日志,以便进行信息监控、信息统计、计算PV(Page View)等。
2、权限检查:如登录检测,进入处理器检测检测是否登录,如果没有直接返回到登录页面;
3、性能监控:有时候系统在某段时间莫名其妙的慢,可以通过拦截器在进入处理器之前记录开始时间,在处理完后记录结束时间,从而得到该请求的处理时间(如果有反向代理,如apache可以自动记录);
4、通用行为:读取cookie得到用户信息并将用户对象放入请求,从而方便后续流程使用,还有如提取Locale、Theme信息等,只要是多个处理器都需要的即可使用拦截器实现。
5、OpenSessionInView:如hibernate,在进入处理器打开Session,在完成后关闭Session。
1.2 拦截器原理
SpringMVC中的拦截器需要实现HandlerInterceptor。
源码:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.springframework.web.servlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.lang.Nullable;
public interface HandlerInterceptor {
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return true;
}
default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
}
default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
}
}
它有三个抽象方法:
preHandle:控制器方法执行之前执行preHandle(),其boolean类型的返回值表示是否拦截或放行,返回true为放行,即调用控制器方法;返回false表示拦截,即不调用控制器方法
postHandle:控制器方法执行之后执行postHandle(),它的入参还含有modelAndView,因此可以对返回视图进行干涉或者处理。
afterComplation:处理完视图和模型数据,渲染视图完毕之后执行afterComplation()。但仅调用处理器执行链中preHandle返回true的拦截器的afterCompletion
1.3 演示拦截器执行顺序
-
代码演示
-
拦截器 MyInterceptor2代码一致只改个名字
``` package com.liyw.interceptor; import org.springframework.lang.Nullable; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class MyInterceptor implements HandlerInterceptor { public MyInterceptor() { } @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("preHandle==="); return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception { System.out.println("postHandle==="); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception { System.out.println("afterCompletion==="); } } ```
-
controller
@Controller public class InterceptorController { @RequestMapping("/interceptor") public String customException(Model model, @RequestParam(value="id",required=true) Integer id)throws Exception { System.out.println("id"+id); return "success"; } }
-
servlet.xml
参数说明:
1)mvc:mapping 拦截器路径配置,如果需要拦截所有的请求路径为/**
2)mvc:exclude-mapping 拦截器不需要拦截的路径
-
-
<!--配置interceptor-->
<mvc:interceptors>
<mvc:interceptor>
<!--1和2的顺序不能变-->
<!--1--><mvc:mapping path="/interceptor"/>
<!--2--><bean class="com.liyw.interceptor.MyInterceptor"/>
</mvc:interceptor>
<mvc:interceptor>
<!--1和2的顺序不能变-->
<!--1--><mvc:mapping path="/interceptor"/>
<!--2--><bean class="com.liyw.interceptor.MyInterceptor2"/>
</mvc:interceptor>
</mvc:interceptors>
输出:
preHandle
preHandle2
id111
postHandle2
postHandle
afterCompletion2
afterCompletion
说明执行顺序+多个拦截器chain的执行顺序:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lNC9wLNq-1688372883209)(springmvc高级.assets/image-20211111172530677.png)]
-
多个拦截器的执行顺序
正常流程:
a>若每个拦截器的preHandle()都返回true
此时多个拦截器的执行顺序和拦截器在SpringMVC的配置文件的配置顺序有关:
preHandle()会按照配置的顺序执行,而postHandle()和afterComplation()会按照配置的反序执行
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eR0plenQ-1688372883210)(springmvc高级.assets/image-20211112090119517.png)]
1.4 异常流程拦截器执行
-
代码
-
拦截器:
MyInterceptor 3和MyInterceptor 4 与 之前的 MyInterceptor 1和MyInterceptor 2一样,
只是在MyInterceptor 4的preHandle方法返回false:
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("preHandle4==="); response.getWriter().print("break");//流程中断的话需要我们进行响应的处理 return false;//返回false表示流程中断 }
-
servlet.xml
拦截器改成3和4
-
输出结果:
preHandle3==
preHandle4==
afterCompletion3==
此处我们可以看到只有MyInterceptor3的afterCompletion执行,否和图2的中断流程。
而且页面上会显示我们在MyInterceptor4 preHandle 直接写出的响应“break”。
-
b>若某个拦截器的preHandle()返回了false
preHandle()返回false和它之前的拦截器的preHandle()都会执行,postHandle()都不执行,返回false的拦截器之前的拦截器的afterComplation()会执行
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5rLIINKd-1688372883210)(springmvc高级.assets/image-20211112160505735.png)]
1.5 拦截器应用案例开发
- 性能监控案例:
场景需求:如记录一下请求的处理时间,得到一些慢请求(如处理时间超过500毫秒),从而进行性能改进,一般的反向代理服务器如apache都具有这个功能,但此处我们演示一下使用拦截器怎么实现。
实现分析:
1、在进入处理器之前记录开始时间,即在拦截器的preHandle记录开始时间;
2、在结束请求处理之后记录结束时间,即在拦截器的afterCompletion记录结束实现,并用结束时间-开始时间得到这次请求的处理时间。
问题:
我们的拦截器是单例,因此不管用户请求多少次都只有一个拦截器实现,即线程不安全,那我们应该怎么记录时间呢?
解决方案:
使用ThreadLocal,它是线程绑定的变量,提供线程局部变量(一个线程一个ThreadLocal,A线程的ThreadLocal只能看到A线程的ThreadLocal,不能看到B线程的ThreadLocal)
NamedThreadLocal:Spring提供的一个命名的ThreadLocal实现。
在测试时需要把stopWatchHandlerInterceptor放在拦截器链的第一个,这样得到的时间才是比较准确的。
代码:
package com.liyw.interceptor;
import org.springframework.core.NamedThreadLocal;
import org.springframework.lang.Nullable;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class StopWatchHandlerInterceptor implements HandlerInterceptor {
private NamedThreadLocal<Long> startTimeThreadLocal = new NamedThreadLocal<Long>("StopWatch-StartTime");
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
long beginTime = System.currentTimeMillis();// 1、开始时间
startTimeThreadLocal.set(beginTime);// 线程绑定变量(该数据只有当前请求的线程可见)
return true;// 继续流程
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
long endTime = System.currentTimeMillis();// 2、结束时间
long beginTime = startTimeThreadLocal.get();// 得到线程绑定的局部变量(开始时间)
long consumeTime = endTime - beginTime;// 3、消耗的时间
if (consumeTime > 500) {// 此处认为处理时间超过500毫秒的请求为慢请求
System.out.println(String.format("%s consume %d millis", request.getRequestURI(), consumeTime));
}
}
}
- 用户登录状态校验
1.6 其他
拦截器可能会造成拦截静态资源的问题,因此需要对其进行配置。
建议方案:在web.xml中增加对静态资源的处理(放在DispatcherServlet的注册之前)。如有更多类型的静态资源,请根据实际情况配置。
<servlet-mapping>
<servlet-name>default</servlet-name>
<url-pattern>*.js</url-pattern>
<url-pattern>*.html</url-pattern>
<url-pattern>*.css</url-pattern>
<url-pattern>/assets/*"</url-pattern>
<url-pattern>/images/*</url-pattern>
</servlet-mapping>
2. json数据交互
2.1 什么是json
JSON(JavaScript Object Notation, JS 对象标记) 是一种轻量级的数据交换格式,目前使用特别广泛。
常用于远程接口传输,http传输json数据,非常方便页面进行提交/请求结果解析。
- 优点:
简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。
易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率。
2.2 JSON和JavaScript 的关系
在 JavaScript 语言中,一切都是对象。因此,任何JavaScript 支持的类型都可以通过 JSON 来表示,例如字符串、数字、对象、数组等。看看他的要求和语法格式:
1.对象表示为键值对,数据由逗号分隔
2.花括号保存对象
3.方括号保存数组
JSON 键值对是用来保存 JavaScript 对象的一种方式,和 JavaScript 对象的写法也大同小异,键/值对组合中的键名写在前面并用双引号 “” 包裹,使用冒号 : 分隔,然后紧接着值:
{“name”: “QinJiang”}
{“age”: “3”}
{“sex”: “男”}
很多人搞不清楚 JSON 和 JavaScript 对象的关系,甚至连谁是谁都不清楚。其实,可以这么理解:
JSON 是 JavaScript 对象的字符串表示法,它使用文本表示一个 JS 对象的信息,本质是一个字符串。
var obj = {a: ‘Hello’, b: ‘World’}; //这是一个对象,注意键名也是可以使用引号包裹的
var json = ‘{“a”: “Hello”, “b”: “World”}’; //这是一个 JSON 字符串,本质是一个字符串
JSON 和 JavaScript 对象互转
要实现从JSON字符串转换为JavaScript 对象,使用 JSON.parse() 方法:
var obj = JSON.parse('{"a": "Hello", "b": "World"}');
//结果是 {a: 'Hello', b: 'World'}
要实现从JavaScript 对象转换为JSON字符串,使用 JSON.stringify() 方法:
var json = JSON.stringify({a: 'Hello', b: 'World'});
//结果是 '{"a": "Hello", "b": "World"}'
- 代码测试:
新建一个html文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JSON&JS</title>
</head>
<body>
<script type="text/javascript">
//编写一个js的对象
var user = {
name:"张三",
age:20,
sex:"男"
};
//将js对象转换成json字符串
var str = JSON.stringify(user);
console.log(str);
//将json字符串转换为js对象
var user2 = JSON.parse(str);
console.log(user2.age,user2.name,user2.sex);
</script>
</body>
</html>
控制台结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KMm4Fb4T-1688372883210)(springmvc高级.assets/image-20211110164425026.png)]
2.3 Controller返回JSON数据
2.3.1 导入包
SpringMVC默认用MappingJacksonHttpMessageConverter对json数据进行转换,需要加入jackson的包。
当然工具不止这一个,比如还有阿里巴巴的 fastjson 等等。
我们这里使用Jackson,使用它需要导入它的jar包;
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.1</version>
</dependency>
2.3.2 配置
按照springmvc架构配置好web.xml (servlet+servlet-mapping+filter)和 servlet.xml(视图解析+注解扫描)
在SpringMVC的核心配置文件中开启mvc的注解驱动,此时在HandlerAdaptor中会自动装配一个消息转换器:MappingJackson2HttpMessageConverter,可以将响应到浏览器的Java对象转换为Json格式的字符串。
servlet.xml里添加这一行:
<mvc:annotation-driven/>
如果有报错,再追加如下详细配置(旧版本必配,新版本默认可不配):
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.StringHttpMessageConverter"/>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>
</mvc:message-converters>
</mvc:annotation-driven>
2.3.3 返回json数据 - @ResponseBody
User.java:
public class User {
private String userName;
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getUserPass() {
return userPass;
}
public void setPassword(String password) {
this.userPass = password;
}
private String userPass;
@Override
public String toString() {
return "User{" +
"userName='" + userName + '\'' +
", userPass='" + userPass + '\'' +
'}';
}
public User(String userName, String userPass) {
this.userName = userName;
this.userPass = userPass;
}
//jackson的反序列化需要无参构造函数,不加会报错
public User() {
}
}
controller:
package com.liyw.controller;
import com.liyw.pojo.User;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
//解释json转换
@Controller
public class UserController {
@RequestMapping("/json")
public String jsonPage(){
return "jsonDemo";//经过视图解析器后去转向/WEB-INF/jsp/user.jsp
}
// http://localhost:8080/testResponseUser
// 结果 {"userName":"AMY","userPass":"11111"}
@RequestMapping("/testResponseUser")
@ResponseBody
public User json1() {
//创建一个对象
User user = new User("AMY","11111");
//由于@ResponseBody注解,这里会将对象转成json格式返回;十分方便
return user;
}
}
2.3.4 接受json数据-@RequestBody
- controller
新增一个方法,接受前端提交的json格式的用户提交信息,并以json的结构返回:
// json收+json发
@RequestMapping("/testAjax")
@ResponseBody
public User testAjax(@RequestBody User user){ System.out.println("username:"+user.getUserName()+",password:"+user.getUserPass());
user.setUserName("后端返回的用户名");
return user;
}
提示:除了接受用户的方法,我们还需要一个跳转到对应html界面执行操作的方法。
@RequestMapping("/toUserAddJsonPage")
public String toUserAddJsonPage(){
return "userAddJsonPage";
}
-
核心配置文件.xml
我们想在前端写一个html来完成这里的前后端交互,因此需要修改配置文件中视图解析器的配置。修改后的html文件应位于/WEB-INF/html/xx…html位置
<!--3.视图解析器:DispatcherServlet给他的ModelAndView-->
<!-- 对转向页面的路径解析。 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"
id="InternalResourceViewResolver">
<!--前缀-->
<property name="prefix" value="/WEB-INF/html/"/>
<!--后缀-->
<property name="suffix" value=".html"/>
</bean>
增加配置:
<!--
处理静态资源,例如html、js、css、jpg
若只设置该标签,则只能访问静态资源,其他请求则无法访问
此时必须设置<mvc:annotation-driven/>解决问题
-->
<mvc:default-servlet-handler/>
- html
userAddJsonPage.html
注:
关于静态资源配置在下一部分,这一部分js的可以引用外部网站的js源,在下一节课讲完之后再改成静态资源获取
版本一:使用原生html表单+ajax提交用户表单数据
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Ajax前后端交互</title>
<script type="text/javascript" src="http://code.jquery.com/jquery-3.5.1.min.js"></script>
</head>
<body>
<!--html form表单-->
<form>
<p>用户名:</p>
<input type="text" id="userName">
<p>密码:</p>
<input type="text" id="userPass">
<p><button type="button" onclick="sendJson()">点击我,发送json数据</button></p>
</form>
<script type="text/javascript">
function sendJson() {
// 获取参数
console.log("用户名:"+$("#userName").val())
console.log("密码:"+$("#userPass").val())
//定义一个js对象
var formData = {"userName":$("#userName").val(),"userPass":$("#userPass").val()}
// ajax
$.ajax({
type:"post",
url:"./testAjax",
data:JSON.stringify(formData),//转换成json数据结构
contentType:"application/json",//发送数据的结构 -- 配合后端@RequestBody
dataType:"json",//返回数据类型 -- 配合后端@ResponseBody
success:function (result) {
// 成功的回调
// 拆解返回的数据
var newName = result.userName;
alert("后端返回的用户名:"+newName);
}
})
}
</script>
</body>
</html>
版本二:使用vue(vue.js)+axios提交用户表单数据
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vue+axios前后端交互</title>
<script src="https://unpkg.com/vue@3"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head>
<body>
<!--html form表单-->
<form id="app">
<p>用户名:</p>
<input type="text" id="userName" v-model="userName">
<p>密码:</p>
<input type="text" id="userPass" v-model="userPass">
<p>
<button type="button" @click="sendJson">点击我,发送json数据</button>
</p>
</form>
<script type="text/javascript">
var app = Vue.createApp({
data() {
return {
userName: '',
userPass: '',
}
},
methods:{
sendJson(){
// 获取参数
console.log("用户名:"+this.userName)
console.log("密码:"+this.userPass)
// 配合axios发送
var params = {'userName':this.userName,'userPass':this.userPass}
axios.post('./testAjax',params).then(response=>{
console.log(response.data)
alert("后端返回的用户名:"+response.data.userName);
})
}
}
}).mount("#app");
</script>
</body>
</html>
如果遇到问题,第一步先检查打包/编译出来的包里,依赖是不是正确!
如果遭遇Http 415错误:[org.springframework.web.HttpMediaTypeNotSupportedException: Content type ‘application/x-www-form-urlencoded’ not supported…
如果我们在controller层使用@RequestBody接受数据,那么我们必须要发送application/json的数据格式。我们可以在网络部分查看我们发送的数据格式是否正确。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9k6HzIKK-1688372883211)(springmvc高级.assets/image-20220918160911321.png)]
如果遭遇 HTTP 400 错误的原因常见有:
- 1、前端提交数据的字段名称或者是字段类型和后台的实体类不一致,导致无法封装;
- 2、前端提交的到后台的数据应该是 json 字符串类型,而前端没有将对象转化为字符串类型;
解决方案:
-
1、对照字段名称,类型保证一致性
-
2、使用 stringify 将前端传递的对象转化为字符串
总结:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-siIVG0wl-1688372883211)(springmvc高级.assets/image-20211110173209836.png)]
注:在类上直接使用 @RestController ,这样子,里面所有的方法都只会返回 json 字符串了,不用再每一个都添加@ResponseBody 。
@RequestBody主要用来接收前端传递给后端的json中的数据的(请求体中的数据的)。
@ResponseBody主要用将后端的返回值/对象转换成json数据传递给前端
@RestController
public class UserRestController {
@RequestMapping("/testRestAjax")
public User testAjax(@RequestBody User user){
System.out.println("username:"+user.getUserName()+",password:"+user.getUserPass());
user.setUserName("new Name");
return user;
}
}
注:@RequestBody说明
@RequestBody,它是用来处理前台定义发来的数据Content-Type: 除application/x-www-form-urlencoded外的编码内容,例如application/json, application/xml等;
换句话说我们使用@RequestBody注解的时候,前台的Content-Type必须要改为application/json,如果没有更改,前台会报错415(Unsupported Media Type)。后台日志就会报错Content type ‘application/x-www-form-urlencoded;charset=UTF-8’ not supported
如果是表单提交 Content-type 会是application/x-www-form-urlencoded,可以去掉@RequestBody注解,可以直接用实体类获取对象。
4.文件上传下载
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c9nXbEcU-1688372883212)(springmvc高级.assets/image-20211128163736676.png)]
4.1 文件上传
文件上传要求form表单的请求方式必须为post,并且添加属性enctype=“multipart/form-data”
SpringMVC中将上传的文件封装到MultipartFile对象中,通过此对象可以获取文件相关信息
上传步骤:
a>添加依赖:
<!--fileupload 这个版本会自动引入io包-->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.3</version>
</dependency>
b>在SpringMVC的配置文件中添加配置:
<!-- 配置文件上传,如果没有使用文件上传可以不用配置,当然如果不配,那么配置文件中也不必引入上传组件包 -->
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 默认编码 -->
<property name="defaultEncoding" value="utf-8" />
<!-- 文件大小最大值 -->
<!-- 设定文件上传的最大值为5MB,5*1024*1024 -->
<property name="maxUploadSize" value="5242880"></property>
<!-- 内存中的最大值 设定文件上传时写入内存的最大值,如果小于这个参数不会生成临时文件,默认为10240 -->
<property name="maxInMemorySize" value="40960" />
</bean>
c>控制器方法:
package com.liyw.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.*;
import java.io.File;
@Controller
public class FileUploadController {
@RequestMapping("/fileuploadPage")
public String login1(Model model){
return "fileUpload";//经过视图解析器后去转向/WEB-INF/html/fileUpload.html
}
@RequestMapping(value = "fileupload", method = RequestMethod.POST,
produces = "text/html;charset=utf-8")
public void addPic(
HttpServletResponse response,
HttpServletRequest request,
@RequestParam("uname") String uname,
@RequestParam(value = "file_upload", required = false) MultipartFile file) {
// 上传文件路径
String path = request.getServletContext().getRealPath("/images/");
String filename = file.getOriginalFilename();
File filepath = new File(path, filename);
System.out.println(uname);
System.out.println(filename);
// 判断路径是否存在,如果不存在就创建一个
if (!filepath.getParentFile().exists()) {
filepath.getParentFile().mkdirs();
}
try {
// 将上传文件保存到一个目标文件当中
file.transferTo(new File(path + File.separator + filename));
response.getWriter().write("success");
response.setHeader("Access-Control-Allow-Origin", "*");
}catch (Exception e ){
System.out.println(e);
}
// return "success";
}
}
写法二-返回的是统一返回类型:
@RequestMapping(value = "fileupload", method = RequestMethod.POST,
produces = "application/json;charset=utf-8")
@ResponseBody
public BaseResult addPic(
HttpServletResponse response,
HttpServletRequest request,
@RequestParam("uname") String uname,
@RequestParam(value = "file_upload", required = false) MultipartFile file) {
BaseResult baseResult = new BaseResult();
// 上传文件路径
String path = request.getServletContext().getRealPath("/images/");
String filename = file.getOriginalFilename();
File filepath = new File(path, filename);
System.out.println(uname);
System.out.println(filename);
// 判断路径是否存在,如果不存在就创建一个
if (!filepath.getParentFile().exists()) {
filepath.getParentFile().mkdirs();
}
try {
// 将上传文件保存到一个目标文件当中--
file.transferTo(new File(path + File.separator + filename));
Map map = new HashMap();
map.put("fileName",filename);
baseResult = BaseResult.setResult(ResultCodeEnum.SUCCESS,map);
}catch (Exception e ){
System.out.println(e);
}
return baseResult;
}
d>前端:
方式一:表单提交写法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>提交文件</title>
</head>
<body>
<form id= "uploadForm" action= "./fileupload" method= "post" enctype ="multipart/form-data">
<h1 >用户信息 </h1>
<p >用户名:
<input type ="text" id="uname" name="uname" />
</p>
<p >密码:
<input type ="text" id="upass" name="upass" />
</p>
<p >上传头像: <input type ="file" name="file_upload" /></p>
<input type ="submit" value="上传"/>
</form>
</body>
</html>
方式二:Ajax异步提交方式
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>提交文件</title>
</head>
<body>
<p>AJAX</p>
<form id="ajaxForm" enctype="multipart/form-data">
<h1 >用户信息 </h1>
<p >用户名:
<input type ="text" id="uname" name="uname" />
</p>
<p >密码:
<input type ="text" id="upass" name="upass" />
</p>
<p >上传头像: <input type ="file" name="file_upload" /></p>
<input type="button" value="上传图片" id="upload" onclick="ajaxFileUpload()"/> <br/>
</form>
<script type="text/javascript" src="http://code.jquery.com/jquery-3.5.1.min.js"></script>
<script type="application/javascript">
function ajaxFileUpload(){
var formData = new FormData($('#ajaxForm')[0]);
$.ajax({
type:"post",
url:"./fileupload",
async:false,
contentType: false, //这个一定要写
processData: false, //这个也一定要写,不然会报错
data:formData,
// dataType:'text', //返回类型,有json,text,HTML
success:function(data){
alert("success")
},
});
}
</script>
</body>
</html>
方式三:vue+Axios异步提交方式
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>提交文件</title>
</head>
<body>
<p>vue+axios</p>
<form id="app" enctype="multipart/form-data">
<h1 >用户信息 </h1>
<p >用户名:
<input type ="text" id="uname" name="uname" v-model = "uname"/>
</p>
<p >密码:
<input type ="text" id="upass" name="upass" v-model = "upass"/>
</p>
<p >上传头像: <input type="file" @change="getFile($event)"></p>
<input type="button" value="上传图片" id="upload" @click.prevent="fileUpload"/> <br/>
</form>
<script src="https://unpkg.com/vue@3"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script type="application/javascript">
var app = Vue.createApp({
data(){
return {
uname: '',
upass: '',
file: '',
}
},
methods:{
getFile(event) {
this.file = event.target.files[0];
console.log(this.file);
},
fileUpload(){
//获取参数
console.log(this.uname)
//设置multipart/form-data
let config = {
headers: {
'Content-Type': 'multipart/form-data'
}
}
// 配axios
let formData = new FormData();
formData.append('uname',this.uname);
formData.append('file_upload',this.file);
axios.post('./fileupload',formData,config).then(res =>{
alert(res.data)
})
}
}
}).mount("#app");
</script>
</body>
</html>
5.restful支持
5.1 什么是restful
- REST:Representational State Transfer,表现层资源状态转移。
Restful就是一个资源定位及资源操作的风格。不是标准也不是协议,只是一种风格。基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。
RESTful软件开发理念,RESTful是对http进行非常好的诠释。
- 功能
资源:互联网所有的事物都可以被抽象为资源
资源操作:使用POST、DELETE、PUT、GET,使用不同方法对资源进行操作。
分别对应 添加、 删除、修改、查询。
传统方式操作资源 :通过不同的参数来实现不同的效果!方法单一,post 和 get
http://127.0.0.1/item/queryItem.action?id=1 查询,GET
http://127.0.0.1/item/saveItem.action 新增,POST
http://127.0.0.1/item/updateItem.action 更新,POST
http://127.0.0.1/item/deleteItem.action?id=1 删除,GET或POST
使用RESTful操作资源 :可以通过不同的请求方式来实现不同的效果!如下:请求地址一样,但是功能可以不同!
http://127.0.0.1/item/1 查询,GET
http://127.0.0.1/item 新增,POST
http://127.0.0.1/item 更新,PUT
http://127.0.0.1/item/1 删除,DELETE
5.2 代码演示
5.2.1 准备工作
按照正常创建springmvc项目,注册dispatcherServlet和开启包的扫描注解等等。
5.2.2 controller
package com.liyw.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class RestFulController {
@RequestMapping("/rest/{p1}/{p2}")
public String index(@PathVariable int p1,@PathVariable int p2){
int result = p1+p2;
System.out.println("结果是:"+result);
return "success";
}
@RequestMapping("/rest1/{p1}/{p2}")
public String rest01(@PathVariable int p1,@PathVariable int p2,Model model){
int result = p1+p2;
System.out.println("结果是:"+result);
// Spring MVC会自动实例化一个Model对象用于向视图中传值
model.addAttribute("msg","result:"+result);
//返回视图位置 login.jsp
return "login";
}
}
访问url: http://localhost:8081/rest/2/3
5.2.3 静态资源
如果你的DispatcherServlet拦截“/”,拦截了所有的请求,同时对*.js,*.jpg的访问也就被拦截了。
我们注册的dispatcherServlet会拦截所有的请求,那对于某些静态资源,我们应该怎么处理呢?
比如我们上面demo里
<script type="text/javascript" src="http://code.jquery.com/jquery-3.5.1.min.js">
//改成 我们想引入自己的js
<script type="application/javascript" src="../js/jquery-3.5.1.min.js"></script>
- 现象
前端控制台会显示资源404
后台报错:web.servlet.DispatcherServlet.noHandlerFound No mapping for GET /js/jquery-3.5.1.min.js
- 解决:添加配置:
<!--
处理静态资源,例如html、js、css、jpg
若只设置该标签,则只能访问静态资源,其他请求则无法访问
此时必须设置<mvc:annotation-driven/>解决问题
-->
<mvc:default-servlet-handler/>
或者配置方法二:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-coiIHaLb-1688372883212)(springmvc高级.assets/image-20211111150356204.png)]
<mvc:resources mapping=“/resources/**” location=“/resources/”/>
location元素表示webapp目录下的resources包下的所有文件;
mapping元素表示以/resources开头的所有请求路径,如/resources/a 或者/resources/a/b;
该配置的作用是:DispatcherServlet不会拦截以/static开头的所有请求路径,并当作静态资源
交由Servlet处理。
注意:引入js需要注意项目结构,路径一般从webapp之后开始写。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ipfn9XlN-1688372883213)(springmvc高级.assets/image-20211213145851946.png)]
重启项目测试是否可以正常访问js资源。
5.2.4 restful的好处
-
-
思考:使用路径变量的好处?
-
-
使路径变得更加简洁,很容易读懂;
-- 举例像百度百科url https://baike.baidu.com/item/李开复 自解释性
-
获得参数更加方便,框架会自动进行类型转换。
-
通过路径变量的类型可以约束访问参数,如果类型不一样,则访问不到对应的请求方法,如这里访问是的路径中两个参数本该是int,但是我传了一个string,类似是/commit/1/a,就会失败。
Resolved [org.springframework.web.method.annotation.MethodArgumentTypeMismatchException: Failed to convert value of type ‘java.lang.String’ to required type ‘int’; nested exception is java.lang.NumberFormatException: For input string: “a3”]
-
-
-
6.统一异常处理
6.1 为什么需要统一异常处理
不管是对底层的数据库操作过程,还是业务层的处理过程,还是控制层的处理过程,都不可避免会遇到各种可预知的、不可预知的异常需要处理。出现异常如果不处理,界面展示500之类的服务器错误页面,是不是用户体验很差呢?
但是如果每个过程都单独处理异常,系统的代码耦合度高,工作量大且不好统一,维护的工作量也很大。
那么,能不能将所有类型的异常处理从各处理过程解耦出来,这样既保证了相关处理过程的功能较单一,也实现了异常信息的统一处理和维护?
答案是肯定的。下面将介绍使用Spring MVC统一处理异常的解决和实现过程。
6.2 实现方式
Spring MVC处理异常有3种方式:
(1)使用Spring MVC提供的简单异常处理器SimpleMappingExceptionResolver;
(2)实现Spring的异常处理接口HandlerExceptionResolver 自定义自己的异常处理器;
(3)使用@ExceptionHandler注解实现异常处理;
- SpringMVC提供了一个处理控制器方法执行过程中所出现的异常的接口:HandlerExceptionResolver
HandlerExceptionResolver接口的实现类有:DefaultHandlerExceptionResolver和SimpleMappingExceptionResolver
SpringMVC提供了自定义的异常处理器SimpleMappingExceptionResolver,所以其实方法一和方法二本质上都是直接实现HandlerExceptionResolver。
6.2.1 使用Spring MVC提供的简单异常处理器SimpleMappingExceptionResolver:
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<!-- 定义需要特殊处理的异常,用类名或完全路径名作为key,异常也页名作为值 -->
<property name="exceptionMappings">
<props>
<!--
properties的键表示处理器方法执行过程中出现的异常
properties的值表示若出现指定异常时,设置一个新的视图名称,跳转到指定页面
-->
<prop key="java.lang.ArithmeticException">error</prop>
</props>
</property>
</bean>
@Controller
public class ExceptionController {
@RequestMapping("/simpleException")
public String simpleException(){
System.out.println(1/0);
return "success";
}
}
如果正常运行,转向success.jsp;如果出现运行异常,经过统一异常处理转向error.jsp
6.2.2 自定义自己的异常处理器
实现Spring的异常处理接口HandlerExceptionResolver。
相比于6.2.1的方式,除了可以进行页面跳转之外,它可以通过我们自己定义异常来从controller层中携带更多的信息。我们可以定义多个自定义异常,然后在异常处理器内对不同类型的异常进行不同的处理和界面转向。
- 定义异常信息bean
package com.liyw.dto;
public class UserException extends Exception {
//异常信息
public String message;
public UserException(String message) {
super(message);
this.message = message;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
- 自定义异常处理器
package com.liyw.utils;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class UserExceptionResolver implements HandlerExceptionResolver {
private UserException userException;
public ModelAndView resolveException(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex) {
//如果抛出的是系统自定义的异常则直接转换
if(ex instanceof UserException) {
userException = (UserException) ex;
} else {
//如果抛出的不是系统自定义的异常则重新构造一个未知错误异常
//这里我就也有UserException省事了,实际中应该要再定义一个新的异常
userException = new UserException("系统未知错误");
}
//向前台返回错误信息
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("message", userException.getMessage());
modelAndView.setViewName("error");
return modelAndView;
}
}
-
配置、
<!--异常处理--> <bean class="com.liyw.utils.UserExceptionResolver"></bean>
-
测试
@RequestMapping("/customException") public String customException(Model model, @RequestParam(value="id",required=true) Integer id)throws Exception { User user=null; if(user==null) { throw new UserException("用户不存在"); } return "success"; }
6.2.3 注解方式
@ControllerAdvice
该注解为统一异常处理的核心,是一种作用于控制层的切面通知(Advice),该注解能够将通用的@ExceptionHandler、@InitBinder和@ModelAttributes方法收集到一个类型,并应用到所有控制器上
该类中的设计思路:
- 使用@ExceptionHandler注解捕获指定或自定义的异常;
- 使用@ControllerAdvice集成@ExceptionHandler的方法到一个类中;
- 在该类中定义一个通用的异常捕获方法,便于捕获未定义的异常信息;
- 自定一个异常类,然后在该类中捕获针对项目或业务的异常
- 可以在捕获异常的方法上配合@ResponseBody,将异常信息同样封装为一个统一结果返回。
package com.liyw.utils;
import com.liyw.dto.BaseResult;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
//统一异常处理
@ControllerAdvice
public class ExceptionAutoUtils {
/**-------- 通用异常处理方法 界面跳转--------**/
@ExceptionHandler(Exception.class)
public String error(Exception e, Model model) {
model.addAttribute("message", e);
return "error"; // 通用异常结果
}
/**-------- 指定异常处理方法 --------**/
//@ExceptionHandler用于设置所标识方法处理的异常 此处执行ArithmeticException
@ExceptionHandler(ArithmeticException.class)
//ex表示当前请求处理中出现的异常对象
public String handleArithmeticException(Exception ex, Model model){
model.addAttribute("message", ex);
return "error";
}
// 配合 @ResponseBody返回统一的返回结果json对象
@ExceptionHandler(UserException.class)
@ResponseBody
public BaseResult error() {
return BaseResult.exceptionResult(); // 通用异常结果
}
}
其他部分代码不动,但要注意开启这个文件夹的注解扫描。
如果遇到了UserException,则返回json为:
{"success":false,"code":3,"message":"系统异常","data":null}
注:JSP和Servlet版本导致el功能默认关闭,加入<%@page isELIgnored=“false”%>标签手动开启el功能。
补充:springmvc国际化支持
1.概念
国际化(internationalization:i18n):国际化是指程序在不做任何修改的情况下,就可以在不同的国家或地区和不同的语言环境下,按照当地的语言和格式习惯的显示字符。例如:对于中国大陆的用户,会自动显示中文简体的提示信息,错误信息等;而对于美国的用户,会自动显示英文的提示信息,错误信息。
本地化(Localization):国际化的程序运行在本地机器上时,能够根据本地机器的语言和地区设置相应的字符,这个过程叫做本地化。
中国建设银行网站默认为中文,可选”繁体/ENGLISH”
2.原理
Spring MVC国际化是建立在Java国际化的基础之上的
Spring MVC的国际化的结构:DispatcherServlet会解析一个LocaleResolver接口对象,通过它来决定用户区域,读出对应用户系统设定的语言或者用户选择的语言,确定其国际化。对于DispatcherServlet而言,只能够注册一个LocaleResolver接口对象,LocaleResolver接口的实现类都在org.springframework.web.servlet.i18n包下
Spring MVC也支持国际化的操作,可使用Spring MVC提供的语言区域解析器接口LocaleResolver,该接口常用实现类:
-
AcceptLanguageLocaleResolver:控制器无需写额外的内容,可以不用显示配置
-
SessionLocaleResolver:使用Session传输语言环境,根据用户session的变量读取区域设置,它是可变的,如果session没有设置,那么它也会使用开发者设置的默认值
-
CookieLocaleResolver:使用Cookie传送语言环境,根据Cookie数据获取国际化信息,如果用户禁止Cookie或者没有设置,它会根据accept-language HTTP头部确定默认区域。
3.基于SessionLocaleResolver的国际化实现
1.在resources文件夹下新建资源文件messages_en_US.properties、messages_zh_CN.properties
messages_en_US.properties:
msg=It's English version.
messages_zh_CN.properties
msg=这是中文版本
注:需要开启idea如下设置进行转换,否则此处需要进行unicode中文转码
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-N2FYkXx3-1688372883213)(springmvc高级.assets/image-20211122205318747.png)]
2.配置文件 sevelet.xml
<!--国际化-->
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<!-- 国际化信息所在的文件名,根据ResourceBundleMessageSource类加载资源文件
.\src\main\resources\messages\messages_en_US.properties -->
<property name="basename" value="messages/messages" />
<!-- 如果在国际化资源文件中找不到对应代码的信息,就用这个代码作为名称 -->
<property name="useCodeAsDefaultMessage" value="true" />
<property name="defaultEncoding" value="UTF-8"/>
</bean>
<mvc:interceptors>
<!-- 国际化操作拦截器 如果采用基于(请求/Session/Cookie)则必需配置 -->
<bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor" />
</mvc:interceptors>
<bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver" />
基于HttpSession的国际化实现使用的是LocaleResolver接口的SessionLocaleResolver实现类,SessionLocaleResolver不是默认的语言区域解析器,需要对其进行显式配置。且Spring中LocaleResolver的bean名称必须为localeResolver,因为Spring读取该bean时是通过该名称读取的;
3.controller
package com.liyw.controller;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.i18n.SessionLocaleResolver;
import org.springframework.web.servlet.support.RequestContext;
import javax.servlet.http.HttpServletRequest;
import java.util.Locale;
@Controller
public class MyController {
@RequestMapping(value="/test",
method={RequestMethod.POST,RequestMethod.GET},
produces="text/html;charset=UTF-8;"
)
@ResponseBody
public String test(HttpServletRequest request,
@RequestParam(value="langType", defaultValue="zh")
String langType){
if(langType.equals("zh")){
Locale locale = new Locale("zh", "CN");
request.getSession().setAttribute(SessionLocaleResolver.LOCALE_SESSION_ATTRIBUTE_NAME,locale);
}
else if(langType.equals("en")){
Locale locale = new Locale("en", "US");
request.getSession().setAttribute(SessionLocaleResolver.LOCALE_SESSION_ATTRIBUTE_NAME,locale);
}else{
request.getSession().setAttribute(SessionLocaleResolver.LOCALE_SESSION_ATTRIBUTE_NAME, LocaleContextHolder.getLocale());
}
//从后台代码获取国际化信息
RequestContext requestContext = new RequestContext(request);
String msg = requestContext.getMessage("msg");
System.out.println(msg);
return msg;
}
}
注:
使用@RequestMapping + @ResponseBody String返回中文乱码
原因分析: 确定的是(经过多次测试的结果)只有当返回值是 String时才会出现中文乱码,而当返回值是Map<String, Object>或者是其它类型时,并没有中文乱码的出现.
然后找原因: 原因是这可以说是spring mvc的一个bug,spring MVC有一系列HttpMessageConverter去处理用@ResponseBody注解的返回值,如返回list或其它则使用 MappingJacksonHttpMessageConverter,返回string,则使用 StringHttpMessageConverter,而这个convert使用的是字符集是iso-8859-1,而且是final的。所以在当返回json中有中文时会出现乱码。
因此在demo中加上了:produces=“text/html;charset=UTF-8;” 或 produces={“text/html;charset=UTF-8;”,“application/json;”} 可以解决乱码。
正常在项目界面中交互不会出现这个问题。
项目结构:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-13ODYQVd-1688372883214)(springmvc高级.assets/image-20211122205142956.png)]
测试结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Uj1N3V74-1688372883214)(springmvc高级.assets/image-20211122205552566.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0quN5Cer-1688372883215)(springmvc高级.assets/image-20211122210137886.png)]
4.补充说明
一般项目国际化分为前端国际化与后端国际化。springmvc的国际化主要做的是后端国际化,也就是对一些后端返回的提示信息做国际化处理。用户存储的数据一般是不需要做国际化的,所以在界面我们只需要正常渲染就可以。
而前端国际化主要是对界面展示的静态资源做国际化处理(也有前端国际化去翻译后端返回消息的,但是不建议这么做),最常见的处理思路:
1、针对不同的语言,各写一套界面。
2、使用配置文件的方式,使用的是同一套界面,根据语言的不同加载对应的配置文件。(推荐)
前端国际化方案常用的是jQuery的国际化插件jQuery.i18n.properties ,它是一款轻量级的插件,压缩后仅 4kb,api也比较简单,它的国际化资源文件以“.properties”为后缀,包含了各语言相关的键值对。有些前端框架也有自己对应的国际化插件,比如vue的国际化插件 vue-i18n。
}
else if(langType.equals(“en”)){
Locale locale = new Locale(“en”, “US”);
request.getSession().setAttribute(SessionLocaleResolver.LOCALE_SESSION_ATTRIBUTE_NAME,locale);
}else{
request.getSession().setAttribute(SessionLocaleResolver.LOCALE_SESSION_ATTRIBUTE_NAME, LocaleContextHolder.getLocale());
}
//从后台代码获取国际化信息
RequestContext requestContext = new RequestContext(request);
String msg = requestContext.getMessage(“msg”);
System.out.println(msg);
return msg;
}
}
注:
使用@RequestMapping + @ResponseBody String返回中文乱码
原因分析: 确定的是(经过多次测试的结果)只有当返回值是 String时才会出现中文乱码,而当返回值是Map<String, Object>或者是其它类型时,并没有中文乱码的出现.
**然后找原因:** 原因是这可以说是spring mvc的一个bug,spring MVC有一系列HttpMessageConverter去处理用@ResponseBody注解的返回值,如返回list或其它则使用 MappingJacksonHttpMessageConverter,返回string,则使用 StringHttpMessageConverter,而这个convert使用的是字符集是iso-8859-1,而且是final的。所以在当返回json中有中文时会出现乱码。
因此在demo中加上了:produces="text/html;charset=UTF-8;" 或 produces={"text/html;charset=UTF-8;","application/json;"} 可以解决乱码。
正常在项目界面中交互不会出现这个问题。
项目结构:
[外链图片转存中...(img-13ODYQVd-1688372883214)]
测试结果:
[外链图片转存中...(img-Uj1N3V74-1688372883214)]
[外链图片转存中...(img-0quN5Cer-1688372883215)]
#### 4.补充说明
一般项目国际化分为前端国际化与后端国际化。springmvc的国际化主要做的是后端国际化,也就是对一些后端返回的提示信息做国际化处理。用户存储的数据一般是不需要做国际化的,所以在界面我们只需要正常渲染就可以。
而前端国际化主要是对界面展示的静态资源做国际化处理(也有前端国际化去翻译后端返回消息的,但是不建议这么做),最常见的处理思路:
1、针对不同的语言,各写一套界面。
2、使用配置文件的方式,使用的是同一套界面,根据语言的不同加载对应的配置文件。(推荐)
前端国际化方案常用的是jQuery的国际化插件jQuery.i18n.properties ,它是一款轻量级的插件,压缩后仅 4kb,api也比较简单,它的国际化资源文件以“.properties”为后缀,包含了各语言相关的键值对。有些前端框架也有自己对应的国际化插件,比如vue的国际化插件 vue-i18n。