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,36 +129,61 @@ public class ImageDetectionService { ...@@ -118,36 +129,61 @@ public class ImageDetectionService {
detection.setFileUrl(img.getUrl()); detection.setFileUrl(img.getUrl());
detection.setSize(transformMb(image.length())); detection.setSize(transformMb(image.length()));
// 设置图片检测信息
Mat src = Imgcodecs.imread(img.getUrl());
// 检测图片的分辨率
detection.setWidthResolution(src.width());
detection.setHeightResolution(src.height());
// 检测图片的DPI
detection.setDpi(ImageUtil.getDpi(image));
// 检测图片清晰度
BigDecimal clarity = BigDecimal.valueOf(ImageUtil.tenengrad(src));
detection.setClarity(clarity.setScale(2, RoundingMode.HALF_UP).doubleValue());
// 检测图片的亮度
detection.setBrightness(Convert.toInt(ImageUtil.brightness(src)));
// 检测图片倾斜角度
detection.setDeskewAngel(Math.abs(Deskew.getDeskewAngle(src)));
// 检测图片的黑边
detection.setBlack(ImageUtil.blackDetection2(src));
// 图片弯曲检测
BendResult bendResult = BendUtil.getBendResult(img.getUrl());
detection.setBend(bendResult.getConfidence());
detection.setBend(0.0);
// 检查是否合格
detection.setQualified(check(detection, imgSetting));
detections.add(detection); detections.add(detection);
} }
// 批量插入 // 批量插入
imageDetectionDao.insertBatch(detections); 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); return ResultVo.success(detections);
} }
private void detectionImage(ImageDetection detection, ImgSetting imgSetting) throws Exception {
// 设置图片检测信息
String url = detection.getFileUrl();
long start = System.currentTimeMillis();
Mat src = Imgcodecs.imread(url);
// 检测图片的分辨率
detection.setWidthResolution(src.width());
detection.setHeightResolution(src.height());
// 检测图片的DPI
detection.setDpi(ImageUtil.getDpi(FileUtil.file(url)));
// 检测图片清晰度
BigDecimal clarity = BigDecimal.valueOf(ImageUtil.tenengrad(src));
detection.setClarity(clarity.setScale(2, RoundingMode.HALF_UP).doubleValue());
// 检测图片的亮度
detection.setBrightness(Convert.toInt(ImageUtil.brightness(src)));
// 检测图片倾斜角度
detection.setDeskewAngel(Math.abs(Deskew.getDeskewAngle(src)));
long time1 = System.currentTimeMillis() - start;
// 检测图片的黑边
detection.setBlack(ImageUtil.blackDetection2(src));
long time2 = System.currentTimeMillis() - start;
// 图片弯曲检测
BendResult bendResult = BendUtil.getBendResult(url);
detection.setBend(bendResult.getConfidence());
// detection.setBend(0.0);
// 检查是否合格
detection.setQualified(check(detection, imgSetting));
imageDetectionDao.updateById(detection);
log.info("{}执行图片检测, 文件大小为:{}, 检测偏离度:{}, 检测黑边:{},耗时为:{}", url, detection.getSize(), time1, time2, System.currentTimeMillis() - start);
src.release();
}
/** /**
* 检查图片是否合格 * 检查图片是否合格
* 1. 亮度 * 1. 亮度
...@@ -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); for (Directory directory : metadata.getDirectories()) {
// bright(src, dst); for (Tag tag : directory.getTags()) {
// im(src, dst); System.out.println(tag.getTagName() + "--------" + tag.getDescription());
} if ("X Resolution".equals(tag.getTagName())) {
System.out.println(Convert.toInt(tag.getDescription()));
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 (Tag tag : directory.getTags()) {
if ("X Resolution".equals(tag.getTagName())) {
res = 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);
}
}
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