Commit 627e5035 by 陈皓

init

parent 1d4b5c8c
package com.zq.file.controller;
import cn.hutool.json.JSON;
import cn.hutool.json.JSONUtil;
import com.zq.common.annotation.rest.AnonymousGetMapping;
import com.zq.common.annotation.rest.AnonymousPostMapping;
import com.zq.file.vo.OnLinePreview.FileResult;
import com.zq.file.vo.OnLinePreview.OnlinePreviewResult;
import com.zq.file.vo.OnLinePreview.UserResult;
import com.zq.file.vo.OnlineEdit.OnlineEditRename;
import com.zq.file.vo.OnlineEdit.OnlineEditUsers;
import com.zq.file.vo.UserAclVo;
import com.zq.file.vo.Watermark;
import com.zq.file.vo.WpsCertificateTimeVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
@Api(value = "WpsCallBackController")
@RequestMapping("/file/wpsCallBack")
@RestController
@Slf4j
public class WpsCallBackController {
// @Resource
// private ISysFileUploadService sysFileUploadService;
// @Autowired
// WpsService wpsService;
//
@ApiOperation("证书信息回调")
@AnonymousPostMapping ("/certificate")
public JSON getCertificateInfo(@RequestBody WpsCertificateTimeVO vo) {
log.info(vo.toString());
return JSONUtil.parse(vo);
}
@ApiOperation("获取文件信息(获取在线浏览和在线编辑的回调)")
@AnonymousGetMapping("/v1/3rd/file/info")
public JSON getFileInfo(@RequestParam("_w_third_fileId") String fileId,
@RequestParam("_w_third_userId") String userId) {
log.info("获取在线浏览文件信息(获取在线浏览和在线编辑的回调)");
String tempUrl = "https://img.yww52.com/test/test.docx";
FileResult file = FileResult.builder()
.id(fileId).name("test.docx").version(1).size(10017L)
.preview_pages(0)
.create_time(1136185445L)
.creator("id0")
.modifier("id1000")
.modify_time(1551409818L)
.download_url(tempUrl)
.user_acl(new UserAclVo()).watermark(new Watermark()).build();
// 设置用户返回信息
UserResult user = UserResult.builder()
.id("id1000").name("wps-1000").permission("read").avatar_url("")
.build();
OnlinePreviewResult result = OnlinePreviewResult.builder().file(file).user(user).build();
return JSONUtil.parse(result);
}
@ApiOperation("获取用户信息(在线编辑的回调)")
@AnonymousPostMapping("/v1/3rd/user/info")
public JSON getUsersInfo(@RequestBody OnlineEditUsers users) {
log.info("获取在线浏览文件信息(获取在线浏览和在线编辑的回调");
return JSONUtil.parse(null);
// return ResultVo.success(wpsService.getUsersInfo(users));
}
@ApiOperation("上传文件新版本(在线编辑的回调)")
@AnonymousPostMapping("/v1/3rd/file/save")
public JSON saveNewFile(@RequestParam("_w_third_fileId") String fileId,
@RequestParam("_w_third_userId") String userId,
@RequestBody MultipartFile multipartFile) {
return JSONUtil.parse(null);
}
@ApiOperation("文件重命名(在线编辑的回调)")
@PutMapping("/v1/3rd/file/rename")
public JSON rename(@RequestParam("_w_third_fileId") String fileId,
@RequestParam("_w_third_userId") String userId,
@RequestBody OnlineEditRename vo) {
return JSONUtil.parse(null);
}
@ApiOperation("通知此文件目前有哪些人正在协作(在线编辑的回调)")
@AnonymousPostMapping("/v1/3rd/file/online")
public JSON onlineInfo(@RequestBody OnlineEditUsers users) {
return JSONUtil.parse(null);
}
@ApiOperation("回调通知")
@AnonymousPostMapping("/v1/3rd/onnotify")
public JSON onnotify() {
return JSONUtil.parse(null);
}
@ApiOperation("获取特定版本的文件信息(在线编辑的回调)")
@AnonymousPostMapping("/v1/3rd/file/version/{version}")
public JSON getVersionInfo(@RequestParam("version") String version,
@RequestParam("_w_third_fileId") String fileId) {
return JSONUtil.parse(null);
}
@ApiOperation("获取所有历史版本的文件信息(在线编辑的回调)")
@AnonymousPostMapping("/v1/3rd/file/history")
public JSON getAllVersionInfo(@RequestParam("_w_third_fileId") String fileId) {
return JSONUtil.parse(null);
}
}
package com.zq.file.controller;
import cn.hutool.http.HttpResponse;
import cn.hutool.json.JSON;
import cn.hutool.json.JSONUtil;
import com.zq.common.annotation.AnonymousAccess;
import com.zq.common.utils.AssertUtils;
import com.zq.common.vo.ResultVo;
import com.zq.file.utils.WpsUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringEscapeUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
/**
* @author ZQ
*/
@Api(value = "WpsController")
@RequestMapping("/file/wps")
@RestController
@Slf4j
public class WpsController {
private final String WAI = "http://171.106.48.55:33806";
private final String NEI = "http://147.1.3.220";
@AnonymousAccess
@ApiOperation("获取在线浏览地址(在线浏览)")
@GetMapping("/getViewUrl/{fileId}")
public ResultVo<String> getViewUrl(@PathVariable String fileId) {
String uri = WAI + "/open/api/preview/v1/files/" + "132aa30a87064" + "/link";
// String uri = DOMAIN + "/open/api/preview/v1/files/" + fileId + "/link";
// 添加参数
Map<String, String> params = new HashMap<>(1);
params.put("type", "w");
params.put("preview_mode", "high_definition");
String result = "";
try (HttpResponse response = WpsUtil.httpGet(uri, params)) {
result = response.body();
} catch (Exception e) {
log.error("获取在线浏览地址接口出现异常");
e.printStackTrace();
}
JSON json = JSONUtil.parseObj(result);
String code = json.getByPath("code", String.class);
AssertUtils.isTrue("200".equals(code), "获取在线浏览地址接口出现异常 code:" + code + ",msg:" + json.getByPath("msg", String.class));
String link = json.getByPath("data.link", String.class);
// 反转义
link = StringEscapeUtils.unescapeJava(link);
return ResultVo.success(link);
}
@AnonymousAccess
@ApiOperation("获取在线编辑地址(在线编辑)")
@GetMapping("/getEditUrl/{fileId}")
public ResultVo getExitUrl(@PathVariable String fileId) {
String uri = WAI + "/open/api/edit/v1/files/" + fileId + "/link";
// 设置查询参数
Map<String, String> params = new HashMap<>(3);
params.put("type", "w");
params.put("_w_third_fileId", fileId);
params.put("_w_third_userId", "78511");
String result = "";
try (HttpResponse response = WpsUtil.httpGet(uri ,params)) {
result = response.body();
} catch (Exception e) {
log.error("获取在线编辑地址接口出现异常");
e.printStackTrace();
}
System.out.println(result);
JSON json = JSONUtil.parseObj(StringEscapeUtils.unescapeJava(result));
String str = (String) json.getByPath("data.link");
return ResultVo.success(str);
}
}
package com.zq.file.utils;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.Method;
import cn.hutool.json.JSON;
import cn.hutool.json.JSONUtil;
import com.zq.common.exception.BusinessException;
import com.zq.common.utils.AssertUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.http.client.utils.URIBuilder;
import org.springframework.stereotype.Component;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;
@Component
@Slf4j
public class WpsUtil {
private static final String accessKey = "AXJIHUKKFLMGSVFA";
private static final String secretKey = "2e252285e64d4097a550468283d8c157";
/**
* 获取WPS-4签名
*
* @param request request请求
* @param body 内容主体,没有则传入NUll
* @return WPS-4签名
*/
public static String getWpsDocsAuthorization(HttpRequest request, byte[] body, String accessKey, String secretKey) {
String ver = "WPS-4";
// 构造Wps-Docs-Authorization头部
StringBuilder sign = new StringBuilder();
sign.append(ver);
sign.append(request.getMethod());
sign.append(getSignPath(request.getUrl()));
sign.append(request.header("Content-type"));
sign.append(request.header("Wps-Docs-Date"));
if (body != null && body.length > 0) {
sign.append(getSHA256StrJava(body));
}
String signTrue;
try {
signTrue = signHmacSha256(sign.toString(), secretKey);
} catch (Exception e) {
log.error("WPS-4签名出现异常", e);
throw new BusinessException("WPS-4签名出现异常:" + e.getMessage());
}
return String.format("WPS-4 %s:%s", accessKey, signTrue);
}
/**
* 利用java原生的摘要实现SHA256加密
*
* @param str 原文
* @return 加密后的报文
*/
public static String getSHA256StrJava(byte[] str) {
MessageDigest messageDigest;
String encodeStr = "";
try {
messageDigest = MessageDigest.getInstance("SHA-256");
messageDigest.update(str);
encodeStr = byte2Hex(messageDigest.digest());
} catch (NoSuchAlgorithmException e) {
log.error("WPS-4加密异常", e);
}
return encodeStr;
}
/**
* 将byte转为16进制
*
* @param bytes 字符数组
* @return 16进制字符串
*/
private static String byte2Hex(byte[] bytes) {
StringBuilder stringBuffer = new StringBuilder();
String temp;
for (byte aByte : bytes) {
temp = Integer.toHexString(aByte & 0xFF);
if (temp.length() == 1) {
stringBuffer.append("0");
}
stringBuffer.append(temp);
}
return stringBuffer.toString();
}
/**
* 获取HAMCSHA256签名内容
*
* @param data 内容
* @param key 应用密钥,即secret_key
* @return HAMCSHA256签名内容
*/
public static String signHmacSha256(String data, String key) throws Exception {
Mac sign = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
sign.init(secretKey);
byte[] array = sign.doFinal(data.getBytes(StandardCharsets.UTF_8));
StringBuilder sb = new StringBuilder();
for (byte item : array) {
sb.append(Integer.toHexString((item & 0xFF) | 0x100), 1, 3);
}
return sb.toString();
}
/**
* 给url添加查询参数
*
* @param url url
* @param params 参数列表
* @return 添加了参数之后的url
*/
public static String perfectUrl(String url, Map<String, String> params) {
if (params == null || params.isEmpty()) {
return url;
}
URIBuilder uriBuilder;
try {
uriBuilder = new URIBuilder(url);
// TODO 每个参数都要实现urlEncode
for (String key : params.keySet()) {
uriBuilder.addParameter(key, params.get(key));
}
} catch (URISyntaxException e) {
log.error("WPS-4参数构造出现异常", e);
throw new BusinessException("WPS-4参数构造出现异常:" + e.getMessage());
}
return uriBuilder.toString();
}
public static HttpResponse httpGet(String url, Map<String, String> params) {
HttpRequest request = HttpRequest.get(perfectUrl(url, params));
request.setMethod(Method.GET);
DateFormat dateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
// 设置请求头
request.contentType("application/json");
request.header("Wps-Docs-Date", dateFormat.format(new Date()));
request.header("Wps-Docs-Authorization", WpsUtil.getWpsDocsAuthorization(request, null, accessKey, secretKey));
return request.execute();
}
public static HttpResponse httpPost(String url, Map<String, String> params, Map<String, Object> body, String accessKey, String secretKey) {
HttpRequest request = HttpRequest.post(perfectUrl(url, params));
DateFormat dateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
// 设置请求头,必需三个header
request.contentType("application/json");
request.header("Wps-Docs-Date", dateFormat.format(new Date()));
// body转json放入签名
if (ObjectUtil.isNull(body)) {
request.header("Wps-Docs-Authorization", WpsUtil.getWpsDocsAuthorization(request, null, accessKey, secretKey));
} else {
request.header("Wps-Docs-Authorization", WpsUtil.getWpsDocsAuthorization(request, JSONUtil.toJsonStr(body).getBytes(), accessKey, secretKey));
}
if (!body.isEmpty()) {
request.body(JSONUtil.toJsonStr(body));
}
return request.execute();
}
/**
* 获取URL中/open后面的内容
*
* @param url URL
* @return URL中/open后面的内容
*/
public static String getSignPath(String url) {
log.debug("url:" + url);
int index = url.indexOf("/api");
String substring = url.substring(index);
log.debug("suburl:" + substring);
return substring;
}
public static void main(String[] args) {
Map<String, String> params = new HashMap<>(2);
params.put("type", "w");
params.put("preview_mode", "ordinary");
String uri = "http://171.106.48.55:33806/open/api/preview/v1/files/" + "132aa30a87064" + "/link";
HttpResponse response = WpsUtil.httpGet(uri, params);
JSON json = JSONUtil.parseObj(response.body());
String code = json.getByPath("code", String.class);
AssertUtils.isTrue("200".equals(code), "获取在线浏览地址接口出现异常 code:" + code + ",msg:" + json.getByPath("msg", String.class));
String link = json.getByPath("data.link", String.class);
// 反转义
link = StringEscapeUtils.unescapeJava(link);
System.out.println(link);
// String uri = "http://171.106.48.55:33806/open/api/privilege/v1/licence/detail";
// HttpResponse response = WpsUtil.httpGet(uri, new HashMap<>());
// System.out.println(response.body());
}
}
package com.zq.file.vo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Creator {
/**
* 创建者ID
*/
private Long id;
/**
* 创建者名称
*/
private String name;
/**
* 创建者的头像地址
*/
private String avatar_url;
}
package com.zq.file.vo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Modifier {
/**
* 修改者ID
*/
private Long id;
/**
* 修改者名称
*/
private String name;
/**
* 修改者头像地址
*/
private String avatar_url;
}
package com.zq.file.vo.OnLinePreview;
import com.zq.file.vo.UserAclVo;
import com.zq.file.vo.Watermark;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 在线浏览的文件返回类
*
* @author ZQ
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class FileResult {
/**
* 文件id,字符串长度不超过64位
*/
private String id;
/**
* 文件名必须带后缀
*/
private String name;
/**
* 文档版本号,从1开始累加,位数小于11
*/
private Integer version;
/**
* 文档大小,单位为字节;此处需传文件真实大小,否则会出现异常
*/
private Long size;
/**
* readonly默认为 false true 开启表格筛选,支持对EXCEL表格文档筛选,但
* 不支持对EXCEL表格文档开启多人同步筛选,该预览特性见常见问题
* false 关闭表格筛选(20220707新增)
*/
private Boolean readonly = false;
/**
* 创建者id
*/
private String creator;
/**
* 创建时间
*/
private Long create_time;
/**
* 修改者id
*/
private String modifier;
/**
* 修改时间
*/
private Long modify_time;
/**
* 下载地址
*/
private String download_url;
/**
* 限制预览页数
*/
private Integer preview_pages;
/**
* 用户权限
*/
private UserAclVo user_acl;
/**
* 水印参数
*/
private Watermark watermark;
}
package com.zq.file.vo.OnLinePreview;
public enum OnlinePreviewEnum {
//成功
Success("Success", Long.parseLong("200")),
//用户未登录
UserNotLogin("UserNotLogin", Long.parseLong("20501001")),
//token过期
SessionExpired("SessionExpired", Long.parseLong("20501002")),
//用户无权限访问
PermissionDenied("PermissionDenied", Long.parseLong("20501003")),
//资源不存在
NotExists("NotExists", Long.parseLong("20501004")),
//参数错误
InvalidArgument("InvalidArgument", Long.parseLong("20501005")),
//保存空间已满
SpaceFull("SpaceFull", Long.parseLong("20501006")),
//自定义错误提示,前端页面显示此错误
CustomMsg("CustomMsg", Long.parseLong("20501007")),
//文件重命名冲突
FnameConflict("FnameConflict", Long.parseLong("20501008")),
//系统内部错误
ServerError("ServerError", Long.parseLong("20501999")),
;
private OnlinePreviewEnum(String msg, Long code) {
this.msg = msg;
this.code = code;
}
private String msg;
private Long code;
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Long getCode() {
return code;
}
public void setCode(Long code) {
this.code = code;
}
}
package com.zq.file.vo.OnLinePreview;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 在线浏览的返回类
*
* @author ZQ
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class OnlinePreviewResult {
/**
* 文件信息
*/
FileResult file;
/**
* 用户信息
*/
UserResult user;
}
package com.zq.file.vo.OnLinePreview;
import lombok.Data;
@Data
//在线预览用户权限
public class UserAclResult {
//重命名权限,1为打开该权限,0为关闭该权限,默认为0
public Integer rename;
//历史版本权限,1为打开该权限,0为关闭该权限,默认为1
public Integer history;
//复制,1为打开该权限,0为关闭该权限
public Integer copy;
//导出,1为打开该权限,0为关闭该权限
public Integer export;
//打印,1为打开该权限,0为关闭该权限
public Integer print;
}
package com.zq.file.vo.OnLinePreview;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 在线浏览的用户类
*
* @author ZQ
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class UserResult {
/**
* 用户id,长度不超过32位
*/
public String id;
/**
* 用户名称
*/
public String name;
/**
* 用户操作权限,预览固定为read
*/
public String permission;
/**
* 用户头像地址,支持url和base64
*/
public String avatar_url;
}
package com.zq.file.vo.OnLinePreview;
import lombok.Data;
@Data
//在线预览水印信息
public class WatermarkResult {
//水印类型,0为无水印,1为文字水印
public Integer type;
//文字水印的文字,当type为1时此字段必填
public String value;
//水印的透明度,非必选,有默认值,格式:rgba( 192, 192, 192, 0.6 )
public String fillstyle;
//水印的字体,非必选,有默认值,格式:bold 20px Serif
public String font;
//水印的旋转度,非必选,有默认值
public Float rotate;
//水印的水平间距,非必选,有默认值
public Integer horizontal;
//水印的垂直间距,非必选,有默认值
public Integer vertical;
}
package com.zq.file.vo.OnlineEdit;
import com.zq.file.vo.Creator;
import com.zq.file.vo.Modifier;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 历史文件
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class FileHistorieVo {
/**
* 文件ID
*/
private String id;
/**
* 文件名
*/
private String name;
/**
* 文件版本号
*/
private Integer version;
/**
* 文件大小
*/
private Long size;
/**
* 文件下载地址
*/
private String download_url;
/**
* 文件创建者
*/
private Creator creator;
/**
* 文件创建时间
*/
private Long create_time;
/**
* 文件修改者
*/
private Modifier modifier;
/**
* 文件修改时间
*/
private Long modify_time;
}
package com.zq.file.vo.OnlineEdit;
import lombok.Data;
@Data
public class GetHistoryVersionResult {
public String id;
public String name;
public Integer version;
public Long size;
public String download_url;
public Long create_time;
public Long modify_time;
public GetHistoryVersionUserResult creator;
public GetHistoryVersionUserResult modifier;
public GetHistoryVersionResult() {
this.creator = new GetHistoryVersionUserResult();
this.modifier = new GetHistoryVersionUserResult();
}
}
package com.zq.file.vo.OnlineEdit;
import lombok.Data;
@Data
public class GetHistoryVersionUserResult {
public String id;
public String name;
public String avatar_url;
}
package com.zq.file.vo.OnlineEdit;
import lombok.Data;
@Data
public class GetVersionFileResult {
public String id;
public String name;
public Integer version;
public Long size;
public Long create_time;
public String creator;
public Long modify_time;
public String modifier;
public String download_url;
}
package com.zq.file.vo.OnlineEdit;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class HistoriesVo {
/**
* 历史文件列表
*/
List<FileHistorieVo> histories;
}
package com.zq.file.vo.OnlineEdit;
public enum OnlineEditEnum {
//成功
Success("Success", Long.parseLong("0")),
//用户未登录
UserNotLogin("UserNotLogin", Long.parseLong("20501001")),
//token过期
SessionExpired("SessionExpired", Long.parseLong("20501002")),
//用户无权限访问
PermissionDenied("PermissionDenied", Long.parseLong("20501003")),
//资源不存在
NotExists("NotExists", Long.parseLong("20501004")),
//参数错误
InvalidArgument("InvalidArgument", Long.parseLong("20501005")),
//保存空间已满
SpaceFull("SpaceFull", Long.parseLong("20501006")),
//自定义错误提示,前端页面显示此错误
CustomMsg("CustomMsg", Long.parseLong("20501007")),
//文件重命名冲突
FnameConflict("FnameConflict", Long.parseLong("20501008")),
//系统内部错误
ServerError("ServerError", Long.parseLong("20501999")),
;
private OnlineEditEnum(String msg, Long code) {
this.msg = msg;
this.code = code;
}
private String msg;
private Long code;
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Long getCode() {
return code;
}
public void setCode(Long code) {
this.code = code;
}
}
package com.zq.file.vo.OnlineEdit;
import com.zq.file.vo.OnLinePreview.FileResult;
import com.zq.file.vo.OnLinePreview.UserAclResult;
import com.zq.file.vo.OnLinePreview.UserResult;
import com.zq.file.vo.OnLinePreview.WatermarkResult;
import lombok.Data;
@Data
public class OnlineEditMessage {
public OnlineEditMessage() {
this.file = new FileResult();
this.user = new UserResult();
this.user_acl = new UserAclResult();
this.watermark = new WatermarkResult();
}
//文件信息
public FileResult file;
//用户信息
public UserResult user;
//用户权限信息
public UserAclResult user_acl;
//水印信息
public WatermarkResult watermark;
}
package com.zq.file.vo.OnlineEdit;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* <p>
* 文件重命名接收类
* </p>
*
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class OnlineEditRename {
String name;
}
package com.zq.file.vo.OnlineEdit;
import lombok.Data;
@Data
public class OnlineEditResult {
public OnlineEditResult() {
this.file = new UploadFileResult();
}
//文件信息
public UploadFileResult file;
}
package com.zq.file.vo.OnlineEdit;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* <p>
* 获取用户信息回调的VO
* </p>
*
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class OnlineEditUsers {
List<String> ids;
}
package com.zq.file.vo.OnlineEdit;
import com.zq.file.vo.OnLinePreview.UserResult;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* <p>
* 获取用户信息返回类
* </p>
*
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class OnlineEditUsersResult {
List<UserResult> users;
}
package com.zq.file.vo.OnlineEdit;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* <p>
* 获取版本文件信息接收类
* </p>
*
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class OnlineEditVersion {
/**
* 文件id,字符串长度不超过64位
*/
private String id;
/**
* 文件名必须带后缀
*/
private String name;
/**
* 文档版本号,从1开始累加,位数小于11
*/
private Integer version;
/**
* 文档大小,单位为字节;此处需传文件真实大小,否则会出现异常
*/
private Long size;
/**
* 创建者id
*/
private String creator;
/**
* 创建时间
*/
private Long create_time;
/**
* 修改者id
*/
private String modifier;
/**
* 修改时间
*/
private Long modify_time;
/**
* 下载地址
*/
private String download_url;
}
package com.zq.file.vo.OnlineEdit;
import lombok.Data;
@Data
public class UploadFileResult {
public String id;
public String name;
public Integer version;
public Long size;
public String download_url;
}
package com.zq.file.vo;
import lombok.Data;
/**
* 用户权限配置
*/
@Data
public class UserAclVo {
/**
* 重命名权限,1为打开该权限,0为关闭该权限,默认为0
*/
private Integer rename = 0;
/**
* 历史版本权限,1为打开该权限,0为关闭该权限,默认为1
*/
private Integer history = 0;
/**
* 复制权限,1为打开该权限,0为关闭该权限,默认为1
*/
private Integer copy = 0;
/**
* 导出权限,1为打开该权限,0为关闭该权限,默认为1
*/
private Integer export = 1;
/**
* 打印权限,1为打开该权限,0为关闭该权限,默认为1
*/
private Integer print = 1;
/**
*只读情况下的可评论权限,1为打开该权限,0为关闭该权限,默认为0v6.0.2207.20220729新增
*/
private Integer comment = 0;
}
package com.zq.file.vo;
import lombok.Data;
/**
* 水印配置
*/
@Data
public class Watermark {
/**
* 水印类型, 0为无水印; 1为文字水印
*/
private Integer type = 0;
/**
* 文字水印的文字,支持通过 \r\n 换行,支持emoji表情,当type为1时此字段必选
*/
private String value = "禁止传阅wps-1000";
/**
* 水印的颜色(含透明度),非必选,有默认值。格式为:
* rgba( 192, 192, 192, 0.6 )
*/
private String fillstyle = "rgba( 192, 192, 192, 0.6 )";
/**
* 水印的字体,非必选,有默认值,格式为:
* bold 20px Serif
*/
private String font = "bold 20px Serif";
/**
* 水印的旋转度,非必选,有默认值
*/
private Double rotate = -0.7853982;
/**
* 水印水平间距,非必选,有默认值
*/
private Integer horizontal = 50;
/**
* 水印垂直间距,非必选,有默认值
*/
private Integer vertical = 100;
}
package com.zq.file.vo;
import lombok.Data;
/**
* @author ZQ
*/
@Data
public class WpsCertificateTimeVO {
/**
* 证书过期时间,单位为秒
*/
private Integer dead_line;
/**
* 证书距离过期天数
*/
private Integer licence_remaining_time;
/**
* 消息
*/
private String message;
}
...@@ -128,7 +128,7 @@ ...@@ -128,7 +128,7 @@
<dependency> <dependency>
<groupId>cn.hutool</groupId> <groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId> <artifactId>hutool-all</artifactId>
<version>5.8.19</version> <version>5.8.23</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.alibaba</groupId> <groupId>com.alibaba</groupId>
......
...@@ -7,6 +7,8 @@ import org.springframework.web.servlet.config.annotation.EnableWebMvc; ...@@ -7,6 +7,8 @@ import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import springfox.documentation.builders.ApiInfoBuilder; import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo; import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType; import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.spring.web.plugins.Docket;
...@@ -43,15 +45,16 @@ public class SwaggerConfig implements WebMvcConfigurer { ...@@ -43,15 +45,16 @@ public class SwaggerConfig implements WebMvcConfigurer {
@Bean @Bean
public Docket api() { public Docket api() {
return new Docket(DocumentationType.SWAGGER_2).enable(false); return new Docket(DocumentationType.SWAGGER_2)
// .groupName("项目") // .enable(false);
// // 生产环境关闭 .groupName("项目")
// .enable(!"product".equals(profile)) // 生产环境关闭
// .select() .enable(!"product".equals(profile))
// .apis(RequestHandlerSelectors.basePackage("com.zq.imgproc.controller")) .select()
// .paths(PathSelectors.any()) .apis(RequestHandlerSelectors.basePackage("com.zq.imgproc.controller"))
// .build() .paths(PathSelectors.any())
// .apiInfo(apiInfo()); .build()
.apiInfo(apiInfo());
} }
private ApiInfo apiInfo() { private ApiInfo apiInfo() {
......
package com.zq.imgproc.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
/**
* <p>
* 线程池配置
* </P>
*
* @author chenhao
* @since 2023/5/29
*/
@EnableAsync
@Configuration
public class ThreadPoolConfig {
@Bean("detectionExecutor")
public Executor asyncServiceExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 设置核心线程数等于系统核数
int availableProcessors = Runtime.getRuntime().availableProcessors();
executor.setCorePoolSize(availableProcessors);
// 设置最大线程数
executor.setMaxPoolSize(availableProcessors * 2);
//配置队列大小
executor.setQueueCapacity(2048);
// 设置线程活跃时间(秒)
executor.setKeepAliveSeconds(30);
// 设置拒绝策略
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
// 设置默认线程名称
executor.setThreadNamePrefix("detection-thread");
// 等待所有任务结束后再关闭线程池
executor.setWaitForTasksToCompleteOnShutdown(true);
//执行初始化
executor.initialize();
return executor;
}
}
...@@ -43,4 +43,9 @@ public class Threshold { ...@@ -43,4 +43,9 @@ public class Threshold {
*/ */
public static final int BLACK = 100; public static final int BLACK = 100;
/**
* 图片DPI
*/
public static final int DPI = 30;
} }
package com.zq.imgproc.controller; package com.zq.imgproc.controller;
import cn.hutool.core.codec.Base64Encoder;
import cn.hutool.core.io.FileUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.zq.common.annotation.rest.AnonymousGetMapping; import com.zq.common.annotation.rest.AnonymousGetMapping;
import com.zq.common.annotation.rest.AnonymousPostMapping;
import com.zq.common.utils.AssertUtils; import com.zq.common.utils.AssertUtils;
import com.zq.common.utils.UuidUtils;
import com.zq.common.vo.ResultVo; import com.zq.common.vo.ResultVo;
import com.zq.imgproc.server.ImgProcService; import com.zq.imgproc.service.ApiService;
import com.zq.imgproc.utils.*;
import com.zq.imgproc.vo.DetectionVO;
import com.zq.imgproc.vo.OptimizationReq; import com.zq.imgproc.vo.OptimizationReq;
import com.zq.imgproc.vo.OptimizationVO; import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
/** /**
* <p> * <p>
...@@ -33,7 +22,7 @@ import java.util.HashMap; ...@@ -33,7 +22,7 @@ import java.util.HashMap;
* @author chenhao * @author chenhao
* @since 2023/3/18 9:32 * @since 2023/3/18 9:32
*/ */
@io.swagger.annotations.Api(tags = "图片处理API") @Api(tags = "图片处理API")
@RequestMapping("/imgproc/v1") @RequestMapping("/imgproc/v1")
@RestController @RestController
public class ApiController { public class ApiController {
...@@ -43,9 +32,9 @@ public class ApiController { ...@@ -43,9 +32,9 @@ public class ApiController {
@Value("${imgconfig.deskew}") @Value("${imgconfig.deskew}")
String deskewUrl; String deskewUrl;
private final ImgProcService service; private final ApiService service;
@Autowired @Autowired
public ApiController(ImgProcService service) { public ApiController(ApiService service) {
this.service = service; this.service = service;
} }
...@@ -56,111 +45,11 @@ public class ApiController { ...@@ -56,111 +45,11 @@ public class ApiController {
} }
@ApiOperation("图片检测") @ApiOperation("图片检测")
@PostMapping ("/detection") @AnonymousPostMapping("/detection")
public ResultVo<DetectionVO> detection(@RequestBody OptimizationReq req) throws Exception { public ResultVo<?> detection(@RequestBody OptimizationReq req) throws Exception {
AssertUtils.hasText(req.getFileContent(), "缺少文件内容");
AssertUtils.hasText(req.getFilename(), "缺少文件名");
return ResultVo.success(service.detection2(req));
}
@ApiOperation("图片校正(deskew工具)")
@PostMapping("/imageCorrection")
public ResultVo<?> imageCorrection(@RequestPart MultipartFile file) {
if (file.isEmpty()) {
return ResultVo.fail("文件不能为空");
}
String imgPath = UploadUtils.saveTempFile(file, "api");
String ext = FileUtil.extName(file.getOriginalFilename());
String yyyyMMdd = new SimpleDateFormat("/yyyyMM/dd/").format(new Date());
String savePath = "/file/temp" + yyyyMMdd + UuidUtils.uuidNoDash() + "." + ext;
// 图片矫正
ImageCorrectionUtil.deskew(imgPath, savePath, deskewUrl, false);
return ResultVo.success(Base64Encoder.encode(FileUtil.readBytes(savePath)));
}
@ApiOperation("去黑边")
@PostMapping("/removeBlack")
public ResultVo<?> removeBlack(@RequestPart MultipartFile file) {
if (file.isEmpty()) {
return ResultVo.fail("文件不能为空");
}
System.load(opencvUrl);
String imgPath = UploadUtils.saveTempFile(file, "api");
String ext = FileUtil.extName(file.getOriginalFilename());
String yyyyMMdd = new SimpleDateFormat("/yyyyMM/dd/").format(new Date());
String savePath = "/file/temp" + yyyyMMdd + UuidUtils.uuidNoDash() + "." + ext;
// 去黑边
RemoveBlackUtil2.remove(imgPath, savePath);
return ResultVo.success(Base64Encoder.encode(savePath));
}
@ApiOperation("图片灰度化")
@PostMapping("/gray")
public ResultVo<?> gray(@RequestPart MultipartFile file) {
if (file.isEmpty()) {
return ResultVo.fail("文件不能为空");
}
System.load(opencvUrl);
String imgPath = UploadUtils.saveTempFile(file, "api");
String ext = FileUtil.extName(file.getOriginalFilename());
String yyyyMMdd = new SimpleDateFormat("/yyyyMM/dd/").format(new Date());
String savePath = "/file/temp" + yyyyMMdd + UuidUtils.uuidNoDash() + "." + ext;
// 灰度化
ImageUtil.gray(imgPath, savePath);
return ResultVo.success(Base64Encoder.encode(FileUtil.readBytes(savePath)));
}
@ApiOperation("图片边缘检测")
@PostMapping("/canny")
public ResultVo<?> canny(@RequestPart MultipartFile file) {
if (file.isEmpty()) {
return ResultVo.fail("文件不能为空");
}
System.load(opencvUrl);
String imgPath = UploadUtils.saveTempFile(file, "api");
String ext = FileUtil.extName(file.getOriginalFilename());
String yyyyMMdd = new SimpleDateFormat("/yyyyMM/dd/").format(new Date());
String savePath = "/file/temp" + yyyyMMdd + UuidUtils.uuidNoDash() + "." + ext;
// 边缘检测
ImageUtil.canny(imgPath, savePath);
return ResultVo.success(Base64Encoder.encode(FileUtil.readBytes(savePath)));
}
@ApiOperation("图片弯曲矫正")
@PostMapping("/correct")
public ResultVo<?> correct(@RequestPart MultipartFile file) {
if (file.isEmpty()) {
return ResultVo.fail("文件不能为空");
}
String imgPath = UploadUtils.saveTempFile(file, "api");
// 图片弯曲矫正
HashMap<String, Object> map = new HashMap<>();
map.put("file", FileUtil.file(imgPath));
String response = HttpUtil.post("http://ddns.gxmailu.com:18888/api/correct", map);
JSONObject res = JSONUtil.parseObj(response);
if (res.get("success", Boolean.class)) {
String base64 = res.get("data", String.class);
return ResultVo.success(base64);
} else {
return ResultVo.fail(res.get("msg", String.class));
}
}
@ApiOperation("图片优化")
@PostMapping("/imageOptimization")
public ResultVo<OptimizationVO> imageOptimization(@RequestBody OptimizationReq req) throws IOException {
AssertUtils.hasText(req.getFileContent(), "缺少文件内容"); AssertUtils.hasText(req.getFileContent(), "缺少文件内容");
AssertUtils.hasText(req.getFilename(), "缺少文件名"); AssertUtils.hasText(req.getFileName(), "缺少文件名");
return ResultVo.success(service.optimization(req)); return service.detection(req);
} }
} }
...@@ -40,7 +40,7 @@ public class ImageBatchController { ...@@ -40,7 +40,7 @@ public class ImageBatchController {
private final ImageDetectionDao imageDetectionDao; private final ImageDetectionDao imageDetectionDao;
private final ImageBatchDao imageBatchDao; private final ImageBatchDao imageBatchDao;
@ApiOperation("根据ID查询") @ApiOperation("根据ID查询不合格页数")
@GetMapping("/unqualified/{id}") @GetMapping("/unqualified/{id}")
public ResultVo<Long> unqualified(@PathVariable Long id) { public ResultVo<Long> unqualified(@PathVariable Long id) {
return ResultVo.success(imageDetectionDao.selectCount( return ResultVo.success(imageDetectionDao.selectCount(
...@@ -48,8 +48,24 @@ public class ImageBatchController { ...@@ -48,8 +48,24 @@ public class ImageBatchController {
)); ));
} }
@ApiOperation("获取批号的完成百分比")
@GetMapping("/getCompleteDegree/{id}")
public ResultVo<Integer> getCompleteDegree(@PathVariable Long id) {
long count = imageDetectionDao.selectCount(
Wrappers.lambdaQuery(ImageDetection.builder().batchId(id).build())
);
if (count == 0) {
return ResultVo.success(0);
}
long complete = imageDetectionDao.selectCount(
Wrappers.lambdaQuery(ImageDetection.builder().batchId(id).build()).isNotNull(ImageDetection::getQualified)
);
return ResultVo.success(Convert.toInt(complete / count) * 100);
}
@ApiOperation("根据ID查询") @ApiOperation("根据ID查询")
@GetMapping("/sele/{id}") @GetMapping("/selectOne/{id}")
public ResultVo<ImageBatch> selectOne(@PathVariable Long id) { public ResultVo<ImageBatch> selectOne(@PathVariable Long id) {
return ResultVo.success(imageBatchDao.selectById(id)); return ResultVo.success(imageBatchDao.selectById(id));
} }
...@@ -70,6 +86,7 @@ public class ImageBatchController { ...@@ -70,6 +86,7 @@ public class ImageBatchController {
if (StrUtil.isNotBlank(req.getEndTime())) { if (StrUtil.isNotBlank(req.getEndTime())) {
wrapper.lt(ImageBatch::getCreateTime, req.getEndTime()); wrapper.lt(ImageBatch::getCreateTime, req.getEndTime());
} }
wrapper.orderByDesc(ImageBatch::getId);
IPage<ImageBatch> page = new Page<>(req.getPage(), req.getSize()); IPage<ImageBatch> page = new Page<>(req.getPage(), req.getSize());
page = imageBatchDao.selectPage(page, wrapper); page = imageBatchDao.selectPage(page, wrapper);
return ResultVo.success(PageVo.ofReqVo(req, page.getRecords(), Convert.toInt(page.getSize()))); return ResultVo.success(PageVo.ofReqVo(req, page.getRecords(), Convert.toInt(page.getSize())));
......
...@@ -4,7 +4,7 @@ import com.zq.common.utils.AssertUtils; ...@@ -4,7 +4,7 @@ import com.zq.common.utils.AssertUtils;
import com.zq.common.vo.PageVo; import com.zq.common.vo.PageVo;
import com.zq.common.vo.ResultVo; import com.zq.common.vo.ResultVo;
import com.zq.imgproc.entity.ImageDetection; import com.zq.imgproc.entity.ImageDetection;
import com.zq.imgproc.server.ImageDetectionService; import com.zq.imgproc.service.ImageDetectionService;
import com.zq.imgproc.vo.DetectionPageReq; import com.zq.imgproc.vo.DetectionPageReq;
import io.swagger.annotations.Api; import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiOperation;
...@@ -54,6 +54,14 @@ public class ImageDetectionController { ...@@ -54,6 +54,14 @@ public class ImageDetectionController {
return service.detection(userId, nickName, batchName, file); return service.detection(userId, nickName, batchName, file);
} }
@ApiOperation("重新检测")
@GetMapping("/reDetection/{batchId}/{userId}")
public ResultVo<?> reDetection(@PathVariable Long batchId, @PathVariable Long userId) {
AssertUtils.notNull(batchId, "批次ID不能为空!");
AssertUtils.notNull(userId, "用户ID不能为空!");
return service.reDetection(batchId, userId);
}
@ApiOperation("导出检测结果") @ApiOperation("导出检测结果")
@GetMapping("/getDetection/{batchId}") @GetMapping("/getDetection/{batchId}")
public ResponseEntity<Resource> getDetection(@PathVariable Long batchId) { public ResponseEntity<Resource> getDetection(@PathVariable Long batchId) {
......
...@@ -4,7 +4,7 @@ import cn.hutool.core.io.FileUtil; ...@@ -4,7 +4,7 @@ import cn.hutool.core.io.FileUtil;
import cn.hutool.extra.servlet.ServletUtil; import cn.hutool.extra.servlet.ServletUtil;
import com.zq.common.utils.AssertUtils; import com.zq.common.utils.AssertUtils;
import com.zq.common.vo.ResultVo; import com.zq.common.vo.ResultVo;
import com.zq.imgproc.server.ImgProcService; import com.zq.imgproc.service.ImgProcService;
import com.zq.imgproc.utils.DecompressUtil; import com.zq.imgproc.utils.DecompressUtil;
import com.zq.imgproc.utils.ImageUtil; import com.zq.imgproc.utils.ImageUtil;
import com.zq.imgproc.vo.*; import com.zq.imgproc.vo.*;
......
...@@ -30,7 +30,9 @@ public class ImgSettingController { ...@@ -30,7 +30,9 @@ public class ImgSettingController {
@ApiOperation("根据用户ID获取指标") @ApiOperation("根据用户ID获取指标")
@GetMapping("/getSetting/{userId}") @GetMapping("/getSetting/{userId}")
public ResultVo<?> getSetting(@PathVariable long userId) { public ResultVo<?> getSetting(@PathVariable long userId) {
ImgSetting setting = imgSettingDao.selectOne(Wrappers.lambdaQuery(ImgSetting.builder().userId(userId).build())); ImgSetting setting = imgSettingDao.selectOne(
Wrappers.lambdaQuery(ImgSetting.builder().userId(userId).build())
);
// 找寻不到用户的指标,则返回默认值 // 找寻不到用户的指标,则返回默认值
if (setting == null) { if (setting == null) {
setting = ImgSetting.builder() setting = ImgSetting.builder()
...@@ -40,6 +42,7 @@ public class ImgSettingController { ...@@ -40,6 +42,7 @@ public class ImgSettingController {
.bend(Threshold.BEND) .bend(Threshold.BEND)
.deskewAngel(Threshold.DESKEW) .deskewAngel(Threshold.DESKEW)
.black(Threshold.BLACK) .black(Threshold.BLACK)
.dpi(Threshold.DPI)
.build(); .build();
} }
return ResultVo.success(setting); return ResultVo.success(setting);
...@@ -52,6 +55,12 @@ public class ImgSettingController { ...@@ -52,6 +55,12 @@ public class ImgSettingController {
return ResultVo.fail("用户ID不能为空!"); return ResultVo.fail("用户ID不能为空!");
} }
if (imgSetting.getDpi() != null) {
if (imgSetting.getDpi() < 0) {
return ResultVo.fail("dpi不能小于0!");
}
}
if (imgSetting.getBrightnessStart() != null) { if (imgSetting.getBrightnessStart() != null) {
if (imgSetting.getBrightnessStart() < 0 || imgSetting.getBrightnessStart() > 255) { if (imgSetting.getBrightnessStart() < 0 || imgSetting.getBrightnessStart() > 255) {
return ResultVo.fail("亮度值的范围在0到255之间!"); return ResultVo.fail("亮度值的范围在0到255之间!");
......
package com.zq.imgproc.service;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.io.FileUtil;
import com.zq.common.utils.UuidUtils;
import com.zq.common.vo.ResultVo;
import com.zq.imgproc.utils.Deskew;
import com.zq.imgproc.utils.ImageUtil;
import com.zq.imgproc.vo.ApiDetectionVO;
import com.zq.imgproc.vo.OptimizationReq;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.opencv.core.Mat;
import org.opencv.imgcodecs.Imgcodecs;
import org.springframework.stereotype.Service;
import java.io.File;
import java.math.BigDecimal;
import java.math.RoundingMode;
/**
* <p>
*
* </p>
*
* @author chenhao
* @since 2023/12/1
*/
@Slf4j
@RequiredArgsConstructor
@Service
public class ApiService {
public ResultVo<?> detection(OptimizationReq req) {
ApiDetectionVO vo = new ApiDetectionVO();
try {
String ext = FileUtil.extName(req.getFileName());
String filePath = "/data/temp/" + UuidUtils.uuidNoDash() + "." + ext;
File image = FileUtil.file(filePath);
FileUtil.writeBytes(Base64.decode(req.getFileContent()), image);
Mat src = Imgcodecs.imread(filePath);
vo.setFileName(req.getFileName());
vo.setSize(transformMb(image.length()));
// 检测图片的分辨率
vo.setWidthResolution(src.width());
vo.setHeightResolution(src.height());
// 检测图片的DPI
vo.setDpi(ImageUtil.getDpi(image));
// 检测图片清晰度
BigDecimal clarity = BigDecimal.valueOf(ImageUtil.tenengrad(src));
vo.setClarity(clarity.setScale(2, RoundingMode.HALF_UP).doubleValue());
// 检测图片的亮度
vo.setBrightness(Convert.toInt(ImageUtil.brightness(src)));
// 检测图片倾斜角度
vo.setDeskewAngel(Math.abs(Deskew.getDeskewAngle(src)));
// 检测图片的黑边
vo.setBlack(ImageUtil.blackDetection2(src));
// 图片弯曲检测
// BendResult bendResult = BendUtil.getBendResult(filePath);
// vo.setBend(bendResult.getConfidence());
vo.setBend(0.0);
FileUtil.del(filePath);
} catch (Exception e) {
log.error("图片检测出现异常", e);
return ResultVo.fail("图片检测出现异常!");
}
return ResultVo.success(vo);
}
/**
* 将字节数转换为MB
*/
private double transformMb(long size) {
BigDecimal sizeDecimal = BigDecimal.valueOf(size);
BigDecimal mb = BigDecimal.valueOf(1024 * 1024);
return sizeDecimal.divide(mb, 2, RoundingMode.DOWN).doubleValue();
}
}
package com.zq.imgproc.server; package com.zq.imgproc.service;
import cn.hutool.core.convert.Convert; import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateUtil; import cn.hutool.core.date.DateUtil;
...@@ -21,11 +21,15 @@ import com.zq.imgproc.utils.BendUtil; ...@@ -21,11 +21,15 @@ import com.zq.imgproc.utils.BendUtil;
import com.zq.imgproc.utils.DecompressUtil; import com.zq.imgproc.utils.DecompressUtil;
import com.zq.imgproc.utils.Deskew; import com.zq.imgproc.utils.Deskew;
import com.zq.imgproc.utils.ImageUtil; import com.zq.imgproc.utils.ImageUtil;
import com.zq.imgproc.vo.*; import com.zq.imgproc.vo.BendResult;
import lombok.RequiredArgsConstructor; import com.zq.imgproc.vo.DetectionPageReq;
import com.zq.imgproc.vo.ImageDetectionDTO;
import com.zq.imgproc.vo.ImgVO;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.opencv.core.Mat; import org.opencv.core.Mat;
import org.opencv.imgcodecs.Imgcodecs; import org.opencv.imgcodecs.Imgcodecs;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.io.InputStreamResource; import org.springframework.core.io.InputStreamResource;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.http.ContentDisposition; import org.springframework.http.ContentDisposition;
...@@ -41,6 +45,7 @@ import java.math.BigDecimal; ...@@ -41,6 +45,7 @@ import java.math.BigDecimal;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.Executor;
/** /**
* <p> * <p>
...@@ -51,13 +56,18 @@ import java.util.List; ...@@ -51,13 +56,18 @@ import java.util.List;
* @since 2023/11/28 * @since 2023/11/28
*/ */
@Slf4j @Slf4j
@RequiredArgsConstructor
@Service @Service
public class ImageDetectionService { public class ImageDetectionService {
private final ImgSettingDao imgSettingDao; @Autowired
private final ImageDetectionDao imageDetectionDao; @Qualifier("detectionExecutor")
private final ImageBatchDao imageBatchDao; private Executor detectionExecutor;
@Autowired
private ImgSettingDao imgSettingDao;
@Autowired
private ImageDetectionDao imageDetectionDao;
@Autowired
private ImageBatchDao imageBatchDao;
public ResultVo<?> detection(long userId, String nickName, String batchName, MultipartFile[] file) throws Exception { public ResultVo<?> detection(long userId, String nickName, String batchName, MultipartFile[] file) throws Exception {
ImageBatch imageBatch = ImageBatch.builder() ImageBatch imageBatch = ImageBatch.builder()
...@@ -83,6 +93,7 @@ public class ImageDetectionService { ...@@ -83,6 +93,7 @@ public class ImageDetectionService {
.bend(Threshold.BEND) .bend(Threshold.BEND)
.deskewAngel(Threshold.DESKEW) .deskewAngel(Threshold.DESKEW)
.black(Threshold.BLACK) .black(Threshold.BLACK)
.dpi(Threshold.DPI)
.build(); .build();
} }
// 解压文件 // 解压文件
...@@ -118,13 +129,38 @@ public class ImageDetectionService { ...@@ -118,13 +129,38 @@ public class ImageDetectionService {
detection.setFileUrl(img.getUrl()); detection.setFileUrl(img.getUrl());
detection.setSize(transformMb(image.length())); detection.setSize(transformMb(image.length()));
detections.add(detection);
}
// 批量插入
imageDetectionDao.insertBatch(detections);
// 进行图片检测
ImgSetting finalImgSetting = imgSetting;
List<ImageDetection> finalDetections = imageDetectionDao.selectList(
Wrappers.lambdaQuery(ImageDetection.builder().batchId(imageBatch.getId()).build())
);
for (ImageDetection detection : finalDetections) {
detectionExecutor.execute(() -> {
try {
detectionImage(detection, finalImgSetting);
} catch (Exception e) {
log.error("图片检测出错!", e);
}
});
}
return ResultVo.success(detections);
}
private void detectionImage(ImageDetection detection, ImgSetting imgSetting) throws Exception {
// 设置图片检测信息 // 设置图片检测信息
Mat src = Imgcodecs.imread(img.getUrl()); String url = detection.getFileUrl();
long start = System.currentTimeMillis();
Mat src = Imgcodecs.imread(url);
// 检测图片的分辨率 // 检测图片的分辨率
detection.setWidthResolution(src.width()); detection.setWidthResolution(src.width());
detection.setHeightResolution(src.height()); detection.setHeightResolution(src.height());
// 检测图片的DPI // 检测图片的DPI
detection.setDpi(ImageUtil.getDpi(image)); detection.setDpi(ImageUtil.getDpi(FileUtil.file(url)));
// 检测图片清晰度 // 检测图片清晰度
BigDecimal clarity = BigDecimal.valueOf(ImageUtil.tenengrad(src)); BigDecimal clarity = BigDecimal.valueOf(ImageUtil.tenengrad(src));
detection.setClarity(clarity.setScale(2, RoundingMode.HALF_UP).doubleValue()); detection.setClarity(clarity.setScale(2, RoundingMode.HALF_UP).doubleValue());
...@@ -132,20 +168,20 @@ public class ImageDetectionService { ...@@ -132,20 +168,20 @@ public class ImageDetectionService {
detection.setBrightness(Convert.toInt(ImageUtil.brightness(src))); detection.setBrightness(Convert.toInt(ImageUtil.brightness(src)));
// 检测图片倾斜角度 // 检测图片倾斜角度
detection.setDeskewAngel(Math.abs(Deskew.getDeskewAngle(src))); detection.setDeskewAngel(Math.abs(Deskew.getDeskewAngle(src)));
long time1 = System.currentTimeMillis() - start;
// 检测图片的黑边 // 检测图片的黑边
detection.setBlack(ImageUtil.blackDetection2(src)); detection.setBlack(ImageUtil.blackDetection2(src));
long time2 = System.currentTimeMillis() - start;
// 图片弯曲检测 // 图片弯曲检测
BendResult bendResult = BendUtil.getBendResult(img.getUrl()); BendResult bendResult = BendUtil.getBendResult(url);
detection.setBend(bendResult.getConfidence()); detection.setBend(bendResult.getConfidence());
detection.setBend(0.0); // detection.setBend(0.0);
// 检查是否合格 // 检查是否合格
detection.setQualified(check(detection, imgSetting)); detection.setQualified(check(detection, imgSetting));
detections.add(detection); imageDetectionDao.updateById(detection);
} log.info("{}执行图片检测, 文件大小为:{}, 检测偏离度:{}, 检测黑边:{},耗时为:{}", url, detection.getSize(), time1, time2, System.currentTimeMillis() - start);
// 批量插入 src.release();
imageDetectionDao.insertBatch(detections);
return ResultVo.success(detections);
} }
/** /**
...@@ -156,7 +192,7 @@ public class ImageDetectionService { ...@@ -156,7 +192,7 @@ public class ImageDetectionService {
* 4. 黑边 * 4. 黑边
* 5. 弯曲 * 5. 弯曲
*/ */
private Integer check(ImageDetection detection, ImgSetting imgSetting) { public Integer check(ImageDetection detection, ImgSetting imgSetting) {
// 亮度 // 亮度
if (detection.getBrightness() < imgSetting.getBrightnessStart() || detection.getBrightness() > imgSetting.getBrightnessEnd()) { if (detection.getBrightness() < imgSetting.getBrightnessStart() || detection.getBrightness() > imgSetting.getBrightnessEnd()) {
return 0; return 0;
...@@ -165,6 +201,10 @@ public class ImageDetectionService { ...@@ -165,6 +201,10 @@ public class ImageDetectionService {
if (detection.getClarity().compareTo(imgSetting.getClarity()) < 0) { if (detection.getClarity().compareTo(imgSetting.getClarity()) < 0) {
return 0; return 0;
} }
// DPI
if (detection.getDpi() < imgSetting.getDpi()) {
return 0;
}
// 偏离度 // 偏离度
if (detection.getDeskewAngel() > imgSetting.getDeskewAngel()) { if (detection.getDeskewAngel() > imgSetting.getDeskewAngel()) {
return 0; return 0;
...@@ -183,7 +223,7 @@ public class ImageDetectionService { ...@@ -183,7 +223,7 @@ public class ImageDetectionService {
/** /**
* 将字节数转换为MB * 将字节数转换为MB
*/ */
private double transformMb(long size) { public double transformMb(long size) {
BigDecimal sizeDecimal = BigDecimal.valueOf(size); BigDecimal sizeDecimal = BigDecimal.valueOf(size);
BigDecimal mb = BigDecimal.valueOf(1024 * 1024); BigDecimal mb = BigDecimal.valueOf(1024 * 1024);
return sizeDecimal.divide(mb, 2, RoundingMode.DOWN).doubleValue(); return sizeDecimal.divide(mb, 2, RoundingMode.DOWN).doubleValue();
...@@ -241,4 +281,40 @@ public class ImageDetectionService { ...@@ -241,4 +281,40 @@ public class ImageDetectionService {
return dtos; return dtos;
} }
public ResultVo<?> reDetection(Long batchId, Long userId) {
List<ImageDetection> finalDetections = imageDetectionDao.selectList(
Wrappers.lambdaQuery(ImageDetection.builder().batchId(batchId).build()).isNull(ImageDetection::getQualified)
);
if (finalDetections.isEmpty()) {
return ResultVo.fail("暂无未检测图片!");
}
// 查询用户配置
ImgSetting imgSetting = imgSettingDao.selectOne(
Wrappers.lambdaQuery(ImgSetting.builder().userId(userId).build())
);
// 查询不到用户配置,使用默认值进行检测
if (imgSetting == null) {
imgSetting = ImgSetting.builder()
.clarity(Threshold.CLARITY)
.brightnessStart(Threshold.BRIGHTNESS_START)
.brightnessEnd(Threshold.BRIGHTNESS_END)
.bend(Threshold.BEND)
.deskewAngel(Threshold.DESKEW)
.black(Threshold.BLACK)
.dpi(Threshold.DPI)
.build();
}
for (ImageDetection detection : finalDetections) {
ImgSetting finalImgSetting = imgSetting;
detectionExecutor.execute(() -> {
try {
detectionImage(detection, finalImgSetting);
} catch (Exception e) {
log.error("图片检测出错!", e);
}
});
}
return ResultVo.success();
}
} }
package com.zq.imgproc.server; package com.zq.imgproc.service;
import cn.hutool.core.codec.Base64Decoder; import cn.hutool.core.codec.Base64Decoder;
...@@ -108,7 +108,7 @@ public class ImgProcService { ...@@ -108,7 +108,7 @@ public class ImgProcService {
public DetectionVO detection2(OptimizationReq req) throws Exception { public DetectionVO detection2(OptimizationReq req) throws Exception {
// 1. 临时保存图片 // 1. 临时保存图片
String ext = FileUtil.extName(req.getFilename()); String ext = FileUtil.extName(req.getFileName());
String yyyyMMdd = new SimpleDateFormat("/yyyyMM/dd/").format(new Date()); String yyyyMMdd = new SimpleDateFormat("/yyyyMM/dd/").format(new Date());
String filePath = "/data/temp" + yyyyMMdd + UuidUtils.uuidNoDash() + "." + ext; String filePath = "/data/temp" + yyyyMMdd + UuidUtils.uuidNoDash() + "." + ext;
FileUtil.writeBytes(Base64Decoder.decode(req.getFileContent()), filePath); FileUtil.writeBytes(Base64Decoder.decode(req.getFileContent()), filePath);
...@@ -428,8 +428,8 @@ public class ImgProcService { ...@@ -428,8 +428,8 @@ public class ImgProcService {
public OptimizationVO optimization(OptimizationReq req) throws IOException { public OptimizationVO optimization(OptimizationReq req) throws IOException {
OptimizationVO res = new OptimizationVO(); OptimizationVO res = new OptimizationVO();
// 1. 临时保存图片 // 1. 临时保存图片
String ext = FileUtil.extName(req.getFilename()); String ext = FileUtil.extName(req.getFileName());
String filename = req.getFilename(); String filename = req.getFileName();
String yyyyMMdd = new SimpleDateFormat("/yyyyMM/dd/").format(new Date()); String yyyyMMdd = new SimpleDateFormat("/yyyyMM/dd/").format(new Date());
String savePath = "/data/temp" + yyyyMMdd + UuidUtils.uuidNoDash() + "/"; String savePath = "/data/temp" + yyyyMMdd + UuidUtils.uuidNoDash() + "/";
String filePath = savePath + filename; String filePath = savePath + filename;
...@@ -555,7 +555,7 @@ public class ImgProcService { ...@@ -555,7 +555,7 @@ public class ImgProcService {
public Boolean recognizeRed(OptimizationReq req) { public Boolean recognizeRed(OptimizationReq req) {
// 暂时保存 // 暂时保存
String filename = req.getFilename(); String filename = req.getFileName();
String yyyyMMdd = new SimpleDateFormat("/yyyyMM/dd/").format(new Date()); String yyyyMMdd = new SimpleDateFormat("/yyyyMM/dd/").format(new Date());
String savePath = "/data/temp" + yyyyMMdd + UuidUtils.uuidNoDash() + "/"; String savePath = "/data/temp" + yyyyMMdd + UuidUtils.uuidNoDash() + "/";
String filePath = savePath + filename; String filePath = savePath + filename;
...@@ -591,8 +591,8 @@ public class ImgProcService { ...@@ -591,8 +591,8 @@ public class ImgProcService {
public String removeRed(OptimizationReq req) { public String removeRed(OptimizationReq req) {
// 暂时保存 // 暂时保存
String ext = FileUtil.extName(req.getFilename()); String ext = FileUtil.extName(req.getFileName());
String filename = req.getFilename(); String filename = req.getFileName();
String yyyyMMdd = new SimpleDateFormat("/yyyyMM/dd/").format(new Date()); String yyyyMMdd = new SimpleDateFormat("/yyyyMM/dd/").format(new Date());
String savePath = "/data/temp" + yyyyMMdd + UuidUtils.uuidNoDash() + "/"; String savePath = "/data/temp" + yyyyMMdd + UuidUtils.uuidNoDash() + "/";
String filePath = savePath + filename; String filePath = savePath + filename;
......
...@@ -23,9 +23,10 @@ import java.util.List; ...@@ -23,9 +23,10 @@ import java.util.List;
@Slf4j @Slf4j
public class BendUtil { public class BendUtil {
private final static String URL = "http://129.204.37.121:8001/api/det/bending"; public final static String MAIN_URL = "http://147.1.3.244:8030/api/det/bending";
private final static String WANPRO_URL = "http://172.28.1.223:8030/api/det/bending"; public final static String URL = "http://129.204.37.121:8001/api/det/bending";
private final static String URL2 = "http://147.1.3.244:8030/api/det/bending"; public final static String WANPRO_URL = "http://172.28.1.223:8030/api/det/bending";
// public final static String URL2 = "http://147.1.3.244:8030/api/det/bending";
public static void main(String[] args) { public static void main(String[] args) {
System.out.println(getBendResult("C:\\Users\\11419\\Desktop\\project\\company\\test\\9.png").toString()); System.out.println(getBendResult("C:\\Users\\11419\\Desktop\\project\\company\\test\\9.png").toString());
...@@ -38,7 +39,7 @@ public class BendUtil { ...@@ -38,7 +39,7 @@ public class BendUtil {
BendResult res = null; BendResult res = null;
try { try {
String result = HttpUtil.post(URL2, param, 1000 * 20); String result = HttpUtil.post(MAIN_URL, param, 1000 * 20);
res = parseResult(result); res = parseResult(result);
} catch (Exception e) { } catch (Exception e) {
log.error("图片弯曲检测失败!", e); log.error("图片弯曲检测失败!", e);
......
...@@ -25,7 +25,7 @@ public class DemoUtil { ...@@ -25,7 +25,7 @@ public class DemoUtil {
String url = URL + "detection"; String url = URL + "detection";
OptimizationReq req = new OptimizationReq(); OptimizationReq req = new OptimizationReq();
req.setFilename("3.png"); req.setFileName("3.png");
req.setFileContent(Base64.encode(FileUtil.file(testImg))); req.setFileContent(Base64.encode(FileUtil.file(testImg)));
long start = System.currentTimeMillis(); long start = System.currentTimeMillis();
HttpRequest request = HttpUtil.createPost(url) HttpRequest request = HttpUtil.createPost(url)
...@@ -45,7 +45,7 @@ public class DemoUtil { ...@@ -45,7 +45,7 @@ public class DemoUtil {
String url = URL + "imageOptimization"; String url = URL + "imageOptimization";
OptimizationReq req = new OptimizationReq(); OptimizationReq req = new OptimizationReq();
req.setFilename("4.png"); req.setFileName("4.png");
req.setFileContent(Base64.encode(FileUtil.file(testImg))); req.setFileContent(Base64.encode(FileUtil.file(testImg)));
long start = System.currentTimeMillis(); long start = System.currentTimeMillis();
HttpRequest request = HttpUtil.createPost(url) HttpRequest request = HttpUtil.createPost(url)
......
...@@ -14,11 +14,13 @@ public class Deskew { ...@@ -14,11 +14,13 @@ public class Deskew {
public static void main(String[] args) { public static void main(String[] args) {
System.load("D:/project/imgproc/lib/opencv_java460.dll"); System.load("D:/project/imgproc/lib/opencv_java460.dll");
Mat image = Imgcodecs.imread("C:/Users/11419/Desktop/Deskew/TestImages/4.png"); Mat image = Imgcodecs.imread("C:/Users/11419/Desktop/test/dark.png");
long start = System.currentTimeMillis();
int angle = getDeskewAngle(image); int angle = getDeskewAngle(image);
System.out.println("耗时为:" + String.valueOf(System.currentTimeMillis() - start));
System.out.println(angle); System.out.println(angle);
Mat roateMat = rotate(image, angle); Mat roateMat = rotate(image, angle);
Imgcodecs.imwrite("C:/Users/11419/Desktop/Deskew/TestImages/4res.png", roateMat); Imgcodecs.imwrite("C:/Users/11419/Desktop/test/bend2res.png", roateMat);
} }
public static Integer getDeskewAngle(Mat src) { public static Integer getDeskewAngle(Mat src) {
...@@ -26,46 +28,38 @@ public class Deskew { ...@@ -26,46 +28,38 @@ public class Deskew {
Mat gray = src.clone(); Mat gray = src.clone();
Imgproc.cvtColor(src, gray, Imgproc.COLOR_BGR2GRAY); Imgproc.cvtColor(src, gray, Imgproc.COLOR_BGR2GRAY);
// // 高斯模糊(?) // 2.高斯滤波,降噪
// @Cleanup Mat gaussianBlur = gray.clone();
// UMat blur = gray.clone(); Imgproc.GaussianBlur(gray, gaussianBlur, new Size(3,3),0);
// GaussianBlur(gray, blur, new Size(9, 9), 0);
// // 二值化(?)
// @Cleanup
// UMat thresh = blur.clone();
// threshold(blur, thresh, 0, 255, THRESH_BINARY_INV + THRESH_OTSU);
Mat kernel = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(5, 5)); // 3. 边缘检测
Mat canny = gaussianBlur.clone();
Imgproc.Canny(gaussianBlur, canny, 50, 150);
// 图片膨胀 // 4. 膨胀,连接边缘
Mat erode = gray.clone(); Mat dilate = canny.clone();
Imgproc.erode(gray, erode, kernel); Imgproc.dilate(canny, dilate, new Mat(), new Point(-1, -1), 3, 1, new Scalar(1));
// 图片腐蚀
Mat dilate = erode.clone();
Imgproc.dilate(erode, dilate, kernel);
// 边缘检测
Mat canny = dilate.clone();
Imgproc.Canny(dilate, canny, 50, 150);
// 霍夫变换得到线条 // 霍夫变换得到线条
Mat lines = new Mat(); Mat lines = new Mat();
//累加器阈值参数,小于设置值不返回 //累加器阈值参数,小于设置值不返回
int threshold = 90; int threshold = 50;
//最低线段长度,低于设置值则不返回 //最低线段长度,低于设置值则不返回
double minLineLength = 100; double minLineLength = 150;
//间距小于该值的线当成同一条线 //间距小于该值的线当成同一条线
double maxLineGap = 10; double maxLineGap = 20;
// 霍夫变换,通过步长为1,角度为PI/180来搜索可能的直线 // 5. 霍夫变换,通过步长为1,角度为PI/180来搜索可能的直线
Imgproc.HoughLinesP(canny, lines, 1, Math.PI / 180, threshold, minLineLength, maxLineGap); Imgproc.HoughLinesP(canny, lines, 1, Math.PI / 180, threshold, minLineLength, maxLineGap);
// Mat mat = canny.clone();
// // 测试画图
// Mat mat = src.clone();
// Scalar color = new Scalar(0, 0, 255); // Scalar color = new Scalar(0, 0, 255);
// for (int i = 0; i < lines.rows(); i++) { // for (int i = 0; i < lines.rows(); i++) {
// double[] line = lines.get(i, 0); // double[] line = lines.get(i, 0);
// Imgproc.line(mat, new Point(line[0], line[1]), new Point(line[2], line[3]), color,3, LINE_AA); // Imgproc.line(mat, new Point(line[0], line[1]), new Point(line[2], line[3]), color,3, LINE_AA);
// } // }
// Imgcodecs.imwrite("C:\\Users\\11419\\Desktop\\test\\bend2res.jpg", mat); // Imgcodecs.imwrite("C:\\Users\\11419\\Desktop\\test\\bend2Test.png", mat);
// 计算倾斜角度 // 计算倾斜角度
List<Integer> angelList = new ArrayList<>(); List<Integer> angelList = new ArrayList<>();
for (int i = 0; i < lines.rows(); i++) { for (int i = 0; i < lines.rows(); i++) {
...@@ -77,8 +71,6 @@ public class Deskew { ...@@ -77,8 +71,6 @@ public class Deskew {
return 0; return 0;
} }
gray.release(); gray.release();
kernel.release();
erode.release();
dilate.release(); dilate.release();
canny.release(); canny.release();
lines.release(); lines.release();
......
...@@ -516,7 +516,7 @@ public class ImageUtil { ...@@ -516,7 +516,7 @@ public class ImageUtil {
* 检测黑边数量 * 检测黑边数量
*/ */
public static int blackDetection2(Mat src) { public static int blackDetection2(Mat src) {
int blackValue = 40; int blackValue = 50;
int width = src.width(); int width = src.width();
int height = src.height(); int height = src.height();
...@@ -581,7 +581,7 @@ public class ImageUtil { ...@@ -581,7 +581,7 @@ public class ImageUtil {
} }
} }
} }
return res; return res == -1 ? 0 : res;
} }
......
...@@ -5,9 +5,7 @@ import cn.hutool.core.io.FileUtil; ...@@ -5,9 +5,7 @@ import cn.hutool.core.io.FileUtil;
import cn.hutool.json.JSONUtil; import cn.hutool.json.JSONUtil;
import com.zq.imgproc.vo.DetectionVO; import com.zq.imgproc.vo.DetectionVO;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.opencv.core.Core;
import org.opencv.core.Mat; import org.opencv.core.Mat;
import org.opencv.core.Scalar;
import org.opencv.imgcodecs.Imgcodecs; import org.opencv.imgcodecs.Imgcodecs;
import java.math.BigDecimal; import java.math.BigDecimal;
...@@ -56,115 +54,10 @@ public class Test2 { ...@@ -56,115 +54,10 @@ public class Test2 {
log.info("检测图片倾斜角度耗时为:--------------{}", System.currentTimeMillis() - start); log.info("检测图片倾斜角度耗时为:--------------{}", System.currentTimeMillis() - start);
start = System.currentTimeMillis(); start = System.currentTimeMillis();
// 检测图片的黑边 // 检测图片的黑边
blackDetection2(image); System.out.println(ImageUtil.blackDetection2(image));
blackDetection3(image); log.info("检测黑边耗时为:-----------------{}", System.currentTimeMillis() - start);
System.out.println(JSONUtil.toJsonStr(vo)); System.out.println(JSONUtil.toJsonStr(vo));
} }
/**
* 黑边检测
*
* @param src 图片矩阵
* @return true表示可能存在黑边
*/
public static boolean blackDetection(Mat src) {
int BLACK_VALUE = 50;
int width = src.width();
int height = src.height();
// 定义初始边界
int top = 0;
int left = 0;
int right = width - 1;
int bottom = height - 1;
// 上方黑边判断
for (int row = 0; row < height; row++) {
if (ImageUtil.sum(src.row(row)) / width < BLACK_VALUE) {
top = row;
} else {
break;
}
}
// 左边黑边判断
for (int col = 0; col < width; col++) {
if (ImageUtil.sum(src.col(col)) / height < BLACK_VALUE) {
left = col;
} else {
break;
}
}
// 右边黑边判断
for (int col = width - 1; col > 0; col--) {
if (ImageUtil.sum(src.col(col)) / height < BLACK_VALUE) {
right = col;
} else {
break;
}
}
// 下方黑边判断
for (int row = height - 1; row > 0; row--) {
if (ImageUtil.sum(src.row(row)) / width < BLACK_VALUE) {
bottom = row;
} else {
break;
}
}
// 若是边界没有被更新,认为不存在黑边
return top != 0 || left != 0 || right != width - 1 || bottom != height - 1;
}
public static void blackDetection2(Mat src) {
long start = System.currentTimeMillis();
int BLACK_VALUE = 30;
int width = src.width();
int height = src.height();
int rows = 0;
int cols = 0;
// 上方黑边判断
for (int row = 0; row < height; row++) {
if (ImageUtil.sum(src.row(row)) / width < BLACK_VALUE) {
rows++;
}
}
// 左边黑边判断
for (int col = 0; col < width; col++) {
if (ImageUtil.sum(src.col(col)) / height < BLACK_VALUE) {
cols++;
}
}
log.info("{}, {}, {}, {}", "22222", rows, cols, System.currentTimeMillis() - start);
}
public static void blackDetection3(Mat src) {
long start = System.currentTimeMillis();
int BLACK_VALUE = 30;
int width = src.width();
int height = src.height();
int rows = 0;
int cols = 0;
int limitRows = 500;
int limitCols = 500;
for (int row = 0; row < height; row++) {
Scalar mean = Core.mean(src.row(row));
if (Convert.toInt(mean.val[0]) < BLACK_VALUE) {
rows++;
}
}
for (int col = 0; col < width; col++) {
Scalar mean = Core.mean(src.col(col));
if (Convert.toInt(mean.val[0]) < BLACK_VALUE) {
cols++;
}
}
log.info("{}, {}, {}, {}", "3333333", rows, cols, System.currentTimeMillis() - start);
}
} }
...@@ -6,12 +6,7 @@ import com.drew.imaging.ImageMetadataReader; ...@@ -6,12 +6,7 @@ import com.drew.imaging.ImageMetadataReader;
import com.drew.metadata.Directory; import com.drew.metadata.Directory;
import com.drew.metadata.Metadata; import com.drew.metadata.Metadata;
import com.drew.metadata.Tag; import com.drew.metadata.Tag;
import com.zq.imgproc.vo.DetectionVO;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.imaging.ImageInfo;
import org.apache.commons.imaging.Imaging;
import org.opencv.core.Mat;
import org.opencv.imgcodecs.Imgcodecs;
import java.io.File; import java.io.File;
...@@ -19,147 +14,18 @@ import java.io.File; ...@@ -19,147 +14,18 @@ import java.io.File;
public class TestUtil { public class TestUtil {
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {
System.load("D:/project/imgproc/lib/opencv_java460.dll"); String path = "C:\\Users\\11419\\Desktop\\test\\black1.png";
String src = "C:\\Users\\11419\\Desktop\\4.png"; File file = FileUtil.file(path);
String dst = "C:\\Users\\11419\\Desktop\\res.png"; String dest = "C:\\Users\\11419\\Desktop\\test\\black1.jpg";
// deskew(src, dst); Metadata metadata = ImageMetadataReader.readMetadata(FileUtil.file(dest));
RemoveBlackUtil2.remove(src, dst);
// bright(src, dst);
// im(src, dst);
}
public static void deskew(String src, String dst) {
Mat mat = Imgcodecs.imread(src);
double skewAngle = Deskew.getDeskewAngle(mat);
Mat rotateMat = Deskew.rotate(mat, skewAngle);
Imgcodecs.imwrite(dst, rotateMat);
}
public static void removeBlack(String src, String dst) {
RemoveBlackUtil2.remove(src, dst);
}
public static void bright(String src, String dst) {
double brightness = ImageUtil.brightness(src);
System.out.println(brightness);
// 亮度异常执行亮度矫正
if (brightness < 100 || brightness > 500) {
// 计算亮度调整值
double alpha = 300 / brightness;
// 执行亮度调整
Mat grayImage = Imgcodecs.imread(src);
Mat adjustedImage = new Mat();
grayImage.convertTo(adjustedImage, -1, alpha, 0);
// 保存调整后的图像
Imgcodecs.imwrite(dst, adjustedImage);
}
}
public static void im(String src, String dst) {
double laplacianScore = ImageUtil.imageSharpnessDetector(src);
System.out.println(laplacianScore);
if (laplacianScore < 500) {
Mat unsharpMaskingMat = ImageUtil.unsharpMasking(src);
// 保存调整后的图像
Imgcodecs.imwrite(dst, unsharpMaskingMat);
}
}
public static DetectionVO detection(String src) throws Exception {
DetectionVO res = new DetectionVO();
Mat image = Imgcodecs.imread(src);
// 检测图片的分辨率
res.setWidthResolution(image.width());
res.setHeightResolution(image.height());
// 检测图片的DPI
res.setDpi(getDpi(FileUtil.file(src)));
// 检测图片清晰度
res.setClarity(ImageUtil.imageSharpnessDetector(image));
// 检测图片的亮度
res.setBrightness(ImageUtil.brightness(image));
// 检测图片倾斜角度
res.setAngle(Deskew.getDeskewAngle(image));
// 检测图片的黑边
res.setBlack(blackDetection(image));
return res;
}
/**
* 获取图片DPI
*
* @param file 图片文件
* @return [水平DPI,垂直DPI]
*/
private static Integer getDpi(File file) throws Exception {
int res;
ImageInfo imageInfo = Imaging.getImageInfo(file);
res = imageInfo.getPhysicalWidthDpi();
if (res == -1) {
Metadata metadata = ImageMetadataReader.readMetadata(file);
for (Directory directory : metadata.getDirectories()) { for (Directory directory : metadata.getDirectories()) {
for (Tag tag : directory.getTags()) { for (Tag tag : directory.getTags()) {
System.out.println(tag.getTagName() + "--------" + tag.getDescription());
if ("X Resolution".equals(tag.getTagName())) { if ("X Resolution".equals(tag.getTagName())) {
res = Convert.toInt(tag.getDescription()); System.out.println(Convert.toInt(tag.getDescription()));
}
}
}
}
return res;
}
/**
* 黑边像素阈值
*/
private static final Integer THRESHOLD = 30;
/**
* 黑边检测
*
* @param src 图片矩阵
* @return true表示可能存在黑边
*/
public static boolean blackDetection(Mat src) {
int width = src.width();
int height = src.height();
// 定义初始边界
int top = 0;
int left = 0;
int right = width - 1;
int bottom = height - 1;
// 上方黑边判断
for (int row = 0; row < height; row++) {
if (ImageUtil.sum(src.row(row)) / width < THRESHOLD) {
top = row;
} else {
break;
}
}
// 左边黑边判断
for (int col = 0; col < width; col++) {
if (ImageUtil.sum(src.col(col)) / height < THRESHOLD) {
left = col;
} else {
break;
}
}
// 右边黑边判断
for (int col = width - 1; col > 0; col--) {
if (ImageUtil.sum(src.col(col)) / height < THRESHOLD) {
right = col;
} else {
break;
}
} }
// 下方黑边判断
for (int row = height - 1; row > 0; row--) {
if (ImageUtil.sum(src.row(row)) / width < THRESHOLD) {
bottom = row;
} else {
break;
} }
} }
// 若是边界没有被更新,认为不存在黑边
return top != 0 || left != 0 || right != width - 1 || bottom != height - 1;
} }
......
package com.zq.imgproc.vo;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* <p>
*
* </p>
*
* @author chenhao
* @since 2023/12/1
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ApiDetectionVO {
@ApiModelProperty("文件名称")
private String fileName;
@ApiModelProperty("图片大小")
private Double size;
@ApiModelProperty("水平分辨率")
private Integer widthResolution;
@ApiModelProperty("垂直分辨率")
private Integer heightResolution;
@ApiModelProperty("DPI")
private Integer dpi;
@ApiModelProperty("图片清晰度")
private Double clarity;
@ApiModelProperty("图片弯曲置信度")
private Double bend;
@ApiModelProperty("图片亮度值")
private Integer brightness;
@ApiModelProperty("图片偏离角度")
private Integer deskewAngel;
@ApiModelProperty("图片黑边数量")
private Integer black;
}
...@@ -15,6 +15,6 @@ public class OptimizationReq { ...@@ -15,6 +15,6 @@ public class OptimizationReq {
private String fileContent; private String fileContent;
private String filename; private String fileName;
} }
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>com.zq</groupId>
<artifactId>image-backend</artifactId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>interface</artifactId>
<version>1.0.0</version>
<name>interface-server</name>
<description>接口服务</description>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>
<!-- AOP -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--Spring boot Web容器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 注解执行器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 搭配redis的对象池依赖 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.12.0</version>
</dependency>
<!-- druid数据源驱动 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.6</version>
</dependency>
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
<!-- mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- swageer -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
</dependency>
<!-- commons-io -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
<!-- hutool -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.23</version>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.41</version>
</dependency>
<!-- jwt -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>4.2.0</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<includeSystemScope>true</includeSystemScope>
</configuration>
</plugin>
</plugins>
<!-- 使用@profiles.active@需要添加以下内容 -->
<resources>
<resource>
<directory>src/main/resources</directory>
<excludes>
<exclude>**/*.jar</exclude>
</excludes>
<!--开启过滤,用指定的参数替换directory下的文件中的参数-->
<filtering>true</filtering>
</resource>
</resources>
</build>
</project>
\ No newline at end of file
package com.zq.im;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
/**
* <p>
* 服务主启动类
* </p>
*
* @author chenhao
* @since 2022/10/12 20:57
*/
@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication()
public class InterfaceApplication {
public static void main(String[] args) {
SpringApplication.run(InterfaceApplication.class, args);
}
}
package com.zq.im.aspect;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.net.NetUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.servlet.ServletUtil;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.zq.im.exception.BusinessException;
import com.zq.im.modules.api.form.ApiForm;
import com.zq.im.modules.system.dao.ApiUserInfoDao;
import com.zq.im.modules.system.dao.OpenApiLogDao;
import com.zq.im.modules.system.entity.ApiUserInfo;
import com.zq.im.modules.system.entity.OpenApiLog;
import com.zq.im.utils.IpUtil;
import com.zq.im.utils.TokenUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
/**
* <p>
* 用户操作日志处理切面
* </p>
*
* @author chenhao
* @since 2022/10/15 17:23
*/
@Order(1)
@Aspect
@Slf4j
@Component
public class ApiLogAspect {
private final OpenApiLogDao openApiLogDao;
private final ApiUserInfoDao apiUserInfoDao;
@Autowired
public ApiLogAspect(OpenApiLogDao openApiLogDao, ApiUserInfoDao apiUserInfoDao) {
this.openApiLogDao = openApiLogDao;
this.apiUserInfoDao = apiUserInfoDao;
}
ThreadLocal<Long> currentTime = new ThreadLocal<>();
/**
* 声明切入点
*/
@Pointcut("@annotation(com.zq.im.aspect.OpenApi)")
public void pointCut() {}
/**
* 环绕通知
*/
@Around("pointCut()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
currentTime.set(System.currentTimeMillis());
// 执行目标方法
Object result = joinPoint.proceed();
// 计算执行耗时
long time = System.currentTimeMillis() - currentTime.get();
currentTime.remove();
// 获取方法名
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method signatureMethod = signature.getMethod();
// 获取注解信息
OpenApi openApi = AnnotationUtils.getAnnotation(signatureMethod, OpenApi.class);
// 获取当前请求对象
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = null;
if (attributes != null) {
request = attributes.getRequest();
}
// 保存日志
insertLog(openApi, request, "INFO", "", time);
return result;
}
/**
* 配置异常通知
*
* @param joinPoint join point for advice
* @param e exception
*/
@AfterThrowing(pointcut = "pointCut()", throwing = "e")
public void logAfterThrowing(JoinPoint joinPoint, Throwable e) {
// 计算执行耗时
long time = System.currentTimeMillis() - currentTime.get();
currentTime.remove();
String errMsg = "";
if (e instanceof BusinessException) {
errMsg = e.getMessage();
}
// 获取方法名
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method signatureMethod = signature.getMethod();
// 获取注解信息
OpenApi openApi = AnnotationUtils.getAnnotation(signatureMethod, OpenApi.class);
// 获取当前请求对象
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = null;
if (attributes != null) {
request = attributes.getRequest();
}
// 保存日志
insertLog(openApi, request, "ERROR", errMsg, time);
}
@Async
public void insertLog(OpenApi openApi, HttpServletRequest request, String logType, String errMsg, long timeCost) {
try {
// 获取IP信息
String clientIp = IpUtil.getIpAddr(request);
// 获取请求对象
ApiForm apiForm = ServletUtil.toBean(request, ApiForm.class, true);
OpenApiLog openApiLog = new OpenApiLog();
openApiLog.setLogType(logType);
DecodedJWT decodedJWT = TokenUtil.getUserContext();
if (decodedJWT != null) {
openApiLog.setAppId(TokenUtil.getAppid(decodedJWT));
openApiLog.setApplyer(TokenUtil.getApplyer(decodedJWT));
} else {
ApiUserInfo apiUserInfo = apiUserInfoDao.selectOne(
Wrappers.lambdaQuery(ApiUserInfo.builder().appId(apiForm.getAppId()).build())
);
if (apiUserInfo == null) {
openApiLog.setLogType("ERROR");
} else {
openApiLog.setAppId(apiUserInfo.getAppId());
openApiLog.setApplyer(apiUserInfo.getApplyer());
}
}
openApiLog.setVersion(getVersion(request));
openApiLog.setMethod(apiForm.getMethod());
openApiLog.setClientIp(clientIp);
openApiLog.setDescription(openApi == null ? "" : openApi.value());
openApiLog.setServerIp(NetUtil.getLocalhostStr());
openApiLog.setErrMsg(errMsg);
openApiLog.setTimeCost(timeCost);
openApiLog.setCreateTime(DateUtil.now());
// 保存日志
openApiLogDao.insert(openApiLog);
} catch (Exception e) {
log.error("记录日志发生错误", e);
}
}
/**
* 获取版本号
*/
public String getVersion(HttpServletRequest request) {
String requestUri = request.getRequestURI();
if (StrUtil.isBlank(requestUri)) {
log.warn("为获取到版本号,URI [{}]", request);
return "";
}
return requestUri.replace("/api/", "").replace("/action", "");
}
}
package com.zq.im.aspect;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* <p>
* 用于标记记录用户操作日志的方法
* </P>
*
* @author yww
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OpenApi {
String value() default "";
}
\ No newline at end of file
package com.zq.im.config;
import cn.hutool.core.lang.Assert;
import cn.hutool.crypto.digest.DigestUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONReader;
import com.alibaba.fastjson2.JSONWriter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.SerializationException;
import org.apache.commons.lang.StringUtils;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.cache.Cache;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import reactor.util.annotation.Nullable;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
/**
* <p>
* Redis配置类
* </p>
*
* @author chenhao
* @since 2022/10/12 20:57
*/
@Slf4j
@Configuration
@EnableCaching
@AutoConfigureBefore(RedisAutoConfiguration.class)
public class RedisConfig extends CachingConfigurerSupport {
/**
* RedisTemplate的配置
*/
@Bean(name = "redisTemplate")
@ConditionalOnMissingBean(name = "redisTemplate")
@SuppressWarnings(value = {"unchecked", "rawtypes"})
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
// 使用FastJson2重写序列化
FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);
// value值的序列化采用FastJson2重写的序列化
template.setValueSerializer(serializer);
template.setHashValueSerializer(serializer);
// key的序列化采用StringRedisSerializer
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
template.afterPropertiesSet();
return template;
}
/**
* 缓存配置
* 设置 redis 数据默认过期时间,默认3小时
*/
@Bean
public RedisCacheConfiguration redisCacheConfiguration() {
FastJson2JsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJson2JsonRedisSerializer<>(Object.class);
RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig();
configuration = configuration.serializeValuesWith(RedisSerializationContext.
SerializationPair.fromSerializer(fastJsonRedisSerializer)).entryTtl(Duration.ofHours(3));
return configuration;
}
/**
* 自定义缓存key生成策略,默认将使用该策略
*/
@Bean
@Override
public KeyGenerator keyGenerator() {
return (target, method, params) -> {
Map<String, Object> container = new HashMap<>(4);
Class<?> targetClassClass = target.getClass();
// 类地址
container.put("class", targetClassClass.toGenericString());
// 方法名称
container.put("methodName", method.getName());
// 包名称
container.put("package", targetClassClass.getPackage());
// 参数列表
for (int i = 0; i < params.length; i++) {
container.put(String.valueOf(i), params[i]);
}
// 转为JSON字符串
String jsonString = JSON.toJSONString(container);
// 做SHA256 Hash计算,得到一个SHA256摘要作为Key
return DigestUtil.sha256Hex(jsonString);
};
}
/**
* Redis的异常处理,当Redis发生异常时,打印日志
*/
@Bean
@Override
@SuppressWarnings(value = {"all"})
public CacheErrorHandler errorHandler() {
log.info("初始化 -> [{}]", "Redis CacheErrorHandler");
return new CacheErrorHandler() {
@Override
public void handleCacheGetError(RuntimeException e, Cache cache, Object key) {
log.error("Redis occur handleCacheGetError:key -> [{}]", key, e);
}
@Override
public void handleCachePutError(RuntimeException e, Cache cache, Object key, Object value) {
log.error("Redis occur handleCachePutError:key -> [{}];value -> [{}]", key, value, e);
}
@Override
public void handleCacheEvictError(RuntimeException e, Cache cache, Object key) {
log.error("Redis occur handleCacheEvictError:key -> [{}]", key, e);
}
@Override
public void handleCacheClearError(RuntimeException e, Cache cache) {
log.error("Redis occur handleCacheClearError:", e);
}
};
}
}
/**
* Value 序列化
*
* @param <T>
*/
class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T> {
public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
private final Class<T> clazz;
public FastJson2JsonRedisSerializer(Class<T> clazz) {
super();
this.clazz = clazz;
}
@Override
public byte[] serialize(T t) throws SerializationException {
if (t == null) {
return new byte[0];
}
return JSON.toJSONString(t, JSONWriter.Feature.WriteClassName).getBytes(DEFAULT_CHARSET);
}
@Override
public T deserialize(byte[] bytes) throws SerializationException {
if (bytes == null || bytes.length == 0) {
return null;
}
String str = new String(bytes, DEFAULT_CHARSET);
return JSON.parseObject(str, clazz, JSONReader.Feature.SupportAutoType);
}
}
/**
* 重写序列化器
*/
class StringRedisSerializer implements RedisSerializer<Object> {
private final Charset charset;
StringRedisSerializer() {
this(StandardCharsets.UTF_8);
}
private StringRedisSerializer(Charset charset) {
Assert.notNull(charset, "Charset must not be null!");
this.charset = charset;
}
@Override
public String deserialize(byte[] bytes) {
return (bytes == null ? null : new String(bytes, charset));
}
@Override
public @Nullable byte[] serialize(Object object) {
String string = JSON.toJSONString(object);
if (StringUtils.isBlank(string)) {
return null;
}
string = string.replace("\"", "");
return string.getBytes(charset);
}
}
\ No newline at end of file
package com.zq.im.config;
import com.zq.im.interceptor.ApiMethodInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.annotation.Resource;
/**
* <p>
* 注册拦截器
* </p>
*
* @author chenhao
* @since 2023/11/9
*/
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Lazy
@Resource
private ApiMethodInterceptor apiMethodInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(apiMethodInterceptor).addPathPatterns("/api/v1/**");
}
}
package com.zq.im.config.feign;
import feign.RequestInterceptor;
import feign.codec.Encoder;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.cloud.openfeign.support.SpringEncoder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;
/**
* @author wilmiam
* @since 2021-07-09 10:34
*/
@Configuration
public class FeignConfig {
/**
* 转发请求头
*/
private static final List<String> FORWARD_HEADERS = Arrays.asList(
"AUTHORIZATION",
"X-FORWARDED-FOR",
"X-FORWARDED-PROTO",
"X-FORWARDED-PORT",
"X-FORWARDED-HOST",
"FORWARDED",
"PROXY-CLIENT-IP",
"WL-PROXY-CLIENT-IP",
"HTTP_X_FORWARDED_FOR",
"HTTP_X_FORWARDED",
"HTTP_X_CLUSTER_CLIENT_IP",
"HTTP_CLIENT_IP",
"HTTP_FORWARDED_FOR",
"HTTP_FORWARDED",
"HTTP_VIA",
"REMOTE_ADDR",
"X-REAL-IP",
"HOST"
);
private final ObjectFactory<HttpMessageConverters> messageConverters;
public FeignConfig(ObjectFactory<HttpMessageConverters> messageConverters) {
this.messageConverters = messageConverters;
}
public static HttpServletRequest getRequest() {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (requestAttributes == null) {
return null;
}
return requestAttributes.getRequest();
}
/**
* 解决fein远程调用丢失请求头
*/
@Bean
public RequestInterceptor requestInterceptor() {
return template -> {
HttpServletRequest request = getRequest();
if (request == null) {
return;
}
Enumeration<String> headerNames = request.getHeaderNames();
if (headerNames != null) {
while (headerNames.hasMoreElements()) {
String name = headerNames.nextElement();
// 不要设置content-length
if ("content-length".equals(name)) {
continue;
}
if (FORWARD_HEADERS.contains(name.toUpperCase())) {
String values = request.getHeader(name);
template.header(name, values);
}
}
}
};
}
@Bean
public Encoder feignEncoder() {
return new FeignSpringFormEncoder(new SpringEncoder(messageConverters));
}
}
package com.zq.im.config.feign;
import feign.RequestTemplate;
import feign.codec.EncodeException;
import feign.codec.Encoder;
import feign.form.ContentType;
import feign.form.FormEncoder;
import feign.form.MultipartFormContentProcessor;
import feign.form.spring.SpringManyMultipartFilesWriter;
import feign.form.spring.SpringSingleMultipartFileWriter;
import org.springframework.web.multipart.MultipartFile;
import java.lang.reflect.Type;
import java.util.Collections;
import java.util.Map;
/**
* feign 多文件上传配置
*
* @author Lander
*/
public class FeignSpringFormEncoder extends FormEncoder {
public FeignSpringFormEncoder() {
this(new Default());
}
public FeignSpringFormEncoder(Encoder delegate) {
super(delegate);
MultipartFormContentProcessor processor = (MultipartFormContentProcessor) this.getContentProcessor(ContentType.MULTIPART);
processor.addFirstWriter(new SpringSingleMultipartFileWriter());
processor.addFirstWriter(new SpringManyMultipartFilesWriter());
}
@Override
public void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException {
if (bodyType != null && bodyType.equals(MultipartFile[].class)) {
MultipartFile[] file = (MultipartFile[]) object;
if (file != null) {
Map<String, Object> data = Collections.singletonMap(file.length == 0 ? "" : file[0].getName(), object);
super.encode(data, MAP_STRING_WILDCARD, template);
return;
}
} else if (bodyType != null && bodyType.equals(MultipartFile.class)) {
MultipartFile file = (MultipartFile) object;
if (file != null) {
Map<String, Object> data = Collections.singletonMap(file.getName(), object);
super.encode(data, MAP_STRING_WILDCARD, template);
return;
}
}
super.encode(object, bodyType, template);
}
}
package com.zq.im.config.mybatis;
import com.baomidou.mybatisplus.core.injector.AbstractMethod;
import com.baomidou.mybatisplus.core.injector.DefaultSqlInjector;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.extension.injector.methods.InsertBatchSomeColumn;
import java.util.List;
/**
* <p>
* 批量插入SQL注入器
* </P>
*
* @author chenhao
* @since 2022/10/12 20:57
*/
public class InsertBatchSqlInjector extends DefaultSqlInjector {
@Override
public List<AbstractMethod> getMethodList(Class<?> mapperClass, TableInfo tableInfo) {
// 获取MybatisPlus的自带方法
List<AbstractMethod> methodList = super.getMethodList(mapperClass, tableInfo);
// 添加自定义批量插入方法,名称为insertBatchSomeColumn
methodList.add(new InsertBatchSomeColumn());
return methodList;
}
}
\ No newline at end of file
package com.zq.im.config.mybatis;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
/**
* <p>
* Mybatis-Plus配置类
* </p>
*
* @author chenhao
* @since 2022/10/12 20:57
*/
@Configuration
@EnableTransactionManagement
@MapperScan("com.zq.im.modules.system.dao")
public class MybatisPlusConfig {
/**
* 插件配置
* 1. 防全表更新与删除插件
* 2. 分页插件
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 防全表更新与删除插件
interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
// 分页插件,指定数据库为MYSQL
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
/**
* 自定义批量插入 SQL 注入器
*/
@Bean
public InsertBatchSqlInjector insertBatchSqlInjector() {
return new InsertBatchSqlInjector();
}
}
package com.zq.im.constant;
/**
* API响应码
*
* @author wilmiam
* @since 2021-07-14 11:37
*/
public enum ApiCodeEnum {
/**
* 成功
*/
SUCCESS("200", "成功"),
/**
* 未知错误
*/
UNKNOWN_ERROR("100", "未知错误"),
/**
* 版本号错误
*/
VERSION_ERROR("101", "版本号错误"),
/**
* 调用方法不存在
*/
METHOD_ERROR("102", "调用方法不存在"),
/**
* 调用方法异常
*/
METHOD_HANDLER_ERROR("103", "调用方法异常"),
/**
* 传递参数异常
*/
PARAM_ERROR("104", "传递参数异常"),
/**
* IP黑名单拦截
*/
IP_BLACK("105", "IP黑名单拦截"),
/**
* API服务维护中
*/
SERVER_MAINTAIN("106", "API服务维护中"),
/**
* 签名校验失败
*/
CHECK_SIGN_VALID_ERROR("107", "签名校验失败"),
/**
* 服务不可用
*/
SERVICE_NOT_AVAILABLE("108", "服务不可用"),
/**
* 业务处理失败
*/
BUSINESS_ERROR("400", "业务处理失败"),
/**
* 登陆验证失败
*/
LOGIN_VALID_ERROR("401", "登陆验证失败"),
/**
* 服务器繁忙
*/
SERVER_ERROR("500", "服务器繁忙"),
;
private final String code;
private final String msg;
ApiCodeEnum(String code, String msg) {
this.code = code;
this.msg = msg;
}
public String code() {
return code;
}
public String msg() {
return msg;
}
}
package com.zq.im.constant;
/**
* API版本
*
* @author wilmiam
* @since 2021-07-14 11:37
*/
public enum ApiVersionEnum {
/**
* V1
*/
v1,
/**
* V2
*/
v2
}
package com.zq.im.constant;
/**
* <p>
* Token相关常量类
* </p>
*
* @author chenhao
*/
public class TokenConstant {
/**
* Token的密钥
*/
public static final String TOKEN_SECRET = "HWTJ";
/**
* Token在请求头部的名称
*/
public static final String TOKEN_HEADER = "Authorization";
/**
* Token的前缀
*/
public static final String TOKEN_PREFIX = "ApiToken.";
/**
* header的头部加密算法声明
*/
public static final String TOKEN_ALG = "HMAC512";
/**
* header的头部Token类型
*/
public static final String TOKEN_TYP = "JWT";
/**
* Token的签发者
*/
public static final String TOKEN_ISSUER = "HWTJ";
/**
* Token面向的主体
*/
public static final String TOKEN_SUBJECT = "api";
/**
* Token的接收方
*/
public static final String TOKEN_AUDIENCE = "api";
/**
* Token解析后当前用户的信息
*/
public static final String ADMIN_TOKEN = "current_token";
}
package com.zq.im.exception;
/**
* 业务错误
*
* @author chenhao
* @since 2022/10/12 20:57
*/
public class BusinessException extends RuntimeException {
private int code = 400;
public BusinessException(String message) {
super(message);
}
public BusinessException(int code, String message) {
super(message);
this.code = code;
}
public BusinessException(int code, String message, Throwable cause) {
super(message, cause);
this.code = code;
}
public int getCode() {
return code;
}
}
\ No newline at end of file
package com.zq.im.interceptor;
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.http.HttpStatus;
import cn.hutool.json.JSONUtil;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.zq.im.constant.ApiCodeEnum;
import com.zq.im.modules.api.form.ApiForm;
import com.zq.im.modules.api.service.ApiManage;
import com.zq.im.modules.api.utils.ApiUtils;
import com.zq.im.utils.IpUtil;
import com.zq.im.utils.RedisUtil;
import com.zq.im.utils.TokenUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 方法拦截器
* 主要是校验Token信息,校验成功线后程保存Token信息
*
* @author chenhao
* @since 2023-11-09
*/
@Slf4j
@Component
public class ApiMethodInterceptor implements HandlerInterceptor {
@Autowired
private RedisUtil redisUtil;
/**
* 当前环境
*/
@Value("${spring.profiles.active:product}")
private String active;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String ip = IpUtil.getIpAddr(request);
ApiForm form = ServletUtil.toBean(request, ApiForm.class, true);
log.info("from [{}] to [{}]", ip, request.getRequestURI() + "#" + form.getMethod());
// 判断是否是可以在未登录状态下执行的方法名
boolean notValid = ApiManage.isNotValid(form.getMethod());
if (notValid) {
return true;
}
// 验证认证信息
String apiToken = redisUtil.getStr(TokenUtil.getApiTokenKey(form.getAppId()));
if (apiToken == null) {
response.setStatus(HttpStatus.HTTP_BAD_REQUEST);
ServletUtil.write(response, JSONUtil.toJsonStr(ApiUtils.of(form, ApiCodeEnum.LOGIN_VALID_ERROR)), "application/json;charset=utf-8");
return false;
}
DecodedJWT decodedJWT;
try {
decodedJWT = TokenUtil.parse(apiToken);
} catch (Exception e) {
response.setStatus(HttpStatus.HTTP_BAD_REQUEST);
ServletUtil.write(response, JSONUtil.toJsonStr(ApiUtils.of(form, ApiCodeEnum.LOGIN_VALID_ERROR)), "application/json;charset=utf-8");
return false;
}
TokenUtil.setUserContext(decodedJWT);
return true;
}
}
package com.zq.im.modules.api.controller;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.extra.servlet.ServletUtil;
import com.zq.im.constant.ApiCodeEnum;
import com.zq.im.constant.ApiVersionEnum;
import com.zq.im.exception.BusinessException;
import com.zq.im.modules.api.form.ApiForm;
import com.zq.im.modules.api.form.ApiResp;
import com.zq.im.modules.api.service.ApiManage;
import com.zq.im.modules.api.utils.ApiUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
/**
* API方法总入口
*
* @author chenhao
* @since 2023/11/9
*/
@Slf4j
@RequiredArgsConstructor
@RestController
@RequestMapping("/api/v1")
public class ApiV1 {
private final ApiManage apiManage;
@PostMapping(value = "/action")
public ResponseEntity<ApiResp> action(ApiForm form, HttpServletResponse response) {
// 设置版本号
form.setVersion(ApiVersionEnum.v1.name());
//解析业务参数
if (!form.parseBizContent()) {
ApiResp apiResp = ApiUtils.of(form, ApiCodeEnum.PARAM_ERROR);
return ResponseEntity.badRequest().body(apiResp);
}
// 调用接口方法
ApiResp resp;
try {
// 调用接口方法
resp = apiManage.action(form);
} catch (BusinessException e) {
// 捕抓业务发生的异常
log.error("v1调用方法业务发生错误", e);
resp = ApiUtils.fail(form, e.getMessage());
} catch (Exception e) {
// 转化指定异常为来自或者包含指定异常
BusinessException businessException = ExceptionUtil.convertFromOrSuppressedThrowable(e, BusinessException.class, true);
if (businessException == null) {
// 不是业务异常才打印错误日志
log.error("v1调用方法发生错误", e);
}
if (businessException != null) {
resp = ApiUtils.fail(form, businessException.getMessage());
} else {
resp = ApiUtils.of(form, ApiCodeEnum.METHOD_HANDLER_ERROR);
}
}
// 没有数据输出空
if (resp == null) {
resp = ApiUtils.of(form, ApiCodeEnum.UNKNOWN_ERROR);
}
// 如果接口返回数据为输入流,则返回输入
Object data = resp.getData();
if (data instanceof InputStream) {
InputStream inputStream = (InputStream) data;
ServletUtil.write(response, inputStream);
return null;
}
return resp.isSuccess() ? ResponseEntity.ok(resp) : ResponseEntity.badRequest().body(resp);
}
}
package com.zq.im.modules.api.form;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.zq.im.exception.BusinessException;
import com.zq.im.modules.api.utils.ApiUtils;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import java.util.TreeMap;
/**
* api 基础form
*
* @author wilmiam
* @since 2021-07-13 09:59
*/
@Slf4j
@Data
public class ApiForm {
private String appId;
private String method;// 请求方法
private String sign;// 签名
private String timestamp;// 时间戳, 单位: 毫秒
private String version;// 接口版本
private String apiNo;// 接口码
private String bizContent;// 请求业务参数
private JSONObject bizContentJson;// 请求业务的json对象
private MultipartFile file; // 上传文件用
private MultipartFile[] fileList; // 上传文件用
/**
* 解析业务参数,将业务参数转为JSON字符串
*/
public boolean parseBizContent() {
try {
// 业务参数不为空,便给参数进行BASE64解码
if (StrUtil.isNotBlank(bizContent)) {
bizContent = ApiUtils.decode(bizContent, "BASE64");
}
// 参数为空就设置为空
if (bizContent == null) {
bizContent = "";
}
// 参数转换为字符串
bizContentJson = JSON.parseObject(bizContent);
if (bizContentJson == null) {
bizContentJson = new JSONObject();
}
return true;
} catch (Exception e) {
log.error("bizContent解析失败:{}", e.getMessage());
return false;
}
}
public <T> T toBean(Class<T> beanClass) {
JSONObject contentJson = getContentJson();
return contentJson.toJavaObject(beanClass);
}
/**
* 获取参数
*/
public String getString(String key) {
String value = getContentJson().getString(key);
return value == null ? "" : value;
}
/**
* 获取业务参数的JSON字符串
*/
public JSONObject getContentJson() {
if (bizContentJson != null) {
return bizContentJson;
}
parseBizContent();
return bizContentJson;
}
/**
* 加签,其实就是拼接加上Key
*/
public String getSignStr(String key) {
// 参数进行排序
TreeMap<String, String> treeMap = getSignTreeMap();
// 参数拼接
StringBuilder src = new StringBuilder();
for (Map.Entry<String, String> entry : treeMap.entrySet()) {
src.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
}
src.append("key=").append(key);
return src.toString();
}
/**
* 获取签名所需参数,并进行字典序排序
*/
public TreeMap<String, String> getSignTreeMap() {
TreeMap<String, String> treeMap = new TreeMap<>();
treeMap.put("apiNo", this.apiNo);
treeMap.put("timestamp", this.timestamp);
treeMap.put("method", this.method);
String bizContent = StrUtil.isBlank(this.bizContent) ? "" : this.bizContent;
treeMap.put("bizContent", bizContent);
// 如果有文件传入,并设置文件的MD5
if (file != null) {
InputStream inputStream;
try {
inputStream = file.getInputStream();
} catch (IOException e) {
log.error("获取文件流发生错误", e);
throw new BusinessException("获取文件流发生错误:" + e.getMessage());
}
treeMap.put("fileMd5", SecureUtil.md5(inputStream));
}
// 如果有文件列表,拼接所有文件的MD5
if (ArrayUtil.isNotEmpty(fileList)) {
StringBuilder md5 = new StringBuilder();
for (MultipartFile multipartFile : fileList) {
InputStream inputStream;
try {
inputStream = multipartFile.getInputStream();
} catch (IOException e) {
log.error("获取文件流发生错误", e);
throw new BusinessException("获取文件流发生错误:" + e.getMessage());
}
md5.append(SecureUtil.md5(inputStream));
}
treeMap.put("fileMd5", md5.toString());
}
return treeMap;
}
}
package com.zq.im.modules.api.form;
import com.zq.im.constant.ApiCodeEnum;
import lombok.Getter;
/**
* @author wilmiam
* @since 2022-11-04 11:29
*/
@Getter
public class ApiResp {
private String apiNo = "";
private String code = ApiCodeEnum.SUCCESS.code();
private String msg = ApiCodeEnum.SUCCESS.msg();
private Long timestamp = System.currentTimeMillis();
private Object data;
public ApiResp(ApiForm form) {
this.apiNo = form.getApiNo() == null ? "" : form.getApiNo();
}
public ApiResp(ApiCodeEnum apiCodeEnum) {
this.code = apiCodeEnum.code();
this.msg = apiCodeEnum.msg();
}
public ApiResp(ApiForm form, ApiCodeEnum apiCodeEnum) {
this.code = apiCodeEnum.code();
this.msg = apiCodeEnum.msg();
this.apiNo = form.getApiNo();
}
public ApiResp setApiNo(String apiNo) {
this.apiNo = apiNo;
return this;
}
public ApiResp setCode(String code) {
this.code = code;
return this;
}
public ApiResp setMsg(String msg) {
this.msg = msg;
return this;
}
public ApiResp setTimestamp(Long timestamp) {
this.timestamp = timestamp;
return this;
}
public ApiResp setData(Object data) {
this.data = data;
return this;
}
public Boolean isSuccess() {
return this.getCode().equals(ApiCodeEnum.SUCCESS.code());
}
}
package com.zq.im.modules.api.service;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.zq.im.constant.ApiCodeEnum;
import com.zq.im.modules.api.form.ApiForm;
import com.zq.im.modules.api.form.ApiResp;
import com.zq.im.modules.api.utils.ApiUtils;
import com.zq.im.modules.api.utils.ReflectionUtils;
import com.zq.im.utils.TokenUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* @author wilmiam
* @since 2022/10/28 16:02
*/
@Slf4j
@Service
public class ApiManage {
/**
* 允许用户未登录状态下执行的方法名
*/
private static final List<String> ALLOW_METHOD = Arrays.asList("getApiToken");
private static final List<String> METHOD_LIST;
static {
METHOD_LIST = methodList();
}
public static List<String> methodList() {
List<String> methodList = new ArrayList<>();
Method[] methods = IApiLogic.class.getMethods();
for (Method method : methods) {
Class<?>[] params = method.getParameterTypes();
if (params.length == 1 && (params[0] == ApiForm.class)) {
methodList.add(method.getName());
}
}
return methodList;
}
public static boolean isNotValid(String method) {
return ALLOW_METHOD.contains(method);
}
public ApiResp action(ApiForm form) throws Exception {
// 如果接口方法不存在,则返回异常
if (!METHOD_LIST.contains(form.getMethod())) {
return ApiUtils.of(form, ApiCodeEnum.METHOD_ERROR);
}
// 校验签名,校验失败直接返回
ApiResp authResp = signValid(form);
if (!authResp.isSuccess()) {
return authResp;
}
IApiLogic apiLogic = ApiUtils.getApiLogic(form);
// 调用接口方法,利用反射更简洁
Object result = ReflectionUtils.invokeMethod(apiLogic, form.getMethod(), new Class<?>[]{ApiForm.class}, new Object[]{form});
return (ApiResp) result;
}
/**
* 签名验证
*/
public ApiResp signValid(ApiForm form) {
// 白名单存在该方法,直接放行
boolean contains = ALLOW_METHOD.contains(form.getMethod());
if (contains) {
return ApiUtils.success(form);
}
// 获取Token中的sessionKey
DecodedJWT apiToken = TokenUtil.getUserContext();
String sessionKey = TokenUtil.getSessionkey(apiToken);
// 通过sessionKey签名,并进行校验
String sign = ApiUtils.getSign(form.getSignStr(sessionKey == null ? "" : sessionKey));
if (!sign.equals(form.getSign())) {
return ApiUtils.of(form, ApiCodeEnum.CHECK_SIGN_VALID_ERROR);
}
return ApiUtils.success(form);
}
}
package com.zq.im.modules.api.service;
import com.zq.im.modules.api.form.ApiForm;
import com.zq.im.modules.api.form.ApiResp;
/**
* API通用接口
*
* @author wilmiam
* @since 2022-11-04 11:35
*/
public interface IApiCommon {
ApiResp signValid(ApiForm form);
}
package com.zq.im.modules.api.service;
import com.zq.im.modules.api.form.ApiForm;
import com.zq.im.modules.api.form.ApiResp;
/**
* API接口类
* 这里的注解没有用,只是为了注释说明方法
*
* @author wilmiam
* @since 2022-11-01 09:53
*/
public interface IApiLogic extends IApiCommon {
/**
* 获取第三方调用token
*/
ApiResp getApiToken(ApiForm form);
/**
* 图片质量检测
*/
ApiResp detection(ApiForm form);
//
// /**
// * 图片纠偏
// *
// * @param form
// * @return
// */
// ApiResp deskew(ApiForm form);
//
// /**
// * 去黑边
// *
// * @param form
// * @return
// */
// ApiResp removeBlack(ApiForm form);
//
// /**
// * 图片灰度化
// *
// * @param form
// * @return
// */
// ApiResp gray(ApiForm form);
//
// /**
// * 图片边缘检测
// *
// * @param form
// * @return
// */
// ApiResp canny(ApiForm form);
//
// /**
// * 图片弯曲矫正
// *
// * @param form
// * @return
// */
// ApiResp correct(ApiForm form);
//
// /**
// * 图片优化
// *
// * @param form
// * @return
// */
// ApiResp imageOptimization(ApiForm form);
}
package com.zq.im.modules.api.service.impl;
import com.zq.im.aspect.OpenApi;
import com.zq.im.modules.api.form.ApiForm;
import com.zq.im.modules.api.form.ApiResp;
import com.zq.im.modules.api.service.IApiLogic;
import com.zq.im.modules.api.utils.ApiUtils;
import com.zq.im.modules.im.req.ImageReq;
import com.zq.im.modules.im.service.ImgProcService;
import com.zq.im.modules.system.service.ApiUserInfoService;
import com.zq.im.utils.AssertUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* API实现类
*
* @author wilmiam
* @since 2022-10-31 17:16
*/
@Slf4j
@Component
public class ApiV1Logic extends BaseApiLogic implements IApiLogic {
@Autowired
ApiUserInfoService apiUserInfoService;
@Autowired
private ImgProcService imgProcService;
@Override
@OpenApi("获取Token")
public ApiResp getApiToken(ApiForm form) {
String appId = form.getString("appId");
String appSecret = form.getString("appSecret");
AssertUtils.hasText(appId, "缺少参数appId");
AssertUtils.hasText(appSecret, "缺少参数secret");
return ApiUtils.toApiResp(form, apiUserInfoService.getApiToken(appId, appSecret));
}
@Override
@OpenApi("检测图片信息")
public ApiResp detection(ApiForm form) {
ImageReq req = form.toBean(ImageReq.class);
AssertUtils.hasText(req.getFileContent(), "缺少文件内容");
AssertUtils.hasText(req.getFileName(), "缺少文件名");
return ApiUtils.toApiResp(form, imgProcService.detection(req));
}
//
// @ApiMethod(name = "图片纠偏", filterRequestField = "fileContent")
// @Override
// public ApiResp deskew(ApiForm form) {
// DetectionVo vo = form.toBean(DetectionVo.class);
// AssertUtils.hasText(vo.getFileContent(), "缺少文件内容");
// AssertUtils.hasText(vo.getFilename(), "缺少文件名");
// return ApiUtils.toApiResp(form, imgProcService.apiService(vo, "imageCorrection"));
// }
//
// @ApiMethod(name = "去黑边", filterRequestField = "fileContent")
// @Override
// public ApiResp removeBlack(ApiForm form) {
// DetectionVo vo = form.toBean(DetectionVo.class);
// AssertUtils.hasText(vo.getFileContent(), "缺少文件内容");
// AssertUtils.hasText(vo.getFilename(), "缺少文件名");
// return ApiUtils.toApiResp(form, imgProcService.apiService(vo, "removeBlack"));
// }
//
// @ApiMethod(name = "图片灰度化", filterRequestField = "fileContent")
// @Override
// public ApiResp gray(ApiForm form) {
// DetectionVo vo = form.toBean(DetectionVo.class);
// AssertUtils.hasText(vo.getFileContent(), "缺少文件内容");
// AssertUtils.hasText(vo.getFilename(), "缺少文件名");
// return ApiUtils.toApiResp(form, imgProcService.apiService(vo, "gray"));
// }
//
// @ApiMethod(name = "图片边缘检测", filterRequestField = "fileContent")
// @Override
// public ApiResp canny(ApiForm form) {
// DetectionVo vo = form.toBean(DetectionVo.class);
// AssertUtils.hasText(vo.getFileContent(), "缺少文件内容");
// AssertUtils.hasText(vo.getFilename(), "缺少文件名");
// return ApiUtils.toApiResp(form, imgProcService.apiService(vo, "gray"));
// }
//
// @ApiMethod(name = "图片弯曲矫正", filterRequestField = "fileContent")
// @Override
// public ApiResp correct(ApiForm form) {
// DetectionVo vo = form.toBean(DetectionVo.class);
// AssertUtils.hasText(vo.getFileContent(), "缺少文件内容");
// AssertUtils.hasText(vo.getFilename(), "缺少文件名");
// return ApiUtils.toApiResp(form, imgProcService.apiService(vo, "correct"));
// }
//
// @ApiMethod(name = "图片优化", filterRequestField = "fileContent")
// @Override
// public ApiResp imageOptimization(ApiForm form) {
// DetectionVo vo = form.toBean(DetectionVo.class);
// AssertUtils.hasText(vo.getFileContent(), "缺少文件内容");
// AssertUtils.hasText(vo.getFilename(), "缺少文件名");
// return ApiUtils.toApiResp(form, imgProcService.imageOptimization(vo, "imageOptimization"));
// }
}
package com.zq.im.modules.api.service.impl;
import com.zq.im.modules.api.service.IApiLogic;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* API实现类
*
* @author wilmiam
* @since 2022-10-31 17:16
*/
@Slf4j
@Component
public class ApiV2Logic extends ApiV1Logic implements IApiLogic {
}
package com.zq.im.modules.api.service.impl;
import com.zq.im.modules.api.form.ApiForm;
import com.zq.im.modules.api.form.ApiResp;
import com.zq.im.modules.api.service.IApiLogic;
import com.zq.im.modules.api.utils.ApiUtils;
/**
* API基础类
* <p>
* 2016年11月15日 下午9:48:27
*
* @author wilmiam
*/
public abstract class BaseApiLogic implements IApiLogic {
@Override
public ApiResp signValid(ApiForm form) {
return ApiUtils.success(form);
}
}
package com.zq.im.modules.api.utils;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import com.zq.im.constant.ApiCodeEnum;
import com.zq.im.modules.api.form.ApiForm;
import com.zq.im.modules.api.form.ApiResp;
import com.zq.im.modules.api.service.IApiLogic;
import com.zq.im.modules.api.service.impl.ApiV1Logic;
import com.zq.im.modules.api.service.impl.ApiV2Logic;
import com.zq.im.modules.system.vo.ResultVo;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;
/**
* @author wilmiam
* @since 2021-07-22 10:18
*/
@Component
public class ApiUtils {
private static final Map<String, IApiLogic> MAP = new HashMap<>();
public ApiUtils(ApiV1Logic apiV1Logic, ApiV2Logic apiV2Logic) {
addApi("v1", apiV1Logic);
addApi("v2", apiV2Logic);
}
public static void addApi(String version, IApiLogic apiLogic) {
MAP.put(version, apiLogic);
}
public static IApiLogic getApiLogic(ApiForm form) {
return MAP.get(form.getVersion());
}
/**
* 获取成功响应
*/
public static ApiResp success(ApiForm form) {
return new ApiResp(form, ApiCodeEnum.SUCCESS);
}
/**
* 获取失败的响应
*/
public static ApiResp fail(ApiForm form, String errMsg) {
ApiResp apiResp = new ApiResp(form, ApiCodeEnum.BUSINESS_ERROR);
apiResp.setMsg(errMsg);
return apiResp;
}
public static ApiResp of(ApiForm form, ApiCodeEnum apiCodeEnum) {
return new ApiResp(form, apiCodeEnum);
}
public static ApiResp toApiResp(ApiForm form, ResultVo resultVo) {
ApiResp apiResp = new ApiResp(form);
if (resultVo.isSuccess()) {
apiResp.setData(resultVo.getData() == null ? "" : resultVo.getData());
} else {
return apiResp.setCode(String.valueOf(resultVo.getErrCode())).setMsg(resultVo.getErrMsg());
}
return apiResp;
}
public static ApiResp toApiResp(ApiForm form, Object data) {
ApiResp apiResp = new ApiResp(form);
apiResp.setData(data);
return apiResp;
}
/**
* 解码
*/
public static String decode(String params, String encryptType) {
if (StrUtil.isBlank(params)) {
return "";
}
// params = EncryptUtils.urlDecode(params, "UTF-8");
if ("RSA".equals(encryptType)) {
params = EncryptUtils.rsaDecodeByPrivateKey(params, RsaUtils.privateKey);
} else {
params = Base64.decodeStr(params);
}
return params;
}
/**
* 编码
* <p>
* 2017年3月15日 下午1:49:09
*
* @param params
* @return
*/
public static String encode(String params, String encryptType) {
if (StrUtil.isBlank(params)) {
return "";
}
if ("RSA".equals(encryptType)) {
params = EncryptUtils.rsaDecodeByPrivateKey(params, RsaUtils.publicKey);
} else {
params = EncryptUtils.base64Decode(params);
}
params = EncryptUtils.urlEncode(params, "UTF-8");
return params;
}
/**
* 获取验证sign
* <p>
* 2017年3月15日 下午3:14:27
*
* @param content
* @return
*/
public static String getSign(String content) {
return SecureUtil.md5(content);
}
}
package com.zq.im.modules.api.utils;
import cn.hutool.core.util.StrUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.server.ServerErrorException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
/**
* 加密工具类
*
* @author wilmiam
* @since 2021-07-09 17:55
*/
public class EncryptUtils {
private static final Logger log = LoggerFactory.getLogger(EncryptUtils.class);
/**
* 字符编码
*/
private static final String CHARSET_NAME = "UTF-8";
/**
* 用来将字节转换成 16 进制表示的字符
*/
private static final char[] HEX_DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
private static final int HEX_RADIUS = 16;
/**
* MD5 加密字符串
*/
public static String md5Encrypt(final String sourceStr) {
return md5Encrypt(sourceStr, CHARSET_NAME);
}
/**
* MD5加密字符串
*/
public static String md5Encrypt(final String sourceStr, String coding) {
if (StrUtil.isBlank(sourceStr)) {
return null;
}
byte[] sourceByte;
try {
sourceByte = sourceStr.getBytes(coding);
} catch (UnsupportedEncodingException e) {
log.error(e.getMessage(), e);
sourceByte = sourceStr.getBytes();
}
MessageDigest md;
try {
md = MessageDigest.getInstance("MD5");
md.update(sourceByte);
// MD5 的计算结果是一个 128 位的长整数,用字节表示就是 16 个字节
final byte[] tmp = md.digest();
// 每个字节用 16 进制表示的话,使用两个字符,所以表示成 16 进制需要 32 个字符
// 16 << 1 相当于 16*2
final char[] str = new char[16 << 1];
// 表示转换结果中对应的字符位置
int k = 0;
// 从第一个字节开始,对 MD5 的每一个字节转换成 16 进制字符的转换
for (int i = 0; i < HEX_RADIUS; i++) {
// 取第 i 个字节
final byte byte0 = tmp[i];
// 取字节中高 4 位的数字转换, >>> 为逻辑右移,将符号位一起右移
str[k++] = HEX_DIGITS[byte0 >>> 4 & 0xf];
// 取字节中低 4 位的数字转换
str[k++] = HEX_DIGITS[byte0 & 0xf];
}
// 换后的结果转换为字符串
return new String(str);
} catch (final NoSuchAlgorithmException e) {
log.error(">> MD5加密错误", e);
}
return null;
}
/**
* 私钥解密(注意:encodedData是base64加密后的才行)
*/
public static String rsaDecodeByPrivateKey(String encodedData, String privateKey) {
try {
byte[] decodedData = RsaUtils.decryptByPrivateKey(Base64.getDecoder().decode(encodedData), privateKey);
return new String(decodedData, CHARSET_NAME);
} catch (Exception e) {
log.error("rsaDecodeByPrivateKey error", e);
return null;
}
}
/**
* 使用BASE64进行解密
*
* @param base64Str base64字符串
* @return 解密字符串
*/
public static String base64Decode(final String base64Str) {
byte[] decode = Base64.getDecoder().decode(base64Str);
try {
return new String(decode, CHARSET_NAME);
} catch (UnsupportedEncodingException e) {
log.error("base64 decode error", e);
throw new ServerErrorException("编码失败");
}
}
/**
* 对字符串进行URL编码
*
* @param sourceStr 需要编码的字符串
* @param enc 编码格式
* @return
*/
public static String urlEncode(String sourceStr, String enc) {
try {
return URLEncoder.encode(sourceStr, enc);
} catch (UnsupportedEncodingException e) {
log.error("urlEncode error", e);
return null;
}
}
}
/**
* Copyright 2015-2025 .
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.zq.im.modules.api.utils;
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* 方法类
*
* @author syh
*/
@Slf4j
public class ReflectionUtils {
/**
* 循环向上转型, 获取对象的 DeclaredMethod
*
* @param object : 子类对象
* @param methodName : 父类中的方法名
* @param parameterTypes : 父类中的方法参数类型
* @return 父类中的方法对象
*/
private static Method getDeclaredMethod(Object object, String methodName, Class<?>... parameterTypes) {
Method method;
Class<?> clazz = object.getClass();
while (clazz != Object.class) {
try {
method = clazz.getDeclaredMethod(methodName, parameterTypes);
return method;
} catch (Exception e) {
// 未获取到就往上找
clazz = clazz.getSuperclass();
}
}
return null;
}
/**
* 直接调用对象方法, 而忽略修饰符(private, protected, default)
*
* @param object : 子类对象
* @param methodName : 父类中的方法名
* @param parameterTypes : 父类中的方法参数类型
* @param parameters : 父类中的方法参数
* @return 父类中方法的执行结果
*/
public static Object invokeMethod(Object object, String methodName, Class<?>[] parameterTypes, Object[] parameters) throws InvocationTargetException, IllegalAccessException {
// 根据 对象、方法名和对应的方法参数 通过反射 调用上面的方法获取 Method 对象
Method method = getDeclaredMethod(object, methodName, parameterTypes);
if (method == null) {
log.warn(object.getClass() + "未获取到" + methodName + "方法!");
return null;
}
// 抑制Java对方法进行检查,主要是针对私有方法而言
method.setAccessible(true);
// 调用object 的 method 所代表的方法,其方法的参数是 parameters
return method.invoke(object, parameters);
}
/**
* 循环向上转型, 获取对象的 DeclaredField
*
* @param object : 子类对象
* @param fieldName : 父类中的属性名
* @return 父类中的属性对象
*/
private static Field getDeclaredField(Object object, String fieldName) {
Field field;
Class<?> clazz = object.getClass();
while (clazz != Object.class) {
try {
field = clazz.getDeclaredField(fieldName);
return field;
} catch (Exception e) {
// 未获取到就往上找
clazz = clazz.getSuperclass();
}
}
return null;
}
/**
* 直接设置对象属性值, 忽略 private/protected 修饰符, 也不经过 setter
*
* @param object : 子类对象
* @param fieldName : 父类中的属性名
* @param value : 将要设置的值
*/
public static void setFieldValue(Object object, String fieldName, Object value) {
// 根据 对象和属性名通过反射 调用上面的方法获取 Field对象
Field field = getDeclaredField(object, fieldName);
if (field == null) {
System.err.println(object.getClass() + "未获取到" + fieldName + "属性!");
return;
}
// 抑制Java对其的检查
field.setAccessible(true);
try {
// 将 object 中 field 所代表的值 设置为 value
field.set(object, value);
} catch (IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
}
}
/**
* 直接读取对象的属性值, 忽略 private/protected 修饰符, 也不经过 getter
*
* @param object : 子类对象
* @param fieldName : 父类中的属性名
* @return : 父类中的属性值
*/
public static Object getFieldValue(Object object, String fieldName) {
// 根据 对象和属性名通过反射 调用上面的方法获取 Field对象
Field field = getDeclaredField(object, fieldName);
if (field == null) {
System.err.println(object.getClass() + "未获取到" + fieldName + "属性!");
return null;
}
// 抑制Java对其的检查
field.setAccessible(true);
try {
// 获取 object 中 field 所代表的属性值
return field.get(object);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
package com.zq.im.modules.api.utils;
import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
/**
* @author wilmiam
* @since 2021-07-09 17:56
*/
public class RsaUtils {
/**
* 获取公钥的key
*/
public static final String PUBLIC_KEY = "RSAPublicKey";
/**
* 获取私钥的key
*/
public static final String PRIVATE_KEY = "RSAPrivateKey";
/**
* 加密算法RSA
*/
private static final String KEY_ALGORITHM = "RSA";
/**
* 签名算法
*/
private static final String SIGNATURE_ALGORITHM = "MD5withRSA";
/**
* RSA最大加密明文大小
*/
private static final int MAX_ENCRYPT_BLOCK = 117;
/**
* RSA最大解密密文大小
*/
private static final int MAX_DECRYPT_BLOCK = 128;
public static String privateKey = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAMTaoTuj4LU9WAMeaVWcpwgyMcdvAA3JRDcG0+pWG086c+WPdSggNNZaVw3szCKOTnWvNc6SoqjpjpbQpC57uag67VzKWLmsZoF6SXjCARyRaEkfK2VRHTfkVpyd8FF16gebVhhyjbkkja9JVEekwqOGzfmnfSKfx5LwvcSxdiSrAgMBAAECgYBZgGHQQPk4zhRHDrurnhbfhhrV5yTqH7kxH5yYLeAqzJPHKsuEm+gKEXcFMMW7bGJF5YycSFVGYTJgZapQLBbDlrZdM8SjxsNyrCKI3v3LNQDsqs5x751HfFVvTme7wroN/uJszUaQJPagEUckMkHvpv7XWoL3Wbz7oy94T3ENoQJBAPAhj2yo9jRZv5JRlYy5BFwqYpxSWqGjzr2k2YiGqB9/y/pDpDx3q42FaBcOlOOeh/My+iVNLcezqgj+U0yx79ECQQDR3Oz9ckCm2q7AMCLFmp9cs4dws6DLim35awOvLIXtm/Z1tRNyuLqb6g2VM4O/QiTu64F3+ljKiOWHAcgxqUe7AkEArTuYy4vs6gFhCb6fg8Cp24+cSifDSF7zM67sW+jA+tBoJ+iKYDD46wS1/gQ/9yGT9Cfve998ylfbr9dB4s9vMQJAOH/uHd3gogtF+N/8vI6AUQjUcfcqVyIRsZCqEUM/W1Ud6VqyvbQWKVu+BGk2EwvPvbMRzCdOOFja0pocN6KHeQJAQPwlDo1IHJI5F60CvfIG8dIwtGexMnd4NNHQ4KH0peK9jUCPkkpW0No5ZEtKNgfdPk23erfyx5cGqocvnoUpoQ==";
public static String publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDE2qE7o+C1PVgDHmlVnKcIMjHHbwANyUQ3BtPqVhtPOnPlj3UoIDTWWlcN7Mwijk51rzXOkqKo6Y6W0KQue7moOu1cyli5rGaBekl4wgEckWhJHytlUR035FacnfBRdeoHm1YYco25JI2vSVRHpMKjhs35p30in8eS8L3EsXYkqwIDAQAB";
/**
* 生成密钥对(公钥和私钥)
*
* @return
* @throws Exception
*/
public static Map<String, Key> genKeyPair() throws Exception {
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM);
keyPairGen.initialize(1024);
KeyPair keyPair = keyPairGen.generateKeyPair();
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
Map<String, Key> keyMap = new HashMap<>(4);
keyMap.put(PUBLIC_KEY, publicKey);
keyMap.put(PRIVATE_KEY, privateKey);
return keyMap;
}
/**
* 用私钥对信息生成数字签名
*
* @param data 已加密数据
* @param privateKey 私钥(BASE64编码)
* @return
* @throws Exception
*/
public static String sign(byte[] data, String privateKey) throws Exception {
byte[] keyBytes = Base64.getDecoder().decode(privateKey);
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
PrivateKey privateK = keyFactory.generatePrivate(pkcs8KeySpec);
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
signature.initSign(privateK);
signature.update(data);
return Base64.getEncoder().encodeToString(signature.sign());
}
/**
* 校验数字签名
*
* @param data 已加密数据
* @param publicKey 公钥(BASE64编码)
* @param sign 数字签名
* @return
* @throws Exception
*/
public static boolean verify(byte[] data, String publicKey, String sign)
throws Exception {
byte[] keyBytes = Base64.getDecoder().decode(publicKey);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
PublicKey publicK = keyFactory.generatePublic(keySpec);
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
signature.initVerify(publicK);
signature.update(data);
return signature.verify(Base64.getDecoder().decode(sign));
}
/**
* <p>
* 公钥解密
* </p>
*
* @param encryptedData 已加密数据
* @param publicKey 公钥(BASE64编码)
* @return
* @throws Exception
*/
public static byte[] decryptByPublicKey(byte[] encryptedData, String publicKey) throws Exception {
return byteTransfer(getCipherByRsaPublicKey(Cipher.DECRYPT_MODE, publicKey), encryptedData, MAX_ENCRYPT_BLOCK);
}
/**
* <p>
* 公钥加密
* </p>
*
* @param data 源数据
* @param publicKey 公钥(BASE64编码)
* @return
* @throws Exception
*/
public static byte[] encryptByPublicKey(byte[] data, String publicKey) throws Exception {
return byteTransfer(getCipherByRsaPublicKey(Cipher.ENCRYPT_MODE, publicKey), data, MAX_ENCRYPT_BLOCK);
}
private static Cipher getCipherByRsaPublicKey(int mode, String publicKey) throws GeneralSecurityException {
byte[] keyBytes = Base64.getDecoder().decode(publicKey);
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
Key publicK = keyFactory.generatePublic(x509KeySpec);
// 对数据加密
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.ENCRYPT_MODE, publicK);
return cipher;
}
/**
* <P>
* 私钥解密
* </p>
*
* @param encryptedData 已加密数据
* @param privateKey 私钥(BASE64编码)
* @return
* @throws Exception
*/
public static byte[] decryptByPrivateKey(byte[] encryptedData, String privateKey) throws Exception {
return byteTransfer(getCipherByRsaPrivateKey(Cipher.DECRYPT_MODE, privateKey), encryptedData, MAX_DECRYPT_BLOCK);
}
/**
* 私钥加密
*
* @param data 源数据
* @param privateKey 私钥(BASE64编码)
* @return
* @throws Exception
*/
public static byte[] encryptByPrivateKey(byte[] data, String privateKey) throws Exception {
return byteTransfer(getCipherByRsaPrivateKey(Cipher.ENCRYPT_MODE, privateKey), data, MAX_ENCRYPT_BLOCK);
}
private static Cipher getCipherByRsaPrivateKey(int mode, String key) throws GeneralSecurityException {
byte[] keyBytes = Base64.getDecoder().decode(key);
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
Key privateK = keyFactory.generatePrivate(pkcs8KeySpec);
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(mode, privateK);
return cipher;
}
/**
* <p>
* 获取私钥
* </p>
*
* @param keyMap 密钥对
* @return
*/
public static String getPrivateKey(Map<String, Key> keyMap) {
Key key = keyMap.get(PRIVATE_KEY);
return Base64.getEncoder().encodeToString(key.getEncoded());
}
/**
* 获取公钥
*
* @param keyMap 密钥对
* @return
*/
public static String getPublicKey(Map<String, Key> keyMap) {
Key key = keyMap.get(PUBLIC_KEY);
return Base64.getEncoder().encodeToString(key.getEncoded());
}
/**
* 根据cipher的模式, 对数据进行分段加密/解密
*
* @param cipher
* @param data
* @return
* @throws GeneralSecurityException,IOException
*/
private static byte[] byteTransfer(Cipher cipher, byte[] data, int maxBlock) throws GeneralSecurityException, IOException {
int inputLen = data.length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offSet = 0;
byte[] cache;
int i = 0;
// 根据cipher的模式, 对数据分段加密/解密
while (inputLen - offSet > 0) {
if (inputLen - offSet > maxBlock) {
cache = cipher.doFinal(data, offSet, maxBlock);
} else {
cache = cipher.doFinal(data, offSet, inputLen - offSet);
}
out.write(cache, 0, cache.length);
i++;
offSet = i * maxBlock;
}
byte[] encryptedData = out.toByteArray();
out.close();
return encryptedData;
}
}
package com.zq.im.modules.api.vo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Objects;
/**
* @author wilmiam
* @since 2022/1/27 12:30
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class MethodVo {
private String name;
private String service;
private String value;
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
MethodVo methodVo = (MethodVo) o;
return Objects.equals(value, methodVo.value);
}
@Override
public int hashCode() {
return Objects.hash(value);
}
}
package com.zq.im.modules.im.controller;
import com.zq.im.modules.im.req.ImageReq;
import com.zq.im.modules.im.service.ImgProcService;
import com.zq.im.modules.system.vo.ResultVo;
import com.zq.im.utils.AssertUtils;
import io.swagger.annotations.Api;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
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;
/**
* <p>
* 图片处理相关接口
* </p>
*
* @author chenhao
* @since 2022/10/27 17:55
*/
@Slf4j
@RestController
@RequestMapping("/interface/api/imgproc")
@RequiredArgsConstructor
@Api(tags = "图片处理相关接口")
public class ImgProcController {
private final ImgProcService service;
@PostMapping(value = "/detection")
public ResultVo<?> detection(@RequestBody ImageReq req) {
AssertUtils.hasText(req.getFileContent(), "缺少文件内容");
AssertUtils.hasText(req.getFileName(), "缺少文件名");
return ResultVo.success(service.detection(req));
}
}
package com.zq.im.modules.im.feign;
import com.zq.im.modules.im.req.ImageReq;
import com.zq.im.modules.im.vo.DetectionResVo;
import com.zq.im.modules.system.vo.ResultVo;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
/**
* @author chenhao
* @since 2022/4/24 10:26
*/
@FeignClient(name = "IMGPROC-SERVER", path = "/imgproc/v1")
public interface ImgprocFeign {
/**
* 图片质量检测接口
*/
@PostMapping(value = "/detection")
ResultVo<DetectionResVo> detection(ImageReq req);
}
package com.zq.im.modules.im.req;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* <p>
* 图片请求类
* </p>
*
* @author chenhao
* @since 2023/11/9
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ImageReq {
/**
* 图片名称
*/
String fileName;
/**
* 图片的Base64字符串
*/
String fileContent;
}
package com.zq.im.modules.im.service;
import com.zq.im.modules.im.feign.ImgprocFeign;
import com.zq.im.modules.im.req.ImageReq;
import com.zq.im.modules.im.vo.DetectionResVo;
import com.zq.im.modules.system.vo.ResultVo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* <p>
* 图片处理Service
* </p>
*
* @author chenhao
* @since 2022/10/27 17:56
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class ImgProcService {
@Autowired
ImgprocFeign feign;
public ResultVo<?> detection(ImageReq req) {
ResultVo<DetectionResVo> resultVo = feign.detection(req);
if (!resultVo.isSuccess()) {
return ResultVo.fail(resultVo.getErrMsg());
}
return resultVo;
}
}
package com.zq.im.modules.im.vo;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* <p>
*
* </p>
*
* @author yww
* @since 2023/4/21
*/
@NoArgsConstructor
@AllArgsConstructor
@Data
@Builder
public class DetectionResVo {
@ApiModelProperty("文件名称")
private String fileName;
@ApiModelProperty("图片大小")
private Double size;
@ApiModelProperty("水平分辨率")
private Integer widthResolution;
@ApiModelProperty("垂直分辨率")
private Integer heightResolution;
@ApiModelProperty("DPI")
private Integer dpi;
@ApiModelProperty("图片清晰度")
private Double clarity;
@ApiModelProperty("图片弯曲置信度")
private Double bend;
@ApiModelProperty("图片亮度值")
private Integer brightness;
@ApiModelProperty("图片偏离角度")
private Integer deskewAngel;
@ApiModelProperty("图片黑边数量")
private Integer black;
}
package com.zq.im.modules.system.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.zq.im.modules.system.entity.ApiUserInfo;
import org.springframework.stereotype.Repository;
/**
* 第三方接口调用用户信息(ApiUserInfo)表数据库访问层
*
* @author makejava
* @since 2021-12-09 17:20:44
*/
@Repository
public interface ApiUserInfoDao extends BaseMapper<ApiUserInfo> {
}
package com.zq.im.modules.system.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.zq.im.modules.system.entity.OpenApiLog;
import org.springframework.stereotype.Repository;
/**
* 开放接口日志Dao层
*
* @author chenhao
*/
@Repository
public interface OpenApiLogDao extends BaseMapper<OpenApiLog> {
}
package com.zq.im.modules.system.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
/**
* 第三方接口调用用户信息(ApiUserInfo)实体类
*
* @author makejava
* @since 2021-12-09 17:20:44
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@TableName(value = "api_user_info")
public class ApiUserInfo {
/**
* 主键
*/
@TableId(type = IdType.ASSIGN_UUID)
private String id;
/**
* APP_ID
*/
private String appId;
/**
* APP_SECRET
*/
private String appSecret;
/**
* 申请人
*/
private String applyer;
/**
* 状态
*/
private Integer state;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新时间
*/
private Date updateTime;
}
package com.zq.im.modules.system.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
*
* 开放接口日志实体类
*
* @author chenhao
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@TableName(value = "open_api_log")
public class OpenApiLog {
/**
* ID
*/
@TableId(type = IdType.AUTO)
@ApiModelProperty("ID")
private String id;
/**
* APPID
*/
private String appId;
/**
* 申请人名称
*/
private String applyer;
/**
* 调用方法
*/
private String method;
/**
* IP
*/
private String serverIp;
/**
* 版本号
*/
private String version;
/**
* 接口描述
*/
private String description;
/**
* 报错描述
*/
private String errMsg;
/**
* 日志级别
*/
private String logType;
/**
* IP
*/
private String clientIp;
/**
* 耗时-毫秒
*/
private Long timeCost;
/**
* 创建时间
*/
private String createTime;
}
package com.zq.im.modules.system.service;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.zq.im.modules.system.dao.ApiUserInfoDao;
import com.zq.im.modules.system.entity.ApiUserInfo;
import com.zq.im.utils.AssertUtils;
import com.zq.im.utils.RedisUtil;
import com.zq.im.utils.TokenUtil;
import com.zq.im.utils.UuidUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* @author chenhao
*/
@Slf4j
@Service
public class ApiUserInfoService {
@Autowired
ApiUserInfoDao apiUserInfoDao;
@Resource
RedisUtil redisUtil;
public static void main(String[] args) {
// 创建用户
ApiUserInfo apiUserInfo = new ApiUserInfo();
apiUserInfo.setId(UuidUtils.uuidNoDash());
apiUserInfo.setAppId(UuidUtils.uuidNoDash());
apiUserInfo.setAppSecret(UuidUtils.uuidNoDash());
apiUserInfo.setApplyer("测试用户");
apiUserInfo.setState(1);
System.out.println(DateUtil.now());
System.out.println(JSONUtil.toJsonStr(apiUserInfo));
}
public Map<String, Object> getApiToken(String appId, String appSecret) {
ApiUserInfo apiAppInfo = apiUserInfoDao.selectOne(
Wrappers.lambdaQuery(ApiUserInfo.builder().appId(appId).appSecret(appSecret).build())
);
AssertUtils.notNull(apiAppInfo, "账号或密码错误");
AssertUtils.isTrue(apiAppInfo.getState() == 1, "账号未激活");
String sessionKey = IdUtil.simpleUUID();
String apiToken = TokenUtil.createToken(apiAppInfo.getAppId(), sessionKey, apiAppInfo.getApplyer());
log.debug(">> [session-key]:{}", sessionKey);
redisUtil.setStr(TokenUtil.getApiTokenKey(apiAppInfo.getAppId()), apiToken, 120L);
Map<String, Object> data = new HashMap<>();
data.put("userId", apiAppInfo.getId());
data.put("username", apiAppInfo.getApplyer());
data.put("key", sessionKey);
data.put("expireTime", TimeUnit.MINUTES.toSeconds(TokenUtil.API_TOKEN_EXPIRE_MINUTES));
data.put("unit", "秒");
return data;
}
}
package com.zq.im.modules.system.vo;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.io.Serializable;
/**
* 统一的接口响应信息
*
* @author wilmiam
* @since 2021-07-09 17:45
*/
@ApiModel("API响应消息")
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
public class ResultVo<T> implements Serializable {
@ApiModelProperty(value = "成功标记", example = "true")
private boolean success;
@ApiModelProperty("错误码")
@JsonInclude(JsonInclude.Include.NON_DEFAULT)
private int errCode;
@ApiModelProperty("错误信息")
private String errMsg;
@ApiModelProperty("响应的数据")
private T data;
public static ResultVo success() {
return success(null);
}
public static <E> ResultVo<E> success(E data) {
ResultVo<E> result = new ResultVo<>();
result.setSuccess(true);
result.setData(data);
return result;
}
public static ResultVo fail(String errMsg) {
return fail(500, errMsg);
}
public static ResultVo fail(int errCode, String errMsg) {
ResultVo result = new ResultVo<>();
result.setSuccess(false);
result.setErrCode(errCode);
result.setErrMsg(errMsg);
return result;
}
public boolean isSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
public int getErrCode() {
return errCode;
}
public void setErrCode(int errCode) {
this.errCode = errCode;
}
public String getErrMsg() {
return errMsg;
}
public void setErrMsg(String errMsg) {
this.errMsg = errMsg;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
package com.zq.im.utils;
import cn.hutool.core.util.StrUtil;
import com.zq.im.exception.BusinessException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
/**
* 断言验证帮助类
*
* @author wilmiam
* @since 2018-04-03
*/
@SuppressWarnings("all")
public final class AssertUtils {
private static final String[] IMG_EXTS = {"png", "jpg", "jpeg"};
/**
* Don't let anyone instantiate this class
*/
private AssertUtils() {
}
/**
* 判断给定的文件名后缀是否为图片
*
* @param ext 文件名后缀, 不带点
* @param errMsg 错误信息
*/
public static void isImgExt(String ext, String errMsg) {
isImgExt(ext, 400, errMsg);
}
/**
* 判断给定的文件名后缀是否为图片
*
* @param ext 文件名后缀, 不带点
* @param errCode 断言失败的错误代码
* @param errMsg 错误信息
*/
public static void isImgExt(String ext, int errCode, String errMsg) {
if (StrUtil.isBlank(ext) || Arrays.stream(IMG_EXTS).noneMatch(img -> img.equalsIgnoreCase(ext))) {
throw new BusinessException(errCode, errMsg);
}
}
/**
* UnifiedExceptionHandler
* 判断一个布尔表达式, 若表达式为{@code true}则抛出指定错误信息的{@code BusinessException}.
*
* @param expression 布尔表达式
* @param message 断言失败时的错误信息
* @throws BusinessException
*/
public static void notTrue(boolean expression, String message) throws BusinessException {
notTrue(expression, 400, message);
}
/**
* 判断一个布尔表达式, 若表达式为{@code true}则抛出指定错误信息的{@code BusinessException}.
*
* @param expression 布尔表达式
* @param errCode 断言失败时的错误代码
* @param message 断言失败时的错误信息
* @throws BusinessException
*/
public static void notTrue(boolean expression, int errCode, String message) throws BusinessException {
if (expression) {
throw new BusinessException(errCode, message);
}
}
/**
* 判断一个布尔表达式, 若表达式为{@code false}则抛出指定错误信息的{@code BusinessException}.
*
* @param expression 布尔表达式
* @param message 断言失败时的错误信息
* @throws BusinessException
*/
public static void isTrue(boolean expression, String message) throws BusinessException {
isTrue(expression, 400, message);
}
/**
* 判断一个布尔表达式, 若表达式为{@code false}则抛出指定错误信息的{@code BusinessException}.
*
* @param expression 布尔表达式
* @param errCode 断言失败时的错误代码
* @param message 断言失败时的错误信息
* @throws BusinessException
*/
public static void isTrue(boolean expression, int errCode, String message) throws BusinessException {
if (!expression) {
throw new BusinessException(errCode, message);
}
}
/**
* 如果对象为{@code null}, 则抛出异常
*
* @param object 要判断的对象
* @throws BusinessException
*/
public static void notNull(Object object) throws BusinessException {
notNull(object, "不能处理空对象");
}
/**
* 如果对象为{@code null}, 则抛出异常
*
* @param object 要判断的对象
* @param message 断言失败时的错误信息
* @throws BusinessException
*/
public static void notNull(Object object, String message) throws BusinessException {
notNull(object, 400, message);
}
/**
* 如果对象为{@code null}, 则抛出异常
*
* @param object 要判断的对象
* @param errCode 断言失败时的错误代码
* @param errMsg 断言失败时的错误信息
* @throws BusinessException
*/
public static void notNull(Object object, int errCode, String errMsg) throws BusinessException {
if (object == null) {
throw new BusinessException(errCode, errMsg);
}
}
/**
* 如果字符串为{@code null}、空字符串或仅包含空白字符, 则抛出异常
*
* @param text 要进行检查的字符串
* @throws BusinessException
*/
public static void hasText(String text) throws BusinessException {
hasText(text, 400, "参数不能为空字符串");
}
/**
* 如果字符串为{@code null}、空字符串或仅包含空白字符, 则抛出异常
*
* @param text 要进行检查的字符串
* @param message 断言失败时的错误信息
* @throws BusinessException
*/
public static void hasText(String text, String message) throws BusinessException {
hasText(text, 400, message);
}
/**
* 如果字符串为{@code null}、空字符串或仅包含空白字符, 则抛出异常
*
* @param text 要进行检查的字符串
* @param errCode 断言失败时的错误代码
* @param errMsg 错误信息
* @throws BusinessException
*/
public static void hasText(String text, int errCode, String errMsg) throws BusinessException {
if (StrUtil.isBlank(text)) {
throw new BusinessException(errCode, errMsg);
}
}
/**
* 如果数组为{@code null}或长度为0, 则抛出异常
*
* @param array 要进行检查的数组
* @param message 断言失败时的错误信息
* @param <T> 数组的数据类型
* @throws BusinessException
*/
public static <T> void notEmpty(T[] array, String message) throws BusinessException {
notEmpty(array, 400, message);
}
/**
* 如果数组为{@code null}或长度为0, 则抛出异常
*
* @param array 要进行检查的数组
* @param errCode 断言失败时的错误代码
* @param errMsg 错误信息
* @param <T> 数组的数据类型
* @throws BusinessException
*/
public static <T> void notEmpty(T[] array, int errCode, String errMsg) throws BusinessException {
if (array == null || array.length == 0) {
throw new BusinessException(errCode, errMsg);
}
}
/**
* 如果数组里包含有{@code null}的元素, 则抛出异常. 注意: 若数组本身为{@code null}则不会进行处理, 直接返回
*
* @param array 要进行检查的数组
* @param message 断言失败时的错误信息
* @param <T> 数组的数据类型
* @throws BusinessException
*/
public static <T> void noNullElements(T[] array, String message) throws BusinessException {
noNullElements(array, 400, message);
}
/**
* 如果数组里包含有{@code null}的元素, 则抛出异常. 注意: 若数组本身为{@code null}则不会进行处理, 直接返回
*
* @param array 要进行检查的数组
* @param errCode 断言失败时的错误代码
* @param errMsg 错误信息
* @param <T> 数组的数据类型
* @throws BusinessException
*/
public static <T> void noNullElements(T[] array, int errCode, String errMsg) throws BusinessException {
if (array != null) {
for (T element : array) {
if (element == null) {
throw new BusinessException(errCode, errMsg);
}
}
}
}
/**
* 如果集合为{@code null},或者不包含任何元素,则抛出异常
*
* @param collection 要进行检查的集合
* @param message 断言失败时的错误信息
* @throws BusinessException
*/
public static void notEmpty(Collection<?> collection, String message) throws BusinessException {
notEmpty(collection, 400, message);
}
/**
* 如果集合为{@code null},或者不包含任何元素,则抛出异常
*
* @param collection 要进行检查的集合
* @param errCode 断言失败时的错误代码
* @param errMsg 错误信息
* @throws BusinessException
*/
public static void notEmpty(Collection<?> collection, int errCode, String errMsg) throws BusinessException {
if (collection == null || collection.isEmpty()) {
throw new BusinessException(errCode, errMsg);
}
}
/**
* 如果键值对为{@code null},或者不包含任何键值,则抛出异常
*
* @param map 要进行检查的键值对
* @param message 断言失败时的错误信息
* @throws BusinessException
*/
public static void notEmpty(Map<?, ?> map, String message) throws BusinessException {
notEmpty(map, 400, message);
}
/**
* 如果键值对为{@code null},或者不包含任何键值,则抛出异常
*
* @param map 要进行检查的键值对
* @param errCode 断言失败时的错误代码
* @param errMsg 错误信息
* @throws BusinessException
*/
public static void notEmpty(Map<?, ?> map, int errCode, String errMsg) throws BusinessException {
if (map == null || map.isEmpty()) {
throw new BusinessException(errCode, errMsg);
}
}
}
package com.zq.im.utils;
import cn.hutool.core.util.CharUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* <p>
* IP相关工具类
* </p>
*
* @author chenhao
* @since 2022/10/12 20:57
*/
public class IpUtil {
public static long getLongIpAddr(HttpServletRequest request) {
String ip = getIpAddr(request);
return ipv4ToLong(ip);
}
/**
* 获取客户端IP
*
* @return IP
*/
public static String getIpAddr() {
return getIpAddr(getRequest());
}
/**
* 获取客户端IP
*
* @param request 请求
* @return IP
*/
public static String getIpAddr(HttpServletRequest request) {
if (request == null) {
return "unknown";
}
// 判断x-forwarded-for是否有IP
String ip = request.getHeader("x-forwarded-for");
if (isNotUnknown(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
// 判断Proxy-Client-IP是否有IP
if (isNotUnknown(ip)) {
ip = request.getHeader("X-Forwarded-For");
}
// 判断X-Forwarded-For是否有IP
if (isNotUnknown(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
// 判断WL-Proxy-Client-IP是否有IP
if (isNotUnknown(ip)) {
ip = request.getHeader("X-Real-IP");
}
// 判断X-Real-IP是否有IP
if (isNotUnknown(ip)) {
ip = request.getRemoteAddr();
}
// 若是IP最后为0:0:0:0:0:0:0:1,就是本地回环地址,否则第一个非unknown的IP地址就是真实地址
return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : getMultistageReverseProxyIp(ip);
}
/**
* 从多级反向代理中获得第一个非unknown IP地址
*
* @param ip 获得的IP地址
* @return 第一个非unknown IP地址
*/
@SuppressWarnings("all")
private static String getMultistageReverseProxyIp(String ip) {
// 多级反向代理检测
if (ip != null && ip.indexOf(",") > 0) {
final String[] ips = ip.trim().split(",");
for (String subIp : ips) {
if (isNotUnknown(subIp)) {
ip = subIp;
break;
}
}
}
return ip;
}
/**
* 检测给定字符串是否为未知,多用于检测HTTP请求相关
*
* @param checkString 被检测的字符串
* @return 是否未知
*/
private static boolean isNotUnknown(String checkString) {
return StrUtil.isNotBlank(checkString) && StrUtil.isNotEmpty(checkString) && !"unknown".equalsIgnoreCase(checkString);
}
/**
* 获取UserAgent信息对象
*
* @param request 请求
* @return UserAgent
*/
public static UserAgent getBrowser(HttpServletRequest request) {
return UserAgentUtil.parse(request.getHeader("User-Agent"));
}
/**
* 获取当前请求
*
* @return 当前请求
*/
private static HttpServletRequest getRequest() {
return ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
}
/**
* 将ipv4地址转换为long类型
* 等同于MySQL的inet_aton方法
*
* @param ipStr ipv4地址
* @return long类型的ipv地址
*/
public static long ipv4ToLong(String ipStr) {
String regex = "^(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)$";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(ipStr);
if (matcher.matches()) {
long addr = 0;
for (int i = 1; i <= 4; i++) {
addr |= Long.parseLong(matcher.group(i)) << 8 * (4 - i);
}
return addr;
} else {
throw new RuntimeException("ipv4地址格式出错!");
}
}
/**
* 将long类型的ip转为ipv4字符串
* 等同于MySQL的inet_ntoa方法
*
* @param longIp long类型IP
* @return IPV4字符串
*/
public static String longToIpv4(long longIp) {
StringBuilder sb = new StringBuilder();
// 直接右移24位
sb.append(longIp >> 24 & 0xFF);
sb.append(CharUtil.DOT);
// 将高8位置0,然后右移16位
sb.append(longIp >> 16 & 0xFF);
sb.append(CharUtil.DOT);
sb.append(longIp >> 8 & 0xFF);
sb.append(CharUtil.DOT);
sb.append(longIp & 0xFF);
return sb.toString();
}
}
package com.zq.im.utils;
import com.alibaba.fastjson2.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
* <p>
* Redis的工具类
* </p>
*
* @ClassName RedisUtil
* @Author yww
* @Date 2022/10/15 10:40
*/
@Slf4j
@Component
@SuppressWarnings("all")
public class RedisUtil {
private final StringRedisTemplate stringRedisTemplate;
private final RedisTemplate<String, Object> redisTemplate;
@Autowired
public RedisUtil(StringRedisTemplate stringRedisTemplate, RedisTemplate<String, Object> redisTemplate) {
this.stringRedisTemplate = stringRedisTemplate;
this.redisTemplate = redisTemplate;
}
/**
* 普通缓存
*
* @param key 键
*/
public void setStr(String key, String value) {
stringRedisTemplate.opsForValue().set(key, value);
}
/**
* 普通缓存
*
* @param key 键
*/
public void setStr(String key, String value, long time) {
stringRedisTemplate.opsForValue().set(key, value, time, TimeUnit.MINUTES);
}
/**
* 普通缓存
*
* @param key 键
*/
public void setStr(String key, String value, long time, TimeUnit timeUnit) {
stringRedisTemplate.opsForValue().set(key, value, time, timeUnit);
}
/**
* 普通缓存获取
*
* @param key 键
* @return 值
*/
public String getStr(String key) {
return key == null ? null : stringRedisTemplate.opsForValue().get(key);
}
/**
* 普通缓存获取
*
* @param key 键
* @return 值
*/
public <T> T getStr(String key, Class<T> clazz) {
return key == null ? null : JSON.parseObject(stringRedisTemplate.opsForValue().get(key), clazz);
}
/**
* 普通缓存删除
*
* @param key 键
*/
public void deleteStr(String key) {
stringRedisTemplate.delete(key);
}
/**
* Object缓存
*
* @param key 键
*/
public void setObj(String key, Object value) {
redisTemplate.opsForValue().set(key, value);
}
/**
* Object缓存
*
* @param key 键
*/
public void setObj(String key, Object value, long time) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.MINUTES);
}
/**
* Object缓存
*
* @param key 键
*/
public void setObj(String key, Object value, long time, TimeUnit timeUnit) {
redisTemplate.opsForValue().set(key, value, time, timeUnit);
}
/**
* Object缓存获取
*
* @param key 键
* @return 值
*/
public Object getObj(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
/**
* Object缓存获取
*
* @param key 键
* @return 值
*/
public <T> T getObj(String key, Class<T> clazz) {
return key == null ? null : (T) redisTemplate.opsForValue().get(key);
}
/**
* Object缓存删除
*
* @param key 键
*/
public void deleteObj(String key) {
redisTemplate.delete(key);
}
/**
* 查找匹配key
*
* @param pattern key
* @return /
*/
public List<String> scan(String pattern) {
ScanOptions options = ScanOptions.scanOptions().match(pattern).build();
RedisConnectionFactory factory = redisTemplate.getConnectionFactory();
RedisConnection rc = Objects.requireNonNull(factory).getConnection();
Cursor<byte[]> cursor = rc.scan(options);
List<String> result = new ArrayList<>();
while (cursor.hasNext()) {
result.add(new String(cursor.next()));
}
try {
RedisConnectionUtils.releaseConnection(rc, factory);
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
/**
* 指定缓存失效时间
*
* @param key 键
* @param time 时间(秒)
*/
public boolean expire(String key, long time) {
try {
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
/**
* 指定缓存失效时间
*
* @param key 键
* @param time 时间(秒)
* @param timeUnit 单位
*/
public boolean expire(String key, long time, TimeUnit timeUnit) {
try {
if (time > 0) {
redisTemplate.expire(key, time, timeUnit);
}
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
/**
* 根据 key 获取过期时间
*
* @param key 键 不能为null
* @return 时间(秒) 返回0代表为永久有效
*/
public long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
/**
* 分页查询 key
*
* @param patternKey key
* @param page 页码
* @param size 每页数目
* @return /
*/
public List<String> findKeysForPage(String patternKey, int page, int size) {
ScanOptions options = ScanOptions.scanOptions().match(patternKey).build();
RedisConnectionFactory factory = redisTemplate.getConnectionFactory();
RedisConnection rc = Objects.requireNonNull(factory).getConnection();
Cursor<byte[]> cursor = rc.scan(options);
List<String> result = new ArrayList<>(size);
int tmpIndex = 0;
int fromIndex = page * size;
int toIndex = page * size + size;
while (cursor.hasNext()) {
if (tmpIndex >= fromIndex && tmpIndex < toIndex) {
result.add(new String(cursor.next()));
tmpIndex++;
continue;
}
// 获取到满足条件的数据后,就可以退出了
if (tmpIndex >= toIndex) {
break;
}
tmpIndex++;
cursor.next();
}
try {
RedisConnectionUtils.releaseConnection(rc, factory);
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
/**
* 判断key是否存在
*
* @param key 键
* @return true 存在 false不存在
*/
public boolean hasKey(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除缓存
*
* @param keys 可以传一个值 或多个
*/
public void del(String... keys) {
if (keys != null && keys.length > 0) {
if (keys.length == 1) {
boolean result = redisTemplate.delete(keys[0]);
log.debug("--------------------------------------------");
log.debug("删除缓存:{},结果:{}", keys[0], result);
log.debug("--------------------------------------------");
} else {
Set<String> keySet = new HashSet<>();
for (String key : keys) {
keySet.addAll(redisTemplate.keys(key));
}
long count = redisTemplate.delete(keySet);
log.debug("--------------------------------------------");
log.debug("成功删除缓存:{}", keySet);
log.debug("缓存删除数量:{}个", count);
log.debug("--------------------------------------------");
}
}
}
// ============================String=============================
/**
* 普通缓存获取
*
* @param key 键
* @return 值
*/
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
/**
* 批量获取
*/
public List<Object> multiGet(List<String> keys) {
return redisTemplate.opsForValue().multiGet(keys);
}
/**
* 普通缓存放入
*
* @param key 键
* @param value 值
* @return true成功 false失败
*/
public boolean set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 普通缓存放入并设置时间
*
* @param key 键
* @param value 值
* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
* @return true成功 false 失败
*/
public boolean set(String key, Object value, long time) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 普通缓存放入并设置时间
*
* @param key 键
* @param value 值
* @param time 时间
* @param timeUnit 类型
* @return true成功 false 失败
*/
public boolean set(String key, Object value, long time, TimeUnit timeUnit) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, timeUnit);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
// ================================Map=================================
/**
* HashGet
*
* @param key 键 不能为null
* @param item 项 不能为null
* @return 值
*/
public Object hget(String key, String item) {
return redisTemplate.opsForHash().get(key, item);
}
/**
* 获取hashKey对应的所有键值
*
* @param key 键
* @return 对应的多个键值
*/
public Map<Object, Object> hmget(String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* HashSet
*
* @param key 键
* @param map 对应多个键值
* @return true 成功 false 失败
*/
public boolean hmset(String key, Map<String, Object> map) {
try {
redisTemplate.opsForHash().putAll(key, map);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* HashSet 并设置时间
*
* @param key 键
* @param map 对应多个键值
* @param time 时间(秒)
* @return true成功 false失败
*/
public boolean hmset(String key, Map<String, Object> map, long time) {
try {
redisTemplate.opsForHash().putAll(key, map);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
* @return true 成功 false失败
*/
public boolean hset(String key, String item, Object value) {
try {
redisTemplate.opsForHash().put(key, item, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
* @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
* @return true 成功 false失败
*/
public boolean hset(String key, String item, Object value, long time) {
try {
redisTemplate.opsForHash().put(key, item, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除hash表中的值
*
* @param key 键 不能为null
* @param item 项 可以使多个 不能为null
*/
public void hdel(String key, Object... item) {
redisTemplate.opsForHash().delete(key, item);
}
/**
* 判断hash表中是否有该项的值
*
* @param key 键 不能为null
* @param item 项 不能为null
* @return true 存在 false不存在
*/
public boolean hHasKey(String key, String item) {
return redisTemplate.opsForHash().hasKey(key, item);
}
/**
* hash递增 如果不存在,就会创建一个 并把新增后的值返回
*
* @param key 键
* @param item 项
* @param by 要增加几(大于0)
*/
public double hincr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, by);
}
/**
* hash递减
*
* @param key 键
* @param item 项
* @param by 要减少记(小于0)
*/
public double hdecr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, -by);
}
// ============================set=============================
/**
* 根据key获取Set中的所有值
*
* @param key 键
*/
public Set<Object> sGet(String key) {
try {
return redisTemplate.opsForSet().members(key);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 根据value从一个set中查询,是否存在
*
* @param key 键
* @param value 值
* @return true 存在 false不存在
*/
public boolean sHasKey(String key, Object value) {
try {
return redisTemplate.opsForSet().isMember(key, value);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将数据放入set缓存
*
* @param key 键
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSet(String key, Object... values) {
try {
return redisTemplate.opsForSet().add(key, values);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 将set数据放入缓存
*
* @param key 键
* @param time 时间(秒)
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSetAndTime(String key, long time, Object... values) {
try {
Long count = redisTemplate.opsForSet().add(key, values);
if (time > 0) {
expire(key, time);
}
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 获取set缓存的长度
*
* @param key 键
*/
public long sGetSetSize(String key) {
try {
return redisTemplate.opsForSet().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 移除值为value的
*
* @param key 键
* @param values 值 可以是多个
* @return 移除的个数
*/
public long setRemove(String key, Object... values) {
try {
Long count = redisTemplate.opsForSet().remove(key, values);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
// ===============================list=================================
/**
* 获取list缓存的内容
*
* @param key 键
* @param start 开始
* @param end 结束 0 到 -1代表所有值
*/
public List<Object> lGet(String key, long start, long end) {
try {
return redisTemplate.opsForList().range(key, start, end);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 获取list缓存的长度
*
* @param key 键
*/
public long lGetListSize(String key) {
try {
return redisTemplate.opsForList().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 通过索引 获取list中的值
*
* @param key 键
* @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
*/
public Object lGetIndex(String key, long index) {
try {
return redisTemplate.opsForList().index(key, index);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
*/
public boolean lSet(String key, Object value) {
try {
redisTemplate.opsForList().rightPush(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @param time 时间(秒)
*/
public boolean lSet(String key, Object value, long time) {
try {
redisTemplate.opsForList().rightPush(key, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
*/
public boolean lSet(String key, List<Object> value) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @param time 时间(秒)
*/
public boolean lSet(String key, List<Object> value, long time) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据索引修改list中的某条数据
*
* @param key 键
* @param index 索引
* @param value 值
* @return /
*/
public boolean lUpdateIndex(String key, long index, Object value) {
try {
redisTemplate.opsForList().set(key, index, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 移除N个值为value
*
* @param key 键
* @param count 移除多少个
* @param value 值
* @return 移除的个数
*/
public long lRemove(String key, long count, Object value) {
try {
return redisTemplate.opsForList().remove(key, count, value);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* @param prefix 前缀
* @param ids id
*/
public void delByKeys(String prefix, Set<Long> ids) {
Set<String> keys = new HashSet<>();
for (Long id : ids) {
keys.addAll(redisTemplate.keys(String.valueOf(new StringBuffer(prefix).append(id))));
}
long count = redisTemplate.delete(keys);
// 此处提示可自行删除
log.debug("--------------------------------------------");
log.debug("成功删除缓存:{}", keys);
log.debug("缓存删除数量:{}个", count);
log.debug("--------------------------------------------");
}
}
package com.zq.im.utils;
import java.util.HashMap;
import java.util.Map;
/**
* <p>
* 基于ThreadLocal的线程相关上下文帮助类, 用于在同一线程下传递变量.
* </p>
*
* @author zq
* @since 2023/11/8
*/
public class ThreadContextUtil {
private static ThreadLocal<Map<String, Object>> threadLocalMap = ThreadLocal.withInitial(HashMap::new);
/**
* 根据指定的key获取当前线程相关的变量值
*
* @param key 变量的key
* @param <T> 变量值的具体类型
* @return 若无此key对应的变量值, 则返回{@code null}
* @throws ClassCastException 若接收此返回值的变量类型与上下文保存的值的实际类型不匹配, 则抛出异常
*/
@SuppressWarnings("unchecked")
public static <T> T get(String key) {
return (T) threadLocalMap.get().get(key);
}
/**
* 根据指定的key获取当前线程相关的变量值, 若为{@code null}则返回指定的默认值
*
* @param key 变量的key
* @param defaultValue 默认值
* @param <T> 变量值的具体类型
* @return 若无此key对应的变量值, 则返回defaultValue
* @throws ClassCastException 若接收此返回值的变量类型与上下文保存的值的实际类型不匹配, 则抛出异常
*/
public static <T> T get(String key, T defaultValue) {
T value = get(key);
return value == null ? defaultValue : value;
}
/**
* 设置线程相关上下文的变量值
*
* @param key 变量的key
* @param value 变量值
*/
public static void set(String key, Object value) {
threadLocalMap.get().put(key, value);
}
/**
* 删除指定key的变量
*
* @param key 变量的key
*/
public static void remove(String key) {
threadLocalMap.get().remove(key);
}
/**
* 清除当前线程相关的上下文
*/
public static void close() {
threadLocalMap.remove();
}
/**
* Don't let anyone instantiate this class
*/
private void ThreadContext() {
}
}
package com.zq.im.utils;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.IdUtil;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.JWTVerifier;
import com.zq.im.constant.TokenConstant;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* <p>
* Token工具类
* 1. Header,记录令牌类型和签名算法
* 2. payload,携带用户信息
* (1) iss(issuer), 签发者
* (2) sub(subject), 面向的主体
* (3) aud(audience), 接收方
* (4) nbf(notBefore), 开始生效生效时间戳
* (5) exp(expiresAt), 过期时间戳
* (6) iat(issuedAt ), 签发时间
* (7) jti(jwtId), 唯一标识
* 3. signature, 签名,防止Token被篡改
* </p>
*
* @author chenhao
* @since 2022/10/15 14:31
*/
public class TokenUtil {
public static final String APPID = "appId";
public static final String SESSIONKEY = "sessionKey";
public static final String APPLYER = "applyer";
/**
* 过期时长
*/
public static final long API_TOKEN_EXPIRE_MINUTES = 120L;
/**
* 线程变量的键值
*/
private static final String APP_TOKEN_CONTEXT_KEY = "app-token";
/**
* 生成Token
* 当前使用HMAC512的加密算法
*
* @return Token
*/
public static String createToken(String appId, String sessionKey, String applyer) {
// 设置Token头部(不设置也会默认有这两个值)
Map<String, Object> header = new HashMap<String, Object>(2) {
private static final long serialVersionUID = 1L;
{
put("alg", TokenConstant.TOKEN_ALG);
put("typ", TokenConstant.TOKEN_TYP);
}
};
// 设置负载
Map<String, Object> payload = new HashMap<String, Object>(1) {
private static final long serialVersionUID = 1L;
{
put(APPID, appId);
put(SESSIONKEY, sessionKey);
put(APPLYER, applyer);
}
};
// 过期时间三小时
long now = DateUtil.current();
long exp = now + 1000 * API_TOKEN_EXPIRE_MINUTES * 60;
return JWT.create()
// 设置header
.withHeader(header)
// 设置payload
.withIssuer(TokenConstant.TOKEN_ISSUER)
.withSubject(TokenConstant.TOKEN_SUBJECT)
.withAudience(TokenConstant.TOKEN_AUDIENCE)
.withNotBefore(new Date(now))
.withExpiresAt(new Date(exp))
.withIssuedAt(new Date(now))
.withJWTId(IdUtil.fastSimpleUUID())
.withPayload(payload)
// 签名
.sign(Algorithm.HMAC512(TokenConstant.TOKEN_SECRET));
}
/**
* 解析Token
* 当前使用HMAC512的加密算法
*
* @param token Token
*/
public static DecodedJWT parse(String token) {
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC512(TokenConstant.TOKEN_SECRET)).build();
return jwtVerifier.verify(token);
}
/**
* 获取APPID
*
* @param decoded 解析后的Token
* @return appId
*/
public static String getAppid(DecodedJWT decoded) {
return decoded.getClaim(APPID).asString();
}
/**
* 获取SessionKey
*
* @param decoded 解析后的Token
* @return sessionKey
*/
public static String getSessionkey(DecodedJWT decoded) {
return decoded.getClaim(SESSIONKEY).asString();
}
/**
* 获取Applyer
*
* @param decoded 解析后的Token
* @return applyer
*/
public static String getApplyer(DecodedJWT decoded) {
return decoded.getClaim(APPLYER).asString();
}
public static String getApiTokenKey(String tokenKey) {
return TokenConstant.TOKEN_PREFIX + tokenKey;
}
public static DecodedJWT getUserContext() {
return ThreadContextUtil.get(APP_TOKEN_CONTEXT_KEY);
}
public static void setUserContext(DecodedJWT decodedJWT) {
if (decodedJWT != null) {
ThreadContextUtil.set(APP_TOKEN_CONTEXT_KEY, decodedJWT);
}
}
public static void main(String[] args) {
// String token = createToken("ec3f54b398e64fbeb9ed00bb0144a91b",
// "4c0d6c2a99e541c8a1affc3c8d8dcddb");
String token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhcGkiLCJhdWQiOiJhcGkiLCJuYmYiOjE2OTk0OTc1MTIsInNlc3Npb25LZXkiOiI5MjhkMmM0YjE2NTY0YTY3OTkwZjY4ZmE2ZmExZmIxNSIsImlzcyI6IkhXVEoiLCJleHAiOjE2OTk1MDQ3MTIsInVzZXJOYW1lIjoiZWMzZjU0YjM5OGU2NGZiZWI5ZWQwMGJiMDE0NGE5MWIiLCJpYXQiOjE2OTk0OTc1MTIsImp0aSI6ImNmYTBhNzNmYmM0NTRlYmE4NzNlNmY3ODA3ZDE1Y2QyIn0.0ZoVFzZWFYmUEwbymR7M75hbt6RMDlpyWlh9NzmXgwATaPZGsGCFSou5RvjQWa2vKFOOdUV065pXX3uyWJ3c0Q";
DecodedJWT decodedJWT = parse(token);
setUserContext(decodedJWT);
DecodedJWT jwt = getUserContext();
System.out.println(jwt.getToken());
System.out.println(getSessionkey(decodedJWT));
}
}
package com.zq.im.utils;
import java.util.UUID;
/**
* Utility class that handles the uuid stuff.
*
* @author wilmiam
* @since 2021-07-09 18:05
*/
public class UuidUtils {
/**
* Don't let anyone instantiate this class
*/
private UuidUtils() {
}
/**
* 返回一个随机的带有分隔符"-"的36位UUID字符串
*/
public static String uuid() {
return UUID.randomUUID().toString();
}
/**
* 返回一个随机的没有分隔符"-"的32位UUID字符串
*/
public static String uuidNoDash() {
return uuid().replaceAll("-", "");
}
}
package com.zq.im.utils;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.net.url.UrlQuery;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.zq.im.modules.im.vo.DetectionResVo;
import lombok.Data;
import java.io.File;
import java.io.InputStream;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
/**
* 使用的请求工具为hutool
* <dependency>
* <groupId>cn.hutool</groupId>
* <artifactId>hutool-all</artifactId>
* <version>${hutool.version}</version>
* </dependency>
*
* @author wilmiam
* @since 2021/12/13 9:21
*/
public class V1Demo {
private static final Map<String, ApiInfo> API_INFO_MAP = new HashMap<>();
private static final String APP_ID = "ec3f54b398e64fbeb9ed00bb0144a91b";
private static final String APP_SECRET = "4c0d6c2a99e541c8a1affc3c8d8dcddb";
private static final String URL = "http://127.0.0.1:9900/api/v1/action";
public static ApiInfo getApiInfo() {
ApiInfo apiInfo = API_INFO_MAP.get(APP_ID);
if (apiInfo != null && (apiInfo.getDueTime().getTime() - 3000) > System.currentTimeMillis()) {
return apiInfo;
}
Map<String, Object> bizContentMap = new HashMap<>();
bizContentMap.put("appId", APP_ID);
bizContentMap.put("appSecret", APP_SECRET);
String apiNo = IdUtil.simpleUUID();
String timestamp = DateUtil.format(DateUtil.date(), DatePattern.PURE_DATETIME_PATTERN);
//JSON字符串
String bizContent = JSONUtil.toJsonStr(bizContentMap);
Map<String, Object> params = new HashMap<>(6);
params.put("appId", APP_ID);
params.put("apiNo", apiNo);
params.put("method", "getApiToken");
params.put("timestamp", timestamp);
params.put("bizContent", URLUtil.encode(Base64.encode(bizContent)));
HttpRequest request = HttpRequest.post(URL)
.contentType("application/x-www-form-urlencoded")
.form(params);
System.out.println("请求报文 => " + UrlQuery.of(params));
HttpResponse execute = request.execute();
String body = execute.body();
System.out.println("响应报文 => " + body);
int status = execute.getStatus();
if (status == 200) {
JSONObject obj = JSONUtil.parseObj(body);
Integer code = obj.getInt("code");
if (code == 200) {
JSONObject data = obj.getJSONObject("data");
apiInfo = data.toBean(ApiInfo.class);
apiInfo.setDueTime(new Date(System.currentTimeMillis() + (data.getInt("expireTime") * 1000)));
API_INFO_MAP.put(APP_ID, apiInfo);
}
}
return apiInfo;
}
public static Object doPost(String method, Map<String, Object> bizContentMap, String sessionKey) {
return doPost(method, bizContentMap, sessionKey, null, null);
}
public static Object doPost(String method, Map<String, Object> bizContentMap, String sessionKey, File file, File[] fileList) {
String apiNo = IdUtil.simpleUUID();
String timestamp = DateUtil.format(DateUtil.date(), DatePattern.PURE_DATETIME_PATTERN);
//JSON字符串
String bizContent = bizContentMap == null ? "" : JSONUtil.toJsonStr(bizContentMap);
Map<String, Object> params = new HashMap<>(6);
params.put("appId", APP_ID);
params.put("apiNo", apiNo);
params.put("method", method);
params.put("timestamp", timestamp);
params.put("bizContent", URLUtil.encode(Base64.encode(bizContent)));
params.put("sign", getSign(bizContent, method, timestamp, apiNo, sessionKey, file, fileList));
params.put("file", file);
params.put("fileList", fileList);
// POST的Content-Type默认是application/x-www-form-urlencoded,有文件则是multipart/form-data
HttpRequest request = HttpRequest.post(URL)
.form(params);
System.out.println("请求报文 => " + UrlQuery.of(params));
HttpResponse execute = request.execute();
String body = execute.body();
System.out.println("响应报文 => " + body);
// 失败 => {"apiNo":"09c1ad82ec0f4b2d80cae0cfb1d7059b","code":"103","msg":"调用方法异常","timestamp":1638176339560,"data":null,"success":false}
// 成功 => {"apiNo":"bc070a7c31ac4b8eb1180b2d82a2096b","code":"200","msg":"成功","timestamp":1638176552353,"data":{"userId":"123","username":"admin@gxfy.com"},"success":true}
int status = execute.getStatus();
if (status == 200) {
JSONObject obj = JSONUtil.parseObj(body);
Integer code = obj.getInt("code");
if (code == 200) {
return obj.get("data");
}
}
return null;
}
/**
* 下载文件
*
* @param method
* @param bizContentMap
* @param sessionKey
* @return
*/
public static InputStream download(String method, Map<String, Object> bizContentMap, String sessionKey) {
String apiNo = IdUtil.simpleUUID();
String timestamp = DateUtil.format(DateUtil.date(), DatePattern.PURE_DATETIME_PATTERN);
//JSON字符串
String bizContent = bizContentMap == null ? "" : JSONUtil.toJsonStr(bizContentMap);
Map<String, Object> params = new HashMap<>(6);
params.put("appId", APP_ID);
params.put("apiNo", apiNo);
params.put("method", method);
params.put("timestamp", timestamp);
params.put("bizContent", URLUtil.encode(Base64.encode(bizContent)));
params.put("sign", getSign(bizContent, method, timestamp, apiNo, sessionKey, null, null));
// POST的Content-Type默认是application/x-www-form-urlencoded,有文件则是multipart/form-data
HttpRequest request = HttpRequest.post(URL)
.form(params);
System.out.println("请求报文 => " + UrlQuery.of(params));
HttpResponse execute = request.execute();
if (!execute.isOk()) {
String body = execute.body();
System.out.println("响应报文 => " + body);
return null;
}
return execute.bodyStream();
}
/**
* 获取签名
*
* @param bizContent
* @param method
* @param timestamp
* @param apiNo
* @param sessionKey
* @return
*/
public static String getSign(String bizContent, String method, String timestamp, String apiNo, String sessionKey, File file, File[] fileList) {
// 参与签名的参数排序
TreeMap<String, String> signTreeMap = new TreeMap<>();
signTreeMap.put("apiNo", apiNo);
signTreeMap.put("timestamp", timestamp);
signTreeMap.put("method", method);
signTreeMap.put("bizContent", bizContent);
if (file != null) {
signTreeMap.put("fileMd5", SecureUtil.md5(file));
}
if (ArrayUtil.isNotEmpty(fileList)) {
StringBuilder md5 = new StringBuilder();
for (File f : fileList) {
md5.append(SecureUtil.md5(f));
}
signTreeMap.put("fileMd5", md5.toString());
}
// 拼接签名参数
StringBuilder src = new StringBuilder();
for (Map.Entry<String, String> entry : signTreeMap.entrySet()) {
src.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
}
src.append("key=").append(sessionKey);
return SecureUtil.md5(src.toString());
}
public static void main(String[] args) {
// 这两个值可以存起来,不需每次都获取
ApiInfo apiInfo = getApiInfo();
String sessionKey = apiInfo.getKey();
System.out.println(sessionKey);
// String sessionKey = "860a013f4e7344bca5869b361a186648";
Map<String, Object> params = new HashMap<>(2);
params.put("filename", "6.png");
params.put("fileContent", Base64.encode(FileUtil.file("C:\\Users\\11419\\Desktop\\test\\bend1.jpg")));
Object o = doPost("detection", params, sessionKey);
DetectionResVo convert = Convert.convert(DetectionResVo.class, o);
System.out.println(JSONUtil.toJsonStr(convert));
}
@Data
public static class ApiInfo {
private String userId;
private String username;
private String key;
private String expireTime;
private String unit;
private Date dueTime;
}
}
server:
port: 9900
tomcat:
max-http-form-post-size: -1
#配置数据源
spring:
application:
name: INTERFACE-SERVER
servlet:
#上传文件限制
multipart:
#单个文件大小
max-file-size: 20MB
#设置总上传的数据大小
max-request-size: 100MB
mvc:
hiddenmethod:
filter:
enabled: true #启用Servlet的Filter过滤器,需要的地方HttpServletRequestFilter
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
redis:
#数据库索引
database: 7
host: ${redis.url}
port: ${redis.port}
password: ${redis.password}
lettuce:
pool:
min-idle: 10
max-active: 50
max-idle: 16
max-wait: 5000
time-between-eviction-runs: 1s
datasource:
druid:
db-type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: ${db.cloud.driver-class-name}
username: ${db.cloud.username}
password: ${db.cloud.password}
url: ${db.cloud.url.cloud}
initial-size: 3 # 初始化时建立物理连接的个数
min-idle: 5 # 最小连接池数量
max-active: 200 # 最大连接池数量
max-wait: 5000 # 获取连接时最大等待时间,单位毫秒
time-between-eviction-runs-millis: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
min-evictable-idle-time-millis: 100000 # 连接保持空闲而不被驱逐的最小时间
max-evictable-idle-time-millis: 300000 # 连接保持空闲而不被驱逐的最大时间
test-while-idle: true
test-on-borrow: false
test-on-return: false
validation-query: select 1
webStatFilter:
enabled: true
#安全配置
stat-view-servlet:
enabled: true
url-pattern: /druid/*
reset-enable: false
login-username: admin
login-password: GXfy2021
filter:
stat:
enabled: true
log-slow-sql: true
slow-sql-millis: 1000
merge-sql: true
wall:
config:
multi-statement-allow: true
# mybatis plus 配置
mybatis-plus:
global-config:
db-config:
select-strategy: not_empty
update-strategy: not_empty
spring:
profiles:
active: @profiles.active@
cloud:
config:
name: config
profile: ${spring.profiles.active}
discovery:
enabled: true
service-id: CONFIG-SERVER
username: admin
password: GXfy2022
eureka:
instance:
prefer-ip-address: true
lease-renewal-interval-in-seconds: 2 #向服务端发送心跳间隔
lease-expiration-duration-in-seconds: 6 #告诉服务端多少秒没收到心跳将我踢出掉
instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}
client:
registry-fetch-interval-seconds: 2 #从服务端注册表中获取注册信息的时间间隔
serviceUrl:
defaultZone: @register.url@
feign:
client:
config:
default:
connect-timeout: 2000 #连接超时时间
read-timeout: 10000 #读超时时间
ribbon:
# ribbon服务列表刷新间隔(单位ms)
ServerListRefreshInterval: 2000
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!--<include resource="org/springframework/boot/logging/logback/base.xml"/>-->
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<include resource="org/springframework/boot/logging/logback/console-appender.xml"/>
<property name="default_log_path" value="logs"/>
<property name="default_log_file" value="interface"/><!--主要日志文件名-->
<property name="LOG_PATH" value="${LOG_PATH:-${default_log_path}}"/>
<property name="LOG_FILE" value="${LOG_FILE:-${default_log_file}}"/>
<!-- the name of the application's logging context -->
<!-- by default each JMXConfigurator instance will be registered under the same name in the same JVM -->
<!-- we need to set the contextName for different apps, so that the jmxconfigurator won't collide -->
<contextName>user</contextName>
<jmxConfigurator/>
<!--主要日志配置 开始-->
<appender name="SIZED_ROLLING_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder>
<pattern>%d %-5p [%t] %logger : %m%n</pattern>
<charset>UTF-8</charset>
</encoder>
<file>${LOG_FILE}.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/${LOG_FILE}.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>50MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!-- 日志文档保留天数 -->
<maxHistory>15</maxHistory>
</rollingPolicy>
</appender>
<!-- 异步输出 -->
<appender name="main-logger-appender" class="ch.qos.logback.classic.AsyncAppender">
<!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
<discardingThreshold>0</discardingThreshold>
<!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
<queueSize>512</queueSize>
<!-- 添加附加的appender,最多只能添加一个 -->
<appender-ref ref="SIZED_ROLLING_FILE"/>
</appender>
<!--主要日志配置 结束-->
<!--DEBUG日志配置 开始-->
<appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder>
<pattern>%d %-5p [%t] %logger : %m%n</pattern>
<charset>UTF-8</charset>
</encoder>
<file>debug.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/debug.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>50MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!-- 日志文档保留天数 -->
<maxHistory>15</maxHistory>
</rollingPolicy>
<!-- 此日志文件只记录ERROR级别的 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>DEBUG</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 异步输出 -->
<appender name="debug-appender" class="ch.qos.logback.classic.AsyncAppender">
<!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
<discardingThreshold>0</discardingThreshold>
<!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
<queueSize>512</queueSize>
<!-- 添加附加的appender,最多只能添加一个 -->
<appender-ref ref="DEBUG_FILE"/>
</appender>
<!--DEBUG日志配置 结束-->
<!--INFO日志配置 开始-->
<appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder>
<pattern>%d %-5p [%t] %logger : %m%n</pattern>
<charset>UTF-8</charset>
</encoder>
<file>info.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/info.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>50MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!-- 日志文档保留天数 -->
<maxHistory>15</maxHistory>
</rollingPolicy>
<!-- 此日志文件只记录ERROR级别的 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 异步输出 -->
<appender name="info-appender" class="ch.qos.logback.classic.AsyncAppender">
<!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
<discardingThreshold>0</discardingThreshold>
<!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
<queueSize>512</queueSize>
<!-- 添加附加的appender,最多只能添加一个 -->
<appender-ref ref="INFO_FILE"/>
</appender>
<!--INFO日志配置 结束-->
<!--WARN日志配置 开始-->
<appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder>
<pattern>%d %-5p [%t] %logger : %m%n</pattern>
<charset>UTF-8</charset>
</encoder>
<file>warn.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/warn.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>50MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!-- 日志文档保留天数 -->
<maxHistory>15</maxHistory>
</rollingPolicy>
<!-- 此日志文件只记录ERROR级别的 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>WARN</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 异步输出 -->
<appender name="warn-appender" class="ch.qos.logback.classic.AsyncAppender">
<!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
<discardingThreshold>0</discardingThreshold>
<!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
<queueSize>512</queueSize>
<!-- 添加附加的appender,最多只能添加一个 -->
<appender-ref ref="WARN_FILE"/>
</appender>
<!--WARN日志配置 结束-->
<!--ERROR错误日志配置 开始-->
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 正在记录的日志文件的路径及文件名 -->
<file>error.log</file>
<!--日志文件输出格式-->
<encoder>
<pattern>%d %-5p [%t] %logger : %m%n</pattern>
<charset>UTF-8</charset> <!-- 此处设置字符集 -->
</encoder>
<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/error.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>50MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!--日志文件保留天数-->
<maxHistory>15</maxHistory>
</rollingPolicy>
<!-- 此日志文件只记录ERROR级别的 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 异步输出 -->
<appender name="error-appender" class="ch.qos.logback.classic.AsyncAppender">
<!-- 不丢失日志.默认的,如果队列的80%已满,则会丢弃TRACT、DEBUG、INFO级别的日志 -->
<discardingThreshold>0</discardingThreshold>
<!-- 更改默认的队列的深度,该值会影响性能.默认值为256 -->
<queueSize>512</queueSize>
<!-- 添加附加的appender,最多只能添加一个 -->
<appender-ref ref="ERROR_FILE"/>
</appender>
<!--ERROR错误日志配置 结束-->
<logger name="com.zq.im" level="DEBUG"/>
<logger name="com.zq.im.dao" level="INFO"/>
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="main-logger-appender"/>
<appender-ref ref="debug-appender"/>
<appender-ref ref="info-appender"/>
<appender-ref ref="warn-appender"/>
<appender-ref ref="error-appender"/>
</root>
</configuration>
...@@ -41,6 +41,7 @@ ...@@ -41,6 +41,7 @@
<module>admin-server</module> <module>admin-server</module>
<module>logging-server</module> <module>logging-server</module>
<module>imgproc-server</module> <module>imgproc-server</module>
<module>interface-server</module>
</modules> </modules>
<dependencies> <dependencies>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment