mirror of https://github.com/elunez/eladmin
Merge branch 'master' into deploy
commit
6ab1caac67
12
README.md
12
README.md
|
@ -1,4 +1,4 @@
|
|||
<h1 style="text-align: center">EL-ADMIN 后台管理系统</h1>
|
||||
<h1 style="text-align: center">ELADMIN 后台管理系统</h1>
|
||||
<div style="text-align: center">
|
||||
|
||||
[](https://github.com/elunez/eladmin/blob/master/LICENSE)
|
||||
|
@ -11,9 +11,9 @@
|
|||
#### 项目简介
|
||||
一个基于 Spring Boot 2.1.0 、 Spring Boot Jpa、 JWT、Spring Security、Redis、Vue的前后端分离的后台管理系统
|
||||
|
||||
**开发文档:** [https://el-admin.vip](https://el-admin.vip)
|
||||
**开发文档:** [https://eladmin.vip](https://eladmin.vip)
|
||||
|
||||
**体验地址:** [https://el-admin.vip/demo](https://el-admin.vip/demo)
|
||||
**体验地址:** [https://eladmin.vip/demo](https://eladmin.vip/demo)
|
||||
|
||||
**账号密码:** `admin / 123456`
|
||||
|
||||
|
@ -87,9 +87,9 @@
|
|||
|
||||
#### 特别鸣谢
|
||||
|
||||
- 感谢 [JetBrains](https://www.jetbrains.com/) 提供的非商业开源软件开发授权
|
||||
- 感谢 [七牛云](https://portal.qiniu.com/signup?utm_source=kaiyuan&utm_media=ELADMIN) 提供的免费云存储与CDN加速支持
|
||||
|
||||
- 感谢 [七牛云](https://www.qiniu.com/) 提供的免费云存储与CDN加速支持
|
||||
- 感谢 [JetBrains](https://www.jetbrains.com/) 提供的非商业开源软件开发授权
|
||||
|
||||
- 感谢 [PanJiaChen](https://github.com/PanJiaChen/vue-element-admin) 大佬提供的前端模板
|
||||
|
||||
|
@ -102,7 +102,7 @@
|
|||
- 感谢 [d15801543974](https://github.com/d15801543974) 大佬提供的基于注解的通用查询方式
|
||||
|
||||
#### 项目捐赠
|
||||
项目的发展离不开你的支持,请作者喝杯咖啡吧☕ [Donate](https://el-admin.vip/donation/)
|
||||
项目的发展离不开你的支持,请作者喝杯咖啡吧☕ [Donate](https://eladmin.vip/donation/)
|
||||
|
||||
#### 反馈交流
|
||||
- QQ交流群:一群:<strike>891137268</strike> 、二群:<strike>947578238</strike>、三群:659622532
|
|
@ -28,7 +28,7 @@ import java.lang.annotation.Target;
|
|||
* 应该是 @DataPermission(joinName = "dept", fieldName = "id")
|
||||
* </p>
|
||||
* @author Zheng Jie
|
||||
* @website https://el-admin.vip
|
||||
* @website https://eladmin.vip
|
||||
* @date 2020-05-07
|
||||
**/
|
||||
@Target(ElementType.TYPE)
|
||||
|
|
|
@ -106,7 +106,7 @@ public class RedisConfig extends CachingConfigurerSupport {
|
|||
@Override
|
||||
public KeyGenerator keyGenerator() {
|
||||
return (target, method, params) -> {
|
||||
Map<String,Object> container = new HashMap<>(4);
|
||||
Map<String,Object> container = new HashMap<>(8);
|
||||
Class<?> targetClassClass = target.getClass();
|
||||
// 类地址
|
||||
container.put("class",targetClassClass.toGenericString());
|
||||
|
|
|
@ -21,7 +21,7 @@ import org.springframework.stereotype.Component;
|
|||
|
||||
/**
|
||||
* @author Zheng Jie
|
||||
* @website https://el-admin.vip
|
||||
* @website https://eladmin.vip
|
||||
* @description
|
||||
* @date 2020-05-18
|
||||
**/
|
||||
|
|
|
@ -77,7 +77,7 @@ public class SwaggerConfig {
|
|||
private ApiInfo apiInfo() {
|
||||
return new ApiInfoBuilder()
|
||||
.description("一个简单且易上手的 Spring boot 后台管理框架")
|
||||
.title("EL-ADMIN 接口文档")
|
||||
.title("ELADMIN 接口文档")
|
||||
.version("2.6")
|
||||
.build();
|
||||
}
|
||||
|
|
|
@ -23,10 +23,11 @@ import me.zhengjie.utils.ThrowableUtil;
|
|||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.validation.FieldError;
|
||||
import org.springframework.validation.ObjectError;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
import java.util.Objects;
|
||||
import static org.springframework.http.HttpStatus.*;
|
||||
|
||||
/**
|
||||
|
@ -95,11 +96,10 @@ public class GlobalExceptionHandler {
|
|||
public ResponseEntity<ApiError> handleMethodArgumentNotValidException(MethodArgumentNotValidException e){
|
||||
// 打印堆栈信息
|
||||
log.error(ThrowableUtil.getStackTrace(e));
|
||||
String[] str = Objects.requireNonNull(e.getBindingResult().getAllErrors().get(0).getCodes())[1].split("\\.");
|
||||
String message = e.getBindingResult().getAllErrors().get(0).getDefaultMessage();
|
||||
String msg = "不能为空";
|
||||
if(msg.equals(message)){
|
||||
message = str[1] + ":" + message;
|
||||
ObjectError objectError = e.getBindingResult().getAllErrors().get(0);
|
||||
String message = objectError.getDefaultMessage();
|
||||
if (objectError instanceof FieldError) {
|
||||
message = ((FieldError) objectError).getField() + ": " + message;
|
||||
}
|
||||
return buildResponseEntity(ApiError.error(message));
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ import java.io.Closeable;
|
|||
|
||||
/**
|
||||
* @author Zheng Jie
|
||||
* @website https://el-admin.vip
|
||||
* @website https://eladmin.vip
|
||||
* @description 用于关闭各种连接,缺啥补啥
|
||||
* @date 2021-03-05
|
||||
**/
|
||||
|
|
|
@ -182,7 +182,8 @@ public class FileUtil extends cn.hutool.core.io.FileUtil {
|
|||
public static File upload(MultipartFile file, String filePath) {
|
||||
Date date = new Date();
|
||||
SimpleDateFormat format = new SimpleDateFormat("yyyyMMddhhmmssS");
|
||||
String name = getFileNameNoEx(file.getOriginalFilename());
|
||||
// 过滤非法文件名
|
||||
String name = getFileNameNoEx(verifyFilename(file.getOriginalFilename()));
|
||||
String suffix = getExtensionName(file.getOriginalFilename());
|
||||
String nowStr = "-" + format.format(date);
|
||||
try {
|
||||
|
@ -350,6 +351,44 @@ public class FileUtil extends cn.hutool.core.io.FileUtil {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证并过滤非法的文件名
|
||||
* @param fileName 文件名
|
||||
* @return 文件名
|
||||
*/
|
||||
public static String verifyFilename(String fileName) {
|
||||
// 过滤掉特殊字符
|
||||
fileName = fileName.replaceAll("[\\\\/:*?\"<>|~\\s]", "");
|
||||
|
||||
// 去掉文件名开头和结尾的空格和点
|
||||
fileName = fileName.trim().replaceAll("^[. ]+|[. ]+$", "");
|
||||
|
||||
// 不允许文件名超过255(在Mac和Linux中)或260(在Windows中)个字符
|
||||
int maxFileNameLength = 255;
|
||||
if (System.getProperty("os.name").startsWith("Windows")) {
|
||||
maxFileNameLength = 260;
|
||||
}
|
||||
if (fileName.length() > maxFileNameLength) {
|
||||
fileName = fileName.substring(0, maxFileNameLength);
|
||||
}
|
||||
|
||||
// 过滤掉控制字符
|
||||
fileName = fileName.replaceAll("[\\p{Cntrl}]", "");
|
||||
|
||||
// 过滤掉 ".." 路径
|
||||
fileName = fileName.replaceAll("\\.{2,}", "");
|
||||
|
||||
// 去掉文件名开头的 ".."
|
||||
fileName = fileName.replaceAll("^\\.+/", "");
|
||||
|
||||
// 保留文件名中最后一个 "." 字符,过滤掉其他 "."
|
||||
fileName = fileName.replaceAll("^(.*)(\\.[^.]*)$", "$1").replaceAll("\\.", "") +
|
||||
fileName.replaceAll("^(.*)(\\.[^.]*)$", "$2");
|
||||
|
||||
return fileName;
|
||||
}
|
||||
|
||||
|
||||
public static String getMd5(File file) {
|
||||
return getMd5(getByte(file));
|
||||
}
|
||||
|
|
|
@ -161,8 +161,10 @@ public class QueryHelp {
|
|||
break;
|
||||
case BETWEEN:
|
||||
List<Object> between = new ArrayList<>((List<Object>)val);
|
||||
if(between.size() == 2){
|
||||
list.add(cb.between(getExpression(attributeName, join, root).as((Class<? extends Comparable>) between.get(0).getClass()),
|
||||
(Comparable) between.get(0), (Comparable) between.get(1)));
|
||||
}
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
|
|
|
@ -185,7 +185,8 @@ public class RedisUtils {
|
|||
} else {
|
||||
Set<Object> keySet = new HashSet<>();
|
||||
for (String key : keys) {
|
||||
keySet.addAll(redisTemplate.keys(key));
|
||||
if (redisTemplate.hasKey(key))
|
||||
keySet.add(key);
|
||||
}
|
||||
long count = redisTemplate.delete(keySet);
|
||||
log.debug("--------------------------------------------");
|
||||
|
|
|
@ -20,7 +20,7 @@ import lombok.Getter;
|
|||
|
||||
/**
|
||||
* @author Zheng Jie
|
||||
* @website https://el-admin.vip
|
||||
* @website https://eladmin.vip
|
||||
* @description
|
||||
* @date 2020-06-10
|
||||
**/
|
||||
|
|
|
@ -59,6 +59,7 @@ public class GeneratorServiceImpl implements GeneratorService {
|
|||
|
||||
private final ColumnInfoRepository columnInfoRepository;
|
||||
|
||||
private final String CONFIG_MESSAGE = "请先配置生成器";
|
||||
@Override
|
||||
public Object getTables() {
|
||||
// 使用预编译防止sql注入
|
||||
|
@ -169,7 +170,7 @@ public class GeneratorServiceImpl implements GeneratorService {
|
|||
@Override
|
||||
public void generator(GenConfig genConfig, List<ColumnInfo> columns) {
|
||||
if (genConfig.getId() == null) {
|
||||
throw new BadRequestException("请先配置生成器");
|
||||
throw new BadRequestException(CONFIG_MESSAGE);
|
||||
}
|
||||
try {
|
||||
GenUtil.generatorCode(columns, genConfig);
|
||||
|
@ -182,7 +183,7 @@ public class GeneratorServiceImpl implements GeneratorService {
|
|||
@Override
|
||||
public ResponseEntity<Object> preview(GenConfig genConfig, List<ColumnInfo> columns) {
|
||||
if (genConfig.getId() == null) {
|
||||
throw new BadRequestException("请先配置生成器");
|
||||
throw new BadRequestException(CONFIG_MESSAGE);
|
||||
}
|
||||
List<Map<String, Object>> genList = GenUtil.preview(columns, genConfig);
|
||||
return new ResponseEntity<>(genList, HttpStatus.OK);
|
||||
|
@ -191,7 +192,7 @@ public class GeneratorServiceImpl implements GeneratorService {
|
|||
@Override
|
||||
public void download(GenConfig genConfig, List<ColumnInfo> columns, HttpServletRequest request, HttpServletResponse response) {
|
||||
if (genConfig.getId() == null) {
|
||||
throw new BadRequestException("请先配置生成器");
|
||||
throw new BadRequestException(CONFIG_MESSAGE);
|
||||
}
|
||||
try {
|
||||
File file = new File(GenUtil.download(columns, genConfig));
|
||||
|
|
|
@ -96,9 +96,9 @@ public class LogServiceImpl implements LogService {
|
|||
log.setUsername(username);
|
||||
log.setParams(getParameter(method, joinPoint.getArgs()));
|
||||
// 记录登录用户,隐藏密码信息
|
||||
if(log.getDescription().equals("用户登录")){
|
||||
if(signature.getName().equals("login") && StringUtils.isNotEmpty(log.getParams())){
|
||||
JSONObject obj = JSONUtil.parseObj(log.getParams());
|
||||
log.setUsername(obj.get("username").toString());
|
||||
log.setUsername(obj.getStr("username", ""));
|
||||
log.setParams(JSONUtil.toJsonStr(Dict.create().set("username", log.getUsername())));
|
||||
}
|
||||
log.setBrowser(browser);
|
||||
|
@ -120,7 +120,7 @@ public class LogServiceImpl implements LogService {
|
|||
//将RequestParam注解修饰的参数作为请求参数
|
||||
RequestParam requestParam = parameters[i].getAnnotation(RequestParam.class);
|
||||
if (requestParam != null) {
|
||||
Map<String, Object> map = new HashMap<>(4);
|
||||
Map<String, Object> map = new HashMap<>(2);
|
||||
String key = parameters[i].getName();
|
||||
if (!StringUtils.isEmpty(requestParam.value())) {
|
||||
key = requestParam.value();
|
||||
|
|
|
@ -58,13 +58,6 @@ public class AppRun {
|
|||
return new SpringContextHolder();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ServletWebServerFactory webServerFactory() {
|
||||
TomcatServletWebServerFactory fa = new TomcatServletWebServerFactory();
|
||||
fa.addConnectorCustomizers(connector -> connector.setProperty("relaxedQueryChars", "[]{}"));
|
||||
return fa;
|
||||
}
|
||||
|
||||
/**
|
||||
* 访问首页提示
|
||||
*
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
package me.zhengjie.config;
|
||||
|
||||
import org.apache.catalina.connector.Connector;
|
||||
import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* @author bearBoy80
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public class RelaxedQueryCharsConnectorCustomizer implements TomcatConnectorCustomizer {
|
||||
@Override
|
||||
public void customize(Connector connector) {
|
||||
connector.setProperty("relaxedQueryChars", "[]{}");
|
||||
}
|
||||
}
|
|
@ -30,7 +30,7 @@ import java.util.*;
|
|||
|
||||
/**
|
||||
* @author Zheng Jie
|
||||
* @website https://el-admin.vip
|
||||
* @website https://eladmin.vip
|
||||
* @description 数据权限服务实现
|
||||
* @date 2020-05-07
|
||||
**/
|
||||
|
|
|
@ -59,6 +59,12 @@ public class MenuServiceImpl implements MenuService {
|
|||
private final RoleService roleService;
|
||||
private final RedisUtils redisUtils;
|
||||
|
||||
private static final String HTTP_PRE = "http://";
|
||||
private static final String HTTPS_PRE = "https://";
|
||||
private static final String YES_STR = "是";
|
||||
private static final String NO_STR = "否";
|
||||
private static final String BAD_REQUEST = "外链必须以http://或者https://开头";
|
||||
|
||||
@Override
|
||||
public List<MenuDto> queryAll(MenuQueryCriteria criteria, Boolean isQuery) throws Exception {
|
||||
Sort sort = Sort.by(Sort.Direction.ASC, "menuSort");
|
||||
|
@ -114,13 +120,12 @@ public class MenuServiceImpl implements MenuService {
|
|||
throw new EntityExistException(Menu.class,"componentName",resources.getComponentName());
|
||||
}
|
||||
}
|
||||
if(resources.getPid().equals(0L)){
|
||||
if (Long.valueOf(0L).equals(resources.getPid())) {
|
||||
resources.setPid(null);
|
||||
}
|
||||
if(resources.getIFrame()){
|
||||
String http = "http://", https = "https://";
|
||||
if (!(resources.getPath().toLowerCase().startsWith(http)||resources.getPath().toLowerCase().startsWith(https))) {
|
||||
throw new BadRequestException("外链必须以http://或者https://开头");
|
||||
if (!(resources.getPath().toLowerCase().startsWith(HTTP_PRE)||resources.getPath().toLowerCase().startsWith(HTTPS_PRE))) {
|
||||
throw new BadRequestException(BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
menuRepository.save(resources);
|
||||
|
@ -140,9 +145,8 @@ public class MenuServiceImpl implements MenuService {
|
|||
ValidationUtil.isNull(menu.getId(),"Permission","id",resources.getId());
|
||||
|
||||
if(resources.getIFrame()){
|
||||
String http = "http://", https = "https://";
|
||||
if (!(resources.getPath().toLowerCase().startsWith(http)||resources.getPath().toLowerCase().startsWith(https))) {
|
||||
throw new BadRequestException("外链必须以http://或者https://开头");
|
||||
if (!(resources.getPath().toLowerCase().startsWith(HTTP_PRE)||resources.getPath().toLowerCase().startsWith(HTTPS_PRE))) {
|
||||
throw new BadRequestException(BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
Menu menu1 = menuRepository.findByTitle(resources.getTitle());
|
||||
|
@ -322,9 +326,9 @@ public class MenuServiceImpl implements MenuService {
|
|||
map.put("菜单标题", menuDTO.getTitle());
|
||||
map.put("菜单类型", menuDTO.getType() == null ? "目录" : menuDTO.getType() == 1 ? "菜单" : "按钮");
|
||||
map.put("权限标识", menuDTO.getPermission());
|
||||
map.put("外链菜单", menuDTO.getIFrame() ? "是" : "否");
|
||||
map.put("菜单可见", menuDTO.getHidden() ? "否" : "是");
|
||||
map.put("是否缓存", menuDTO.getCache() ? "是" : "否");
|
||||
map.put("外链菜单", menuDTO.getIFrame() ? YES_STR : NO_STR);
|
||||
map.put("菜单可见", menuDTO.getHidden() ? NO_STR : YES_STR);
|
||||
map.put("是否缓存", menuDTO.getCache() ? YES_STR : NO_STR);
|
||||
map.put("创建日期", menuDTO.getCreateTime());
|
||||
list.add(map);
|
||||
}
|
||||
|
|
|
@ -60,11 +60,11 @@ public class VerifyServiceImpl implements VerifyService {
|
|||
throw new BadRequestException("服务异常,请联系网站负责人");
|
||||
}
|
||||
content = template.render(Dict.create().set("code",code));
|
||||
emailVo = new EmailVo(Collections.singletonList(email),"EL-ADMIN后台管理系统",content);
|
||||
emailVo = new EmailVo(Collections.singletonList(email),"ELADMIN后台管理系统",content);
|
||||
// 存在就再次发送原来的验证码
|
||||
} else {
|
||||
content = template.render(Dict.create().set("code",oldCode));
|
||||
emailVo = new EmailVo(Collections.singletonList(email),"EL-ADMIN后台管理系统",content);
|
||||
emailVo = new EmailVo(Collections.singletonList(email),"ELADMIN后台管理系统",content);
|
||||
}
|
||||
return emailVo;
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
font-size: 12px;
|
||||
padding: 20px 0px;
|
||||
font-family: Microsoft YaHei;">
|
||||
Copyright ©${.now?string("yyyy")} <a hover="color: #DA251D;" style="color: #999;" href="https://github.com/elunez/eladmin" target="_blank">EL-ADMIN</a> 后台管理系统 All Rights Reserved.
|
||||
Copyright ©${.now?string("yyyy")} <a hover="color: #DA251D;" style="color: #999;" href="https://github.com/elunez/eladmin" target="_blank">ELADMIN</a> 后台管理系统 All Rights Reserved.
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
|
@ -60,7 +60,7 @@
|
|||
font-size: 12px;
|
||||
padding: 20px 0px;
|
||||
font-family: Microsoft YaHei;">
|
||||
Copyright ©${.now?string("yyyy")} <a hover="color: #DA251D;" style="color: #999;" href="https://github.com/elunez/eladmin" target="_blank">EL-ADMIN</a> 后台管理系统 All Rights Reserved.
|
||||
Copyright ©${.now?string("yyyy")} <a hover="color: #DA251D;" style="color: #999;" href="https://github.com/elunez/eladmin" target="_blank">ELADMIN</a> 后台管理系统 All Rights Reserved.
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
|
@ -31,7 +31,7 @@ import java.io.IOException;
|
|||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* @website https://el-admin.vip
|
||||
* @website https://eladmin.vip
|
||||
* @author ${author}
|
||||
* @date ${date}
|
||||
**/
|
||||
|
|
|
@ -29,7 +29,7 @@ import com.alibaba.fastjson.serializer.ToStringSerializer;
|
|||
</#if>
|
||||
|
||||
/**
|
||||
* @website https://el-admin.vip
|
||||
* @website https://eladmin.vip
|
||||
* @description /
|
||||
* @author ${author}
|
||||
* @date ${date}
|
||||
|
|
|
@ -37,7 +37,7 @@ import java.math.BigDecimal;
|
|||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* @website https://el-admin.vip
|
||||
* @website https://eladmin.vip
|
||||
* @description /
|
||||
* @author ${author}
|
||||
* @date ${date}
|
||||
|
|
|
@ -22,7 +22,7 @@ import org.mapstruct.Mapper;
|
|||
import org.mapstruct.ReportingPolicy;
|
||||
|
||||
/**
|
||||
* @website https://el-admin.vip
|
||||
* @website https://eladmin.vip
|
||||
* @author ${author}
|
||||
* @date ${date}
|
||||
**/
|
||||
|
|
|
@ -30,7 +30,7 @@ import me.zhengjie.annotation.Query;
|
|||
</#if>
|
||||
|
||||
/**
|
||||
* @website https://el-admin.vip
|
||||
* @website https://eladmin.vip
|
||||
* @author ${author}
|
||||
* @date ${date}
|
||||
**/
|
||||
|
|
|
@ -20,7 +20,7 @@ import org.springframework.data.jpa.repository.JpaRepository;
|
|||
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
||||
|
||||
/**
|
||||
* @website https://el-admin.vip
|
||||
* @website https://eladmin.vip
|
||||
* @author ${author}
|
||||
* @date ${date}
|
||||
**/
|
||||
|
|
|
@ -25,7 +25,7 @@ import java.io.IOException;
|
|||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* @website https://el-admin.vip
|
||||
* @website https://eladmin.vip
|
||||
* @description 服务接口
|
||||
* @author ${author}
|
||||
* @date ${date}
|
||||
|
|
|
@ -54,7 +54,7 @@ import java.util.ArrayList;
|
|||
import java.util.LinkedHashMap;
|
||||
|
||||
/**
|
||||
* @website https://el-admin.vip
|
||||
* @website https://eladmin.vip
|
||||
* @description 服务实现
|
||||
* @author ${author}
|
||||
* @date ${date}
|
||||
|
|
10
pom.xml
10
pom.xml
|
@ -17,8 +17,8 @@
|
|||
<module>eladmin-generator</module>
|
||||
</modules>
|
||||
|
||||
<name>EL-ADMIN 后台管理</name>
|
||||
<url>https://el-admin.vip</url>
|
||||
<name>ELADMIN 后台管理</name>
|
||||
<url>https://eladmin.vip</url>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
|
@ -185,6 +185,12 @@
|
|||
<version>1.6.2</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-text</artifactId>
|
||||
<version>1.10.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 解析客户端操作系统、浏览器信息 -->
|
||||
<dependency>
|
||||
<groupId>nl.basjes.parse.useragent</groupId>
|
||||
|
|
Loading…
Reference in New Issue