目录
基于SpringMVC框架统一处理异常【续】
统一处理异常的方法需要添加@ExceptionHandler
注解,该注解的源代码:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExceptionHandler {
/**
* Exceptions handled by the annotated method. If empty, will default to any
* exceptions listed in the method argument list.
*/
Class<? extends Throwable>[] value() default {};
}
通过以上源代码可以看到:
- 该注释中存在名为
value
的属性; - 在所有注解中,
value
是默认的属性,如果需要配置该属性的值,当需要配置1个属性的值时,是不需要显式的写出value=
的; - 该
value
属性的值的类型是Class<? extends Throwable>[]
,即“异常类的数组”; - 在配置任何注解属性时,如果注解的属性值是数组格式的,当前配置值时数组元素只有1个值时,不需要使用数组,直接使用元素值即可;
default {}
表示默认值是一个空数组。
假设,使用RuntimeException
类作为以上属性的值,则以下几种配置是完全等效的:
@ExceptionHandler(value={RuntimeException.class})
@ExceptionHandler(value=RuntimeException.class)
@ExceptionHandler({RuntimeException.class})
@ExceptionHandler(RuntimeException.class)
关于该属性的作用:
Exceptions handled by the annotated method. If empty, will default to any exceptions listed in the method argument list.
意思是:
被添加了注解的方法处理的异常。如果该属性的值为空,默认表示处理方法的参数列表中的所有异常。
所以,该属性的作用是“当前处理异常的方法到底需要处理哪些异常,如果没有配置属性值,以方法的参数中声明的异常类型为准”!
例如,处理异常的方法是:
@ExceptionHandler
public R handleException(Throwable e) {
}
显然,@ExceptionHandler
没有配置value
属性,当前方法处理的异常的种类就是方法的参数Throwable
类型的,也就是“所有类型的异常都将由当前方法进行处理”!
如果将以上代码调整为:
@ExceptionHandler(ServiceException.class)
public R handleException(Throwable e) {
}
则以上方法只处理ServiceException
类型的异常!
通常,在统一处理异常时,建议显式的指定需要处理的异常种类!
补:不允许注册相同的手机号码
如果需要判断手机号码是否已经被注册,并且抛出对应的异常,需要完成的开发任务大致有:
- 在业务层必须能够判断得出某手机号码是否已经被注册;
- 必须创建对应的异常类,当手机号码已经被注册时,将抛出该异常的对象;
- 在统一处理异常的代码区域,添加对“手机号码已经被注册”的异常的处理。
正常来看,在当前项目中已经根据用户名查询了用户信息,来检验用户名是否已经被注册,但是,这项检查与手机号码是否已经被注册是没有任何关系的!所以,关于手机号码是否已经被注册,需要重新检查!则应该在持久层接口UserMapper.java
中添加新的查询方法:
/**
* 根据手机号码查询用户数据
*
* @param phone 手机号码
* @return 匹配的用户数据,如果没有匹配的数据则返回null
*/
User findByPhone(String phone);
然后,在UserMapper.xml
中配置以上查询映射的SQL语句:
<select id="findByPhone" resultMap="BaseResultMap">
SELECT * FROM user WHERE phone=#{phone}
</select>
完成后,应该及时测试功能是否正确,所以,在UserMapperTests
中编写并执行单元测试:
@Test
void findByPhone() {
String phone = "13000130000";
User user = mapper.findByPhone(phone);
System.out.println("user=" + user);
}
接下来,需要创建新的异常类,表示“手机号码已经被注册”,则在cn.tedu.straw.api.user.ex
包中创建PhoneDuplicateException
异常类,继承自ServiceException
类:
/**
* 手机号码已经被注册
*/
public class PhoneDuplicateException extends ServiceException {
// 构造方法
}
并且,在UserServiceImpl
类中,在处理“注册”时,添加判断:先调用持久层新增的findByPhone()
来查询用户数据,如果查询结果不为null
,则表示手机号码已经被注册,则抛出异常:
完成后,应该再次调用UserServiceTests
中原有的单元测试,检查修改后的方法是否可以正常运行!
最后,在统一处理异常的过程中,添加对PhoneDuplicateException
的处理:
一、调整R类以增加代码的可读性
为了增加代码的可读性,应该将所使用到的“状态码”都使用一些常量来表示!同时,为了便于管理这些量,可以在R
类中使用内部接口进行封装:
public interface State {
/**
* 错误:用户名已经被注册
*/
Integer ERR_USERNAME_DUPLICATE = 4000;
/**
* 错误:手机号码已经被注册
*/
Integer ERR_PHONE_DUPLICATE = 4001;
/**
* 错误:邀请码错误
*/
Integer ERR_INVITE_CODE = 4002;
/**
* 错误:班级已经被禁用
*/
Integer ERR_CLASS_DISABLED = 4003;
/**
* 错误:插入数据失败
*/
Integer ERR_INSERT = 4004;
/**
* 错误:未知错误
*/
Integer ERR_UNKNOWN = 9000;
}
为了更加快捷的创建R
类的对象,还可以添加一些方法:
public R() {
super();
}
public R(Integer state) {
this.state = state;
}
public static R ok() {
return new R(1);
}
public static R failure(Integer state, Throwable e) {
R r = new R();
r.setState(state);
r.setMessage(e.getMessage());
return r;
}
最后,R
类的完整代码:
package cn.tedu.straw.api.user.util;
import lombok.Data;
import java.io.Serializable;
@Data
public class R implements Serializable {
/**
* 响应时的状态码
*/
private Integer state;
/**
* 操作失败时的提示信息
*/
private String message;
public R() {
super();
}
public R(Integer state) {
this.state = state;
}
public static R ok() {
return new R(1);
}
public static R failure(Integer state, Throwable e) {
R r = new R();
r.setState(state);
r.setMessage(e.getMessage());
return r;
}
public interface State {
/**
* 错误:用户名已经被注册
*/
Integer ERR_USERNAME_DUPLICATE = 4000;
/**
* 错误:手机号码已经被注册
*/
Integer ERR_PHONE_DUPLICATE = 4001;
/**
* 错误:邀请码错误
*/
Integer ERR_INVITE_CODE = 4002;
/**
* 错误:班级已经被禁用
*/
Integer ERR_CLASS_DISABLED = 4003;
/**
* 错误:插入数据失败
*/
Integer ERR_INSERT = 4004;
/**
* 错误:未知错误
*/
Integer ERR_UNKNOWN = 9000;
}
}
同时,GlobalExceptionHandler
中处理异常的代码调整为:
package cn.tedu.straw.api.user.controller;
import cn.tedu.straw.api.user.ex.*;
import cn.tedu.straw.api.user.util.R;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
* 全局处理异常的类
*/
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ServiceException.class)
public R handleException(Throwable e) {
if (e instanceof UsernameDuplicateException) {
return R.failure(R.State.ERR_USERNAME_DUPLICATE, e);
} else if (e instanceof InviteCodeException) {
return R.failure(R.State.ERR_INVITE_CODE, e);
} else if (e instanceof ClassDisabledException) {
return R.failure(R.State.ERR_CLASS_DISABLED, e);
} else if (e instanceof InsertException) {
return R.failure(R.State.ERR_INSERT, e);
} else if (e instanceof PhoneDuplicateException) {
return R.failure(R.State.ERR_PHONE_DUPLICATE, e);
} else {
return R.failure(R.State.ERR_UNKNOWN, e);
}
}
}
二、通过Spring Validation检查数据的有效性
在任何软件中,都应该将用户填写/选择并提交的数据视为“不可靠的数据”,以“用户名”为例,字符串的长度太短或太长的都应该是不允许的,其它的各种数据都应该存在相应一些规则,用于约定数据的基本有效性(格式是否正确、是否存在明确的错误)!
约定:在服务器端,当控制器接收到客户端的请求参数后,第一时间就应该检查这些请求参数的基本有效性,如果不符合所设定的数据格式或简易规则,应该直接响应错误,不予进一步处理!
对于这些数据的处理,通常可以结合Java中的原有的API或判断语句来实现,例如通过数值大小的判断来验证“年龄”属性的值是否合规,也可以通过String
的API来判断用户名的长度、密码的长度等,如果数据规则较复杂,通过API不易于实现,还可以通过正则表达式来进行验证,例如用户名的格式、电子邮箱的格式等。
Spring Validation框架是专门用于检验数据有效性的框架,该框架的依赖的参考代码:
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-validation -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<version>2.3.3.RELEASE</version>
</dependency>
关于Spring Validation的用法:
- 在需要验证的对象的属性之前添加验证注解,并通过注解配置验证规则和错误提示信息;
- 在控制器的处理请求的方法的参数列表中,在被验证的参数之前添加
@Valid
/@Validated
注解,表示该参数将被Spring Validation进行验证; - 在控制器的处理请求的方法的参数列表中,紧随被验证的参数之后,使用
BindingResult
接收验证失败的结果。
以本次执行的“注册”为例,在控制器的处理请求的方法中,是使用RegisterStudentDTO
接收客户端提交的请求参数的,所以,需要在RegisterStudentDTO
的属性之前添加注解,以配置验证规则和错误提示信息,例如:
然后,在处理注册的控制器方法中,为接收请求参数的方法参数添加注解:
并且,在这个参数之后,添加BindingResult
类型的参数,用于接收验证失败的结果:
最后,在方法中,判断验证是否失败,并简单的处理结果:
完成后,即可尝试测试运行,在测试时,可以故意不填写username
属性的值,例如请求URL为 http://localhost:8080/v1/users/reg?username=&password=1234&phone=13400134000&inviteCode=JSD2005-666666,提交访问后,控制台会输出显示:
由于出错时并没有阻止程序运行,所以,即使请求参数有误,程序仍会继续向后执行,甚至可能成功注册!
关于配置验证规则的注解:
@NotNull
:不允许为null
值,当服务器端要求提供某参数,客户端完全没有提交这个值时,则验证失败,例如在控制器的参数列表中添加了String username
,但是,客户端提交的请求参数中根本没有名为username
的参数;@NotEmpty
:不允许为空值,例如""
即为空值,则验证失败;@NotBlank
:不允许为空白,仅能添加在String
类型的属性之前,要求字符串调用trim()
之后仍不为""
空值,否则,验证失败,例如" "
的验证将是失败的;@Pattern
:通过正则表达式验证,使用该注解时,必须配置regexp
属性,用于配置正则表达式;
另外,还有其它注解:
在当前项目中,由于“注册”时只需要填写邀请码、昵称、手机号码、密码即可,则只需要对这几个属性配置验证规则:
注意:由于软件的“注册”页面中并没有要求填写用户名,在处理注册的业务中,可以直接将手机号作为用户名:
在控制器的处理请求的方法中,被验证的参数需要添加@Valid
注解,或者添加@Validated
也是可以的,在Spring的官网的演示案例中,使用的是@Valid
注解。
注意:Spring Validation推荐对整个对象进行验证,而不是对某1个参数的值进行验证,所以,一般会将客户端提交的请求参数进行封装,使用封装的类型作为控制器处理请求的方法的参数!
当需要获取验证失败的结果时(验证成功时代码是可以顺利执行,不需要判断或处理),需要使用BindingResult
接口类型的对象,注意:该对象应该作为处理请求的方法的参数,且必须添加在被验证数据之后!也就是说,被验证对象和BingdingResult
对象都必须在处理请求的方法的参数列表中,且这2个参数是连在一起声明的,中间不会有其它参数!
正例(BindingResult
紧随被验证的参数之后):
public R reg(@Valid RegisterStudentDTO registerStudentDTO, BingdingResult bingResult, HttpServletRequest request) {}
反例(BingdingResult
与被验证的参数之间有其它参数):
public R reg(@Valid RegisterStudentDTO registerStudentDTO, HttpServletRequest request, BingdingResult bingResult) {}
在通过BindingResult
判断错误并获取错误信息后,必须阻止程序继续向后执行!可以继续创建异常类,用于表示“非法请求参数”的错误,并且,在验证失败时,抛出该异常的对象,后续,在统一处理异常的代码中,对这种异常进行处理即可!
先在cn.tedu.straw.api.user.ex
包下创建IllegalParameterException
类,继承自ServiceException
类:
/**
* 非法的请求参数
*/
public class IllegalParameterException extends ServiceException {
// 构造方法
}
然后,在控制器中,如果验证失败,则抛出异常:
在处理异常之前,还应该在R.State
中声明新的常量:
最后,在GlobalExceptionHandler
中处理异常的方法中,补充处理IllegalParameterException
:
全部完成后,重启项目,在浏览器中通过 http://localhost:8080/v1/users/reg?inviteCode=&phone=&nickname=&password= 进行测试访问。
三、搭建Eureka注册中心
当使用分布式集群后,在整个集群中将存在若干个服务器,每个服务器的任务可能都是不同的,并且,服务器之间可能需要彼此访问,在访问之前就必须明确目标服务器的位置,为了更好的统一管理服务器的相关参数(例如服务器的IP地址、端口号、应用程序名称等),就需要使用“发现”框架!
所有的“发现”框架的实现原理大致是:各功能服务器启动后,会在“发现”服务器中进行“注册”,将自身的参数(例如IP地址、端口号、应用程序名称等)提交给“发现”服务器,“发现”服务器会将所有功能服务器的注册信息进行汇总,得到“注册表”,并将注册表发给所有功能服务器,则各功能服务器得到“注册表”之后,就知道其它服务器的参数了!
Eureka是Spring Cloud家族中的1个“发现”框架,用于搭建注册中心!
关于Eureka注册中心的搭建,首先,在当前父项目中创建新的子模块项目straw-eureka-server
:
创建好的项目中,在pom.xml
中已经添加了Spring Cloud家族的依赖的管理:
其版本是:
实际依赖的只是Spring Cloud家族中的eureka-server
:
首先,应该将Spring Cloud家族的依赖管理移动到父项目中(版本暂时使用Hoxton.SR3
):
然后,在straw-eureka-server
的pom.xml
中,设置父级项目,设置应用spring-boot-starter-web
和eureka-server
依赖:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.tedu</groupId>
<artifactId>straw</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>cn.tedu</groupId>
<artifactId>straw-eureka-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>straw-eureka-server</name>
<dependencies>
<!-- Eureka Server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<!-- web:允许项目启动在Tomcat -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
然后,删除项目中的test
文件夹!(因为SpringBoot项目默认在test
中有测试类,而当前项目已经删了单元测试相关的依赖,所以,如果不删除test
文件夹中的文件,就会报错,当前项目也并不需要编写Java代码,所以,不需要使用单元测试)
作为一个Eureka注册中心项目,需要在配置类(启动类就是配置类)的声明之前添加@EnableEurekaServer
注解:
然后,在application.properties
中添加配置信息:
# 指定当前项目部署到服务器的哪个端口号上运行
# Eureka Server的默认端口号是8761
server.port=8761
# 当前项目不要从Eureka Server抓取注册表
eureka.client.fetch-registry=false
# 当前项目不要在Eureka Server中注册
eureka.client.register-with-eureka=false
因为每一个Eureka Server也是一个Eureka Client,默认都会自动连接Eureka Server进行注册并抓取注册,以上2个属性的作用就是分别设置“不要注册”和“不要抓取注册表”。
到此,Eureka Server项目就已经搭建好,启动项目,在浏览器中通过 http://localhost:8761 即可访问到Eureka的状态页面:
在运行一段时间后,Eureka状态页面会显示红色的警告,可以无视:
四、Eureka客户端注册
在集群中,需要到Eureka Server注册的其它服务器的项目都称之为Eureka Client(客户端),每个Eureka Client都需要在pom.xml
中添加eureka-client
的依赖!
所以,在straw-api-user
的pom.xml
中添加eureka-client
的依赖:
关于
eureka-client
的依赖,可以先将straw-eureka-server
中关于eureka-server
的依赖复制过去,然后将server
改为client
即可。
由于Eureka Client是可以自动配置的(该自动配置是SpringBoot完成的,只要添加了依赖,就会自动配置),所以,当添加依赖后,可以不做进一步的配置,直接运行straw-api-user
项目,在Eureka状态列表中可以看到
则表示straw-api-user
在Eureka Server上已经注册!
注意:每个Eureka Client在启动时都会自动尝试找Eureka Server执行注册,在后续的使用中,应该最先启动straw-eureka-server
项目,再启动其它项目,否则,如果其它Eureka Client项目是先启动的,而Eureka Server尚未启动,则在控制台会提示错误信息,例如:
注意:即使出现以上错误,也不影响该项目单机运行(如果某些功能需要与集群中的其它服务器交互,由于没法从Eureka Server中获取注册表,就不知道其它服务器的信息,则无法连接)。
关于在Eureka状态页面显示的信息,由于没有进行详细配置,所以,显示的信息可能不统一,甚至后续的相互调用也存在不确定因素,所以,应该在straw-api-user
的application.properties
中添加配置:
# 显式的配置当前项目部署到服务器时运行在哪个端口号
server.port=8080
# 应用程序名称
spring.application.name=api-user
# 是否使用IP地址来注册,如果是,则注册IP地址,如果否,则注册主机名
eureka.instance.prefer-ip-address=true
# 当前项目在Eureka Server中注册的主机名,仅当prefer-ip-address设置为false时有效
eureka.instance.hostname=localhost
# 当前项目在Eureka Server中注册的IP地址,当prefer-ip-address设置为true时有效
eureka.instance.ip-address=127.0.0.1
# 当前项目在Eureka状态页显示的信息
eureka.instance.instance-id=${spring.application.name}@${eureka.instance.ip-address}:${server.port}
另外,在早期的版本中,Eureka Client项目还需要在配置类的声明之前添加@EnableEurekaClient
注解,目前已经不需要了!
附:常用的正则表达式
以JavaScript中应用正则表达式为例(原文地址:https://www.cnblogs.com/raphael1982/p/8012634.html):
1. 用户名正则
//用户名正则,4到16位(字母,数字,下划线,减号)
var uPattern = /^[a-zA-Z0-9_-]{4,16}$/;
//输出 true
console.log(uPattern.test("iFat3"));
2. 密码强度正则
// 密码强度正则,最少6位,包括至少1个大写字母,1个小写字母,1个数字,1个特殊字符
var pPattern = /^.*(?=.{6,})(?=.*\d)(?=.*[A-Z])(?=.*[a-z])(?=.*[!@#$%^&*? ]).*$/;
// 输出 true
console.log("=="+pPattern.test("iFat3#"));
3. 整数正则
//正整数正则
var posPattern = /^\d+$/;
//负整数正则
var negPattern = /^-\d+$/;
//整数正则
var intPattern = /^-?\d+$/;
//输出 true
console.log(posPattern.test("42"));
//输出 true
console.log(negPattern.test("-42"));
//输出 true
console.log(intPattern.test("-42"));
4. 数字正则
可以是整数也可以是浮点数
//正数正则
var posPattern = /^\d*\.?\d+$/;
//负数正则
var negPattern = /^-\d*\.?\d+$/;
//数字正则
var numPattern = /^-?\d*\.?\d+$/;
console.log(posPattern.test("42.2"));
console.log(negPattern.test("-42.2"));
console.log(numPattern.test("-42.2"));
5. Email正则
//Email正则
var ePattern = /^([A-Za-z0-9_\-\.])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/;
//输出 true
console.log(ePattern.test(<a href="mailto:65974040@qq.com">65974040@qq.com</a>));
6. 手机号码正则
//手机号正则
var mPattern = /^((13[0-9])|(14[5|7])|(15([0-3]|[5-9]))|(18[0,5-9]))\d{8}$/;
//输出 true
console.log(mPattern.test("18600000000"));
7. 身份证号正则
//身份证号(18位)正则
var cP = /^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/;
//输出 true
console.log(cP.test("11010519880605371X"));
8. URL正则
((http|ftp|https)://)(([a-zA-Z0-9\._-]+\.[a-zA-Z]{2,6})|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}))(:[0-9]{1,4})*(/[a-zA-Z0-9\&%_\./-~-]*)?
赤:协议
橙:字母host,前半部分(+号及以前)host、二级域名,后半部分表示一定有类似.cn、.com、.net的跟在后面
黄:域名
绿:IP host,可以进一步简化([0-9]{1,3}(\.[0-9]{1,3}){3})
橙、绿组合成hostname
青:端口
蓝:/path及后面内容,第一个/匹配host后紧跟的斜杠,host后可能有 / ,/ 后可能还有n多字符,当然,也可能没有了。最后四个字符/-~-有些问题,/的ASCII码为47,~的为126,(ASCII码表)这之间包括了数字、大小写字母(与前面的重复),还有些如<>=?{},这些在正常的url中也会被编码的,不会出现在url中,中括号中的 - 如果不成组就表示 - 字符(一般放在中括号两边或加转义)
容易理解,可以满足大部分需求,不能匹配url+锚点、ftp有user:pass@host的情况
9. IPv4地址正则
//ipv4地址正则
var ipP = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
//输出 true
console.log(ipP.test("115.28.47.26"));
10. 十六进制颜色正则
//RGB Hex颜色正则
var cPattern = /^#?([a-fA-F0-9]{6}|[a-fA-F0-9]{3})$/;
//输出 true
console.log(cPattern.test("#b8b8b8"));
11. 日期正则
//日期正则,简单判定,未做月份及日期的判定
var dP1 = /^\d{4}(\-)\d{1,2}\1\d{1,2}$/;
//输出 true
console.log(dP1.test("2017-05-11"));
//输出 true
console.log(dP1.test("2017-15-11"));
//日期正则,复杂判定
var dP2 = /^(?:(?!0000)[0-9]{4}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1[0-9]|2[0-8])|(?:0[13-9]|1[0-2])-(?:29|30)|(?:0[13578]|1[02])-31)|(?:[0-9]{2}(?:0[48]|[2468][048]|[13579][26])|(?:0[48]|[2468][048]|[13579][26])00)-02-29)$/;
//输出 true
console.log(dP2.test("2017-02-11"));
//输出 false
console.log(dP2.test("2017-15-11"));
//输出 false
console.log(dP2.test("2017-02-29"));
12. QQ号码正则
//QQ号正则,5至11位
var qqPattern = /^[1-9][0-9]{4,10}$/;
//输出 true
console.log(qqPattern.test("65974040"));
13. 微信号正则
//微信号正则,6至20位,以字母开头,字母,数字,减号,下划线
var wxPattern = /^[a-zA-Z]([-_a-zA-Z0-9]{5,19})+$/;
//输出 true
console.log(wxPattern.test("RuilongMao"));
14. 车牌号正则
//车牌号正则
var cPattern = /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}[A-Z0-9]{4}[A-Z0-9挂学警港澳]{1}$/;
//输出 true
console.log(cPattern.test("京K39006"));
15. 包含中文正则
//包含中文正则
var cnPattern = /[\u4E00-\u9FA5]/;
//输出 true
console.log(cnPattern.test("42度"));