Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
I
imgproc
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
陈皓
imgproc
Commits
87efa3b4
Commit
87efa3b4
authored
Nov 09, 2023
by
陈皓
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
添加接口服务系统
parent
ee256af6
Show whitespace changes
Inline
Side-by-side
Showing
67 changed files
with
4282 additions
and
1789 deletions
+4282
-1789
config-server/src/main/resources/properties/config-dev.yml
+4
-22
config-server/src/main/resources/properties/config-product.yml
+14
-0
imgproc-server/src/main/java/com/zq/imgproc/server/ImgProcService.java
+1
-0
imgproc-server/src/main/resources/application.yml
+8
-8
imgprocessing-server/src/main/java/com/zq/processing/controller/ApiController.java
+0
-172
imgprocessing-server/src/main/java/com/zq/processing/service/ImgProcService.java
+0
-230
imgprocessing-server/src/main/java/com/zq/processing/utils/DemoUtil.java
+0
-70
imgprocessing-server/src/main/java/com/zq/processing/utils/Deskew.java
+0
-156
imgprocessing-server/src/main/java/com/zq/processing/utils/ImageCorrectionUtil.java
+0
-168
imgprocessing-server/src/main/java/com/zq/processing/utils/ImageUtil.java
+0
-186
imgprocessing-server/src/main/java/com/zq/processing/utils/RemoveBlackUtil.java
+0
-203
imgprocessing-server/src/main/java/com/zq/processing/utils/TestUtil.java
+0
-11
imgprocessing-server/src/main/java/com/zq/processing/utils/UploadUtils.java
+0
-45
imgprocessing-server/src/main/java/com/zq/processing/utils/ValidateUtil.java
+0
-320
imgprocessing-server/src/main/java/com/zq/processing/vo/OptimizationReq.java
+0
-20
imgprocessing-server/src/main/java/com/zq/processing/vo/OptimizationVO.java
+0
-56
imgprocessing-server/src/main/resources/application.yml
+0
-29
interface-server/pom.xml
+74
-31
interface-server/src/main/java/com/zq/im/InterfaceApplication.java
+13
-3
interface-server/src/main/java/com/zq/im/annotation/ApiLog.java
+24
-0
interface-server/src/main/java/com/zq/im/annotation/rest/ApiLogAspect.java
+130
-0
interface-server/src/main/java/com/zq/im/config/RedisConfig.java
+213
-0
interface-server/src/main/java/com/zq/im/config/WebMvcConfig.java
+31
-0
interface-server/src/main/java/com/zq/im/config/feign/FeignConfig.java
+95
-0
interface-server/src/main/java/com/zq/im/config/feign/FeignSpringFormEncoder.java
+55
-0
interface-server/src/main/java/com/zq/im/config/mybatis/InsertBatchSqlInjector.java
+30
-0
interface-server/src/main/java/com/zq/im/config/mybatis/MyMetaObjectHandler.java
+39
-0
interface-server/src/main/java/com/zq/im/config/mybatis/MybatisPlusConfig.java
+48
-0
interface-server/src/main/java/com/zq/im/constant/ApiCodeEnum.java
+94
-0
interface-server/src/main/java/com/zq/im/constant/ApiVersionEnum.java
+21
-0
interface-server/src/main/java/com/zq/im/constant/TokenConstant.java
+57
-0
interface-server/src/main/java/com/zq/im/exception/BusinessException.java
+3
-3
interface-server/src/main/java/com/zq/im/interceptor/ApiMethodInterceptor.java
+77
-0
interface-server/src/main/java/com/zq/im/modules/api/controller/ApiV1.java
+86
-0
interface-server/src/main/java/com/zq/im/modules/api/form/ApiForm.java
+145
-0
interface-server/src/main/java/com/zq/im/modules/api/form/ApiResp.java
+63
-0
interface-server/src/main/java/com/zq/im/modules/api/service/ApiManage.java
+92
-0
interface-server/src/main/java/com/zq/im/modules/api/service/IApiCommon.java
+16
-0
interface-server/src/main/java/com/zq/im/modules/api/service/IApiLogic.java
+73
-0
interface-server/src/main/java/com/zq/im/modules/api/service/impl/ApiV1Logic.java
+103
-0
interface-server/src/main/java/com/zq/im/modules/api/service/impl/ApiV2Logic.java
+17
-0
interface-server/src/main/java/com/zq/im/modules/api/service/impl/BaseApiLogic.java
+22
-0
interface-server/src/main/java/com/zq/im/modules/api/utils/ApiUtils.java
+125
-0
interface-server/src/main/java/com/zq/im/modules/api/utils/EncryptUtils.java
+138
-0
interface-server/src/main/java/com/zq/im/modules/api/utils/ReflectionUtils.java
+163
-0
interface-server/src/main/java/com/zq/im/modules/api/utils/RsaUtils.java
+240
-0
interface-server/src/main/java/com/zq/im/modules/api/vo/MethodVo.java
+43
-0
interface-server/src/main/java/com/zq/im/modules/im/controller/ImgProcController.java
+83
-0
interface-server/src/main/java/com/zq/im/modules/im/feign/ImgprocFeign.java
+24
-0
interface-server/src/main/java/com/zq/im/modules/im/req/ImageReq.java
+32
-0
interface-server/src/main/java/com/zq/im/modules/im/service/ImgProcService.java
+93
-0
interface-server/src/main/java/com/zq/im/modules/im/vo/DetectionResVo.java
+2
-2
interface-server/src/main/java/com/zq/im/modules/system/dao/ApiUserInfoDao.java
+16
-0
interface-server/src/main/java/com/zq/im/modules/system/entity/ApiUserInfo.java
+62
-0
interface-server/src/main/java/com/zq/im/modules/system/service/ApiUserInfoService.java
+71
-0
interface-server/src/main/java/com/zq/im/modules/system/vo/ResultVo.java
+1
-1
interface-server/src/main/java/com/zq/im/utils/AssertUtils.java
+2
-2
interface-server/src/main/java/com/zq/im/utils/IpUtil.java
+168
-0
interface-server/src/main/java/com/zq/im/utils/RedisUtil.java
+795
-0
interface-server/src/main/java/com/zq/im/utils/ThreadContextUtil.java
+81
-0
interface-server/src/main/java/com/zq/im/utils/TokenUtil.java
+156
-0
interface-server/src/main/java/com/zq/im/utils/UuidUtils.java
+1
-27
interface-server/src/main/java/com/zq/im/utils/V1Demo.java
+236
-0
interface-server/src/main/resources/application.yml
+77
-0
interface-server/src/main/resources/bootstrap.yml
+11
-0
interface-server/src/main/resources/logback-spring.xml
+3
-3
pom.xml
+11
-21
No files found.
config-server/src/main/resources/properties/config-dev.yml
View file @
87efa3b4
...
@@ -6,26 +6,8 @@ redis.password:
...
@@ -6,26 +6,8 @@ redis.password:
#数据库源配置
#数据库源配置
db
:
db
:
fypt
:
fypt
:
username
:
oa_system
username
:
root
password
:
GXfy/2014!@#$
password
:
Dk2019!23456
driver-class-name
:
com.
kingbase8
.Driver
driver-class-name
:
com.
mysql.cj.jdbc
.Driver
url
:
url
:
sales
:
jdbc:kingbase8://172.28.1.68:54321/SALES
image
:
jdbc:mysql://119.45.183.210:13308/image?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
#jwt
jwt
:
header
:
Authorization
# 令牌前缀
token-start-with
:
Bearer
# 必须使用最少88位的Base64对该令牌进行编码
base64-secret
:
ZmQ0ZGI5NjQ0MDQwY2I4MjMxY2Y3ZmI3MjdhN2ZmMjNhODViOTg1ZGE0NTBjMGM4NDA5NzYxMjdjOWMwYWRmZTBlZjlhNGY3ZTg4Y2U3YTE1ODVkZDU5Y2Y3OGYwZWE1NzUzNWQ2YjFjZDc0NGMxZWU2MmQ3MjY1NzJmNTE0MzI=
# 令牌过期时间 此处单位/毫秒 ,默认2小时,可在此网站生成 https://www.convertworld.com/zh-hans/time/milliseconds.html
token-validity-in-seconds
:
7200000
# 在线用户key
online-key
:
online-token-
# 验证码
code-key
:
code-key-
# token 续期检查时间范围(默认30分钟,单位默认毫秒),在token即将过期的一段时间内用户操作了,则给用户的token续期
detect
:
1800000
# 续期时间范围,默认 1小时,这里单位毫秒
renew
:
3600000
config-server/src/main/resources/properties/config-product.yml
0 → 100644
View file @
87efa3b4
#Redis配置
redis.url
:
147.1.5.135
redis.port
:
6379
redis.password
:
Gxfy2022!@#$
#数据库源配置
db
:
fypt
:
username
:
ymts1
password
:
gxfy123456
driver-class-name
:
com.mysql.cj.jdbc.Driver
url
:
image
:
jdbc:mysql://147.1.3.70:3306/image?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
\ No newline at end of file
imgproc-server/src/main/java/com/zq/imgproc/server/ImgProcService.java
View file @
87efa3b4
...
@@ -136,6 +136,7 @@ public class ImgProcService {
...
@@ -136,6 +136,7 @@ public class ImgProcService {
// 检测图片的黑边
// 检测图片的黑边
res
.
setBlack
(
blackDetection
(
image
));
res
.
setBlack
(
blackDetection
(
image
));
image
.
release
();
image
.
release
();
FileUtil
.
del
(
filePath
);
return
res
;
return
res
;
}
}
...
...
imgproc-server/src/main/resources/application.yml
View file @
87efa3b4
...
@@ -18,12 +18,12 @@ spring:
...
@@ -18,12 +18,12 @@ spring:
ip
:
ip
:
local-parsing
:
true
local-parsing
:
true
imgconfig
:
opencv
:
/opt/tianjin/lib/opencv_java460.so
deskew
:
/opt/tianjin/lib/Deskew/Bin/deskew
deskewpy
:
/opt/tianjin/lib/correct.py
#imgconfig:
#imgconfig:
# opencv: D:/project/imgproc/lib/opencv_java460.dll
# opencv: /opt/services/tianjin-backend/lib/opencv_java460.so
# deskew: C:/Users/11419/Desktop/Deskew/Bin/deskew.exe
# deskew: /opt/services/tianjin-backend/lib/Deskew/Bin/deskew
# deskewpy: D:/project/imgproc/lib/correct.py
# deskewpy: /opt/tianjin/lib/correct.py
imgconfig
:
opencv
:
D:/project/imgproc/lib/opencv_java460.dll
deskew
:
C:/Users/11419/Desktop/Deskew/Bin/deskew.exe
deskewpy
:
D:/project/imgproc/lib/correct.py
imgprocessing-server/src/main/java/com/zq/processing/controller/ApiController.java
deleted
100644 → 0
View file @
ee256af6
package
com
.
zq
.
processing
.
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.processing.service.ImgProcService
;
import
com.zq.processing.utils.*
;
import
com.zq.processing.vo.DetectionResVo
;
import
com.zq.processing.vo.OptimizationReq
;
import
com.zq.processing.vo.OptimizationVO
;
import
io.swagger.annotations.ApiOperation
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.beans.factory.annotation.Value
;
import
org.springframework.web.bind.annotation.*
;
import
org.springframework.web.multipart.MultipartFile
;
import
java.io.IOException
;
import
java.text.SimpleDateFormat
;
import
java.util.Date
;
import
java.util.HashMap
;
/**
* <p>
* 图片处理API
* </p>
*
* @author chenhao
* @since 2023/3/18 9:32
*/
@io
.
swagger
.
annotations
.
Api
(
tags
=
"图片处理API"
)
@RequestMapping
(
"/imgproc/v1"
)
@RestController
public
class
ApiController
{
@Value
(
"${imgconfig.opencv}"
)
String
opencvUrl
;
@Value
(
"${imgconfig.deskew}"
)
String
deskewUrl
;
private
final
ImgProcService
service
;
@Autowired
public
ApiController
(
ImgProcService
service
)
{
this
.
service
=
service
;
}
@ApiOperation
(
"测试"
)
@PostMapping
(
"/ping"
)
public
ResultVo
<?>
ping
()
{
return
ResultVo
.
success
(
"测试成功"
);
}
@ApiOperation
(
"图片检测"
)
@PostMapping
(
"/detection"
)
public
ResultVo
<
DetectionResVo
>
detection
(
@RequestBody
OptimizationReq
req
)
throws
Exception
{
AssertUtils
.
hasText
(
req
.
getFileContent
(),
"缺少文件内容"
);
AssertUtils
.
hasText
(
req
.
getFilename
(),
"缺少文件名"
);
return
ResultVo
.
success
(
service
.
detection
(
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
;
// 去黑边
RemoveBlackUtil
.
remove
(
imgPath
,
savePath
);
// 图片弯曲矫正
HashMap
<
String
,
Object
>
map
=
new
HashMap
<>();
map
.
put
(
"file"
,
FileUtil
.
file
(
savePath
));
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
(
"/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
.
getFilename
(),
"缺少文件名"
);
return
ResultVo
.
success
(
service
.
optimization
(
req
));
}
}
imgprocessing-server/src/main/java/com/zq/processing/service/ImgProcService.java
deleted
100644 → 0
View file @
ee256af6
package
com
.
zq
.
processing
.
service
;
import
cn.hutool.core.codec.Base64Decoder
;
import
cn.hutool.core.codec.Base64Encoder
;
import
cn.hutool.core.convert.Convert
;
import
cn.hutool.core.io.FileUtil
;
import
com.zq.processing.utils.ImageCorrectionUtil
;
import
com.zq.processing.utils.ImageUtil
;
import
com.zq.processing.utils.RemoveBlackUtil
;
import
com.zq.processing.utils.UuidUtils
;
import
com.zq.processing.vo.DetectionResVo
;
import
com.zq.processing.vo.OptimizationReq
;
import
com.zq.processing.vo.OptimizationVO
;
import
lombok.Cleanup
;
import
lombok.RequiredArgsConstructor
;
import
lombok.extern.slf4j.Slf4j
;
import
org.bytedeco.opencv.opencv_core.Mat
;
import
org.springframework.beans.factory.annotation.Value
;
import
org.springframework.stereotype.Service
;
import
javax.imageio.ImageIO
;
import
java.awt.image.BufferedImage
;
import
java.io.File
;
import
java.io.IOException
;
import
java.text.SimpleDateFormat
;
import
java.util.Date
;
import
java.util.regex.Matcher
;
import
java.util.regex.Pattern
;
import
static
org
.
bytedeco
.
opencv
.
global
.
opencv_imgcodecs
.
imread
;
import
static
org
.
bytedeco
.
opencv
.
global
.
opencv_imgcodecs
.
imwrite
;
/**
* <p>
* 图片检测服务
* </p>
*
* @author chenhao
* @since 2022/10/24 15:02
*/
@Slf4j
@RequiredArgsConstructor
@Service
public
class
ImgProcService
{
@Value
(
"${imgconfig.deskew}"
)
String
deskewUrl
;
@Value
(
"${imgconfig.deskewpy}"
)
String
deskewpyUrl
;
public
DetectionResVo
detection
(
OptimizationReq
req
)
throws
Exception
{
// 1. 临时保存图片
String
ext
=
FileUtil
.
extName
(
req
.
getFilename
());
String
yyyyMMdd
=
new
SimpleDateFormat
(
"/yyyyMM/dd/"
).
format
(
new
Date
());
String
filePath
=
"/data/temp"
+
yyyyMMdd
+
UuidUtils
.
uuidNoDash
()
+
"."
+
ext
;
FileUtil
.
writeBytes
(
Base64Decoder
.
decode
(
req
.
getFileContent
()),
filePath
);
if
(
"heic"
.
equals
(
ext
))
{
BufferedImage
bufferedImage
=
ImageIO
.
read
(
new
File
(
filePath
));
ext
=
"jpg"
;
filePath
=
"/data/temp"
+
yyyyMMdd
+
UuidUtils
.
uuidNoDash
()
+
"."
+
ext
;
ImageIO
.
write
(
bufferedImage
,
"jpg"
,
new
File
(
filePath
));
}
DetectionResVo
res
=
new
DetectionResVo
();
@Cleanup
Mat
image
=
imread
(
filePath
);
// 检测图片的分辨率
res
.
setWidthResolution
(
image
.
cols
());
res
.
setHeightResolution
(
image
.
rows
());
// 检测图片的DPI
res
.
setDpi
(
ImageUtil
.
getDpi
(
FileUtil
.
file
(
filePath
)));
// 检测图片清晰度
res
.
setClarity
(
ImageUtil
.
imageSharpnessDetector
(
filePath
));
// 检测图片的亮度
res
.
setBrightness
(
ImageUtil
.
brightness
(
filePath
));
// 检测图片倾斜角度
res
.
setAngle
(
getAngle
(
filePath
));
// 检测图片的黑边
res
.
setBlack
(
RemoveBlackUtil
.
blackDetection
(
image
));
image
.
release
();
return
res
;
}
/**
* 通过矫正工具获取的倾斜角度
*
* @param imageUrl 图片路径
* @return 图片倾斜角度
*/
private
Double
getAngle
(
String
imageUrl
)
{
// 纠正文件的保存路径
String
ext
=
FileUtil
.
extName
(
imageUrl
);
String
yyyyMMdd
=
new
SimpleDateFormat
(
"/yyyyMM/dd/"
).
format
(
new
Date
());
String
savePath
=
"/data/temp"
+
yyyyMMdd
+
UuidUtils
.
uuidNoDash
()
+
"."
+
ext
;
return
0.0
;
}
public
OptimizationVO
optimization
(
OptimizationReq
req
)
throws
IOException
{
OptimizationVO
res
=
new
OptimizationVO
();
// 1. 临时保存图片
String
ext
=
FileUtil
.
extName
(
req
.
getFilename
());
String
filename
=
req
.
getFilename
();
String
yyyyMMdd
=
new
SimpleDateFormat
(
"/yyyyMM/dd/"
).
format
(
new
Date
());
String
savePath
=
"/data/temp"
+
yyyyMMdd
+
UuidUtils
.
uuidNoDash
()
+
"/"
;
String
filePath
=
savePath
+
filename
;
FileUtil
.
writeBytes
(
Base64Decoder
.
decode
(
req
.
getFileContent
()),
filePath
);
log
.
info
(
"{}图片进行优化"
,
filePath
);
// 处理heic图片
if
(
"heic"
.
equals
(
ext
))
{
BufferedImage
bufferedImage
=
ImageIO
.
read
(
new
File
(
filePath
));
ext
=
"jpg"
;
filename
=
FileUtil
.
mainName
(
filename
)
+
".jpg"
;
filePath
=
savePath
+
filename
;
ImageIO
.
write
(
bufferedImage
,
"jpg"
,
new
File
(
filePath
));
log
.
info
(
"{}图片从heic转换为jpg"
,
filePath
);
}
String
newPath
=
filePath
;
// 2. 执行纠偏
long
start
=
System
.
currentTimeMillis
();
String
deskerFile
=
savePath
+
"(1)."
+
ext
;
String
resMsg
=
ImageCorrectionUtil
.
deskew2
(
filePath
,
deskerFile
,
deskewpyUrl
);
Double
skewAngle
=
Convert
.
toDouble
(
resMsg
);
if
(
skewAngle
==
null
)
{
log
.
error
(
resMsg
);
String
deskewRes
=
ImageCorrectionUtil
.
deskew
(
filePath
,
deskerFile
,
deskewUrl
,
false
);
Pattern
pattern
=
Pattern
.
compile
(
"Skew angle found \\[deg\\]: (-?\\d+(?:\\.\\d+)?)"
);
Matcher
matcher
=
pattern
.
matcher
(
deskewRes
);
if
(
matcher
.
find
())
{
skewAngle
=
Double
.
parseDouble
(
matcher
.
group
(
1
));
log
.
info
(
"矫正工具矫正角度【{}】"
,
skewAngle
);
}
else
{
res
.
setDeskewAngel
(
0
);
}
}
if
(
Double
.
compare
(
skewAngle
,
1
)
<
0
&&
Double
.
compare
(
skewAngle
,
-
1
)
>
0
)
{
res
.
setDeskewAngel
(
0
);
}
else
{
newPath
=
deskerFile
;
res
.
setDeskewAngel
(
skewAngle
);
}
log
.
info
(
"纠偏所需时间【{}】"
,
System
.
currentTimeMillis
()
-
start
);
// 3. 黑边处理
start
=
System
.
currentTimeMillis
();
String
blackUrl
=
savePath
+
"(2)."
+
ext
;
@Cleanup
Mat
bMat
=
imread
(
newPath
);
if
(
RemoveBlackUtil
.
blackDetection
(
bMat
))
{
res
.
setRemoveBlack
(
true
);
RemoveBlackUtil
.
remove
(
newPath
,
blackUrl
);
newPath
=
blackUrl
;
}
else
{
res
.
setRemoveBlack
(
false
);
}
res
.
setCorrect
(
false
);
log
.
info
(
"黑边处理消耗的时间【{}】"
,
System
.
currentTimeMillis
()
-
start
);
// 4。 图片亮度检测
// 计算图像的平均亮度
start
=
System
.
currentTimeMillis
();
double
brightness
=
ImageUtil
.
brightness
(
newPath
);
res
.
setOriginalBrightnessVal
(
brightness
);
// 亮度异常执行亮度矫正
if
(
brightness
<
100
||
brightness
>
500
)
{
res
.
setBrightness
(
true
);
// 计算亮度调整值
double
alpha
=
300
/
brightness
;
// 执行亮度调整
@Cleanup
Mat
grayImage
=
imread
(
newPath
);
@Cleanup
Mat
adjustedImage
=
new
Mat
();
grayImage
.
convertTo
(
adjustedImage
,
-
1
,
alpha
,
0
);
// 保存调整后的图像
imwrite
(
newPath
,
adjustedImage
);
res
.
setBrightnessVal
(
ImageUtil
.
brightness
(
newPath
));
}
else
{
res
.
setBrightness
(
false
);
res
.
setBrightnessVal
(
0
);
}
log
.
info
(
"亮度检测所需时间【{}】"
,
System
.
currentTimeMillis
()
-
start
);
// 5. 图片清晰度检测
start
=
System
.
currentTimeMillis
();
double
laplacianScore
=
ImageUtil
.
imageSharpnessDetector
(
newPath
);
res
.
setOriginalClarityVal
(
laplacianScore
);
if
(
laplacianScore
<
500
)
{
res
.
setClarity
(
true
);
Mat
cMat
=
ImageUtil
.
unsharpMasking
(
newPath
);
// 保存调整后的图像
imwrite
(
newPath
,
cMat
);
res
.
setClarityVal
(
ImageUtil
.
imageSharpnessDetector
(
newPath
));
cMat
.
release
();
}
else
{
res
.
setClarity
(
false
);
res
.
setClarityVal
(
0
);
}
log
.
info
(
"图片清晰度所需时间【{}】"
,
System
.
currentTimeMillis
()
-
start
);
res
.
setFilename
(
filename
);
res
.
setFileContent
(
Base64Encoder
.
encode
(
FileUtil
.
readBytes
(
newPath
)));
return
res
;
// 6. 图片弯曲矫正
// String correctUrl = savePath + "(3)." + ext;
// HashMap<String, Object> map = new HashMap<>();
// map.put("file", FileUtil.file(newPath));
// String response = HttpUtil.post("http://ddns.gxmailu.com:18888/api/correct", map);
// JSONObject correctRes = JSONUtil.parseObj(response);
// if (ObjectUtil.isNotNull(correctRes) && correctRes.get("success", Boolean.class)) {
// String base64 = correctRes.get("data", String.class);
// res.setCorrect(true);
// res.setFileContent(base64);
// res.setFilename(filename);
// } else {
// res.setCorrect(false);
// res.setFilename(filename);
// res.setFileContent(Base64Encoder.encode(FileUtil.readBytes(newPath)));
// }
// return res;
}
}
imgprocessing-server/src/main/java/com/zq/processing/utils/DemoUtil.java
deleted
100644 → 0
View file @
ee256af6
package
com
.
zq
.
processing
.
utils
;
import
cn.hutool.core.codec.Base64
;
import
cn.hutool.core.io.FileUtil
;
import
cn.hutool.http.HttpRequest
;
import
cn.hutool.http.HttpResponse
;
import
cn.hutool.http.HttpUtil
;
import
cn.hutool.json.JSONObject
;
import
cn.hutool.json.JSONUtil
;
import
com.zq.processing.vo.DetectionResVo
;
import
com.zq.processing.vo.OptimizationReq
;
import
com.zq.processing.vo.OptimizationVO
;
public
class
DemoUtil
{
final
static
String
URL
=
"localhost:9100/imgproc/v1/"
;
public
static
void
main
(
String
[]
args
)
{
detection
();
// String testImg = "C:/Users/11419/Desktop/Deskew/TestImages/4.png";
// String resImg = "C:/Users/11419/Desktop/Deskew/TestImages/res4.jpg";
// System.out.println(Convert.toInt(-6.25));
// ImgUtil.rotate(FileUtil.file(testImg), Convert.toInt(-6.25), FileUtil.file(resImg));
}
public
static
void
detection
()
{
String
testImg
=
"C:/Users/11419/Desktop/Deskew/TestImages/3.png"
;
String
resImg
=
"C:/Users/11419/Desktop/Deskew/TestImages/res.jpg"
;
String
url
=
URL
+
"detection"
;
OptimizationReq
req
=
new
OptimizationReq
();
req
.
setFilename
(
"3.png"
);
req
.
setFileContent
(
Base64
.
encode
(
FileUtil
.
file
(
testImg
)));
long
start
=
System
.
currentTimeMillis
();
HttpRequest
request
=
HttpUtil
.
createPost
(
url
)
.
body
(
JSONUtil
.
toJsonStr
(
req
));
try
(
HttpResponse
response
=
request
.
execute
())
{
System
.
out
.
println
(
System
.
currentTimeMillis
()
-
start
);
String
res
=
response
.
body
();
JSONObject
object
=
JSONUtil
.
parseObj
(
res
);
DetectionResVo
detectionRes
=
object
.
get
(
"data"
,
DetectionResVo
.
class
);
System
.
out
.
println
(
JSONUtil
.
toJsonStr
(
detectionRes
));
}
}
public
static
void
imageOptimization
()
{
String
testImg
=
"C:/Users/11419/Desktop/Deskew/TestImages/2.png"
;
String
resImg
=
"C:/Users/11419/Desktop/Deskew/TestImages/res.jpg"
;
String
url
=
URL
+
"imageOptimization"
;
OptimizationReq
req
=
new
OptimizationReq
();
req
.
setFilename
(
"3.png"
);
req
.
setFileContent
(
Base64
.
encode
(
FileUtil
.
file
(
testImg
)));
long
start
=
System
.
currentTimeMillis
();
HttpRequest
request
=
HttpUtil
.
createPost
(
url
)
.
body
(
JSONUtil
.
toJsonStr
(
req
));
try
(
HttpResponse
response
=
request
.
execute
())
{
System
.
out
.
println
(
System
.
currentTimeMillis
()-
start
);
String
res
=
response
.
body
();
JSONObject
object
=
JSONUtil
.
parseObj
(
res
);
OptimizationVO
optimizationVO
=
object
.
get
(
"data"
,
OptimizationVO
.
class
);
FileUtil
.
writeBytes
(
Base64
.
decode
(
optimizationVO
.
getFileContent
()),
FileUtil
.
file
(
resImg
));
optimizationVO
.
setFileContent
(
null
);
System
.
out
.
println
(
JSONUtil
.
toJsonStr
(
optimizationVO
));
}
}
}
imgprocessing-server/src/main/java/com/zq/processing/utils/Deskew.java
deleted
100644 → 0
View file @
ee256af6
package
com
.
zq
.
processing
.
utils
;
import
cn.hutool.core.convert.Convert
;
import
lombok.Cleanup
;
import
org.bytedeco.opencv.opencv_core.Mat
;
import
org.bytedeco.opencv.opencv_core.Point2f
;
import
org.bytedeco.opencv.opencv_core.Scalar4i
;
import
org.bytedeco.opencv.opencv_core.Size
;
import
org.bytedeco.opencv.opencv_imgproc.Vec4iVector
;
import
java.util.ArrayList
;
import
java.util.HashMap
;
import
java.util.List
;
import
java.util.Map
;
import
static
org
.
bytedeco
.
opencv
.
global
.
opencv_imgcodecs
.
imread
;
import
static
org
.
bytedeco
.
opencv
.
global
.
opencv_imgproc
.*;
public
class
Deskew
{
public
static
void
main
(
String
[]
args
)
{
String
testImg
=
"C:/Users/11419/Desktop/Deskew/TestImages/4.png"
;
String
resImg
=
"C:/Users/11419/Desktop/Deskew/TestImages/res.png"
;
long
start
=
System
.
currentTimeMillis
();
@Cleanup
Mat
mat
=
imread
(
testImg
);
System
.
out
.
println
(
getDeskewAngle
(
mat
));
System
.
out
.
println
(
System
.
currentTimeMillis
()
-
start
);
}
public
static
Integer
getDeskewAngle
(
Mat
src
)
{
// 图片灰度化
@Cleanup
Mat
gray
=
ImageUtil
.
gray
(
src
);
// // 高斯模糊(?)
// @Cleanup
// UMat blur = gray.clone();
// GaussianBlur(gray, blur, new Size(9, 9), 0);
// // 二值化(?)
// @Cleanup
// UMat thresh = blur.clone();
// threshold(blur, thresh, 0, 255, THRESH_BINARY_INV + THRESH_OTSU);
@Cleanup
Mat
kernel
=
getStructuringElement
(
MORPH_RECT
,
new
Size
(
5
,
5
));
// 图片膨胀
Mat
erode
=
gray
.
clone
();
erode
(
gray
,
erode
,
kernel
);
// 图片腐蚀
@Cleanup
Mat
dilate
=
erode
.
clone
();
dilate
(
erode
,
dilate
,
kernel
);
// 边缘检测
@Cleanup
Mat
canny
=
dilate
.
clone
();
Canny
(
dilate
,
canny
,
50
,
150
);
// 霍夫变换得到线条
@Cleanup
Vec4iVector
lines
=
new
Vec4iVector
();
//累加器阈值参数,小于设置值不返回
int
threshold
=
30
;
//最低线段长度,低于设置值则不返回
double
minLineLength
=
0
;
//间距小于该值的线当成同一条线
double
maxLineGap
=
200
;
// 霍夫变换,通过步长为1,角度为PI/180来搜索可能的直线
HoughLinesP
(
canny
,
lines
,
1
,
Math
.
PI
/
180
,
threshold
,
minLineLength
,
maxLineGap
);
// 计算倾斜角度
List
<
Integer
>
angelList
=
new
ArrayList
<>();
for
(
int
i
=
0
;
i
<
lines
.
size
();
i
++)
{
Scalar4i
scalar4i
=
lines
.
get
(
0
);
int
k
=
calculateAngle
(
scalar4i
.
get
(
0
),
scalar4i
.
get
(
1
),
scalar4i
.
get
(
2
),
scalar4i
.
get
(
3
));
angelList
.
add
(
k
);
}
if
(
angelList
.
isEmpty
())
{
return
0
;
}
return
most
(
angelList
);
}
/**
* 求数组众数
*
* @param angelList 数组
* @return 数组众数
*/
private
static
int
most
(
List
<
Integer
>
angelList
)
{
if
(
angelList
.
isEmpty
())
{
return
0
;
}
int
res
=
0
;
int
max
=
Integer
.
MIN_VALUE
;
Map
<
Integer
,
Integer
>
map
=
new
HashMap
<>();
for
(
int
i
:
angelList
)
{
map
.
put
(
i
,
map
.
getOrDefault
(
i
,
0
)
+
1
);
}
for
(
Integer
i
:
map
.
keySet
())
{
int
count
=
map
.
get
(
i
);
if
(
count
>
max
)
{
max
=
count
;
res
=
i
;
}
}
return
res
;
}
/**
* 计算直线的倾斜角
*/
private
static
int
calculateAngle
(
double
x1
,
double
y1
,
double
x2
,
double
y2
)
{
if
(
Math
.
abs
(
x2
-
x1
)
<
1
e
-
4
)
{
return
90
;
}
else
if
(
Math
.
abs
(
y2
-
y1
)
<
1
e
-
4
)
{
return
0
;
}
else
{
double
k
=
-(
y2
-
y1
)
/
(
x2
-
x1
);
double
res
=
Math
.
atan
(
k
)
*
57.29577
;
return
Convert
.
toInt
(
Math
.
round
(
res
));
}
}
/**
* 图片旋转
*
* @param image 输入图片
* @param angle 旋转角度
* @return 输出图片
*/
public
static
Mat
rotate
(
Mat
image
,
int
angle
)
{
int
w
=
image
.
cols
();
int
h
=
image
.
rows
();
// 定义参数
double
scale
=
1.0
;
@Cleanup
Point2f
center
=
new
Point2f
((
float
)
w
/
2
,
(
float
)
h
/
2
);
// 定义旋转矩阵
@Cleanup
Mat
rotationMatrix
=
getRotationMatrix2D
(
center
,
-
angle
,
scale
);
// 旋转矩阵
Mat
rotatedImage
=
new
Mat
();
warpAffine
(
image
,
rotatedImage
,
rotationMatrix
,
new
Size
(
w
,
h
));
return
rotatedImage
;
}
}
imgprocessing-server/src/main/java/com/zq/processing/utils/ImageCorrectionUtil.java
deleted
100644 → 0
View file @
ee256af6
package
com
.
zq
.
processing
.
utils
;
import
cn.hutool.core.util.RuntimeUtil
;
import
com.drew.imaging.ImageMetadataReader
;
import
com.drew.metadata.Directory
;
import
com.drew.metadata.Metadata
;
import
com.drew.metadata.Tag
;
import
javax.imageio.ImageIO
;
import
java.awt.*
;
import
java.awt.image.BufferedImage
;
import
java.io.File
;
import
java.io.FileOutputStream
;
/**
* <p>
* 纠偏工具Deskew
* </p>
*
* @author chenhao
* @since 2023/3/8 10:23
*/
public
class
ImageCorrectionUtil
{
/**
* 纠正图片旋转
*
* @param srcImgPath 图片地址
*/
public
static
void
correctImg
(
String
srcImgPath
)
{
FileOutputStream
fos
=
null
;
try
{
// 原始图片
File
srcFile
=
new
File
(
srcImgPath
);
// 获取偏转角度
int
angle
=
getAngle
(
srcFile
);
if
(
angle
!=
90
&&
angle
!=
270
)
{
return
;
}
// 原始图片缓存
BufferedImage
srcImg
=
ImageIO
.
read
(
srcFile
);
// 宽高互换
// 原始宽度
int
imgWidth
=
srcImg
.
getHeight
();
// 原始高度
int
imgHeight
=
srcImg
.
getWidth
();
// 中心点位置
double
centerWidth
=
((
double
)
imgWidth
)
/
2
;
double
centerHeight
=
((
double
)
imgHeight
)
/
2
;
// 图片缓存
BufferedImage
targetImg
=
new
BufferedImage
(
imgWidth
,
imgHeight
,
BufferedImage
.
TYPE_INT_RGB
);
// 旋转对应角度
Graphics2D
g
=
targetImg
.
createGraphics
();
g
.
rotate
(
Math
.
toRadians
(
angle
),
centerWidth
,
centerHeight
);
g
.
drawImage
(
srcImg
,
(
imgWidth
-
srcImg
.
getWidth
())
/
2
,
(
imgHeight
-
srcImg
.
getHeight
())
/
2
,
null
);
g
.
rotate
(
Math
.
toRadians
(-
angle
),
centerWidth
,
centerHeight
);
g
.
dispose
();
// 输出图片
fos
=
new
FileOutputStream
(
srcFile
);
ImageIO
.
write
(
targetImg
,
"jpg"
,
fos
);
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
}
finally
{
if
(
fos
!=
null
)
{
try
{
fos
.
flush
();
fos
.
close
();
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
}
}
}
}
/**
* 获取图片旋转角度
*
* @param file 上传图片
* @return 图片旋转角度
*/
private
static
int
getAngle
(
File
file
)
throws
Exception
{
Metadata
metadata
=
ImageMetadataReader
.
readMetadata
(
file
);
for
(
Directory
directory
:
metadata
.
getDirectories
())
{
for
(
Tag
tag
:
directory
.
getTags
())
{
if
(
"Orientation"
.
equals
(
tag
.
getTagName
()))
{
String
orientation
=
tag
.
getDescription
();
if
(
orientation
.
contains
(
"90"
))
{
return
90
;
}
else
if
(
orientation
.
contains
(
"180"
))
{
return
180
;
}
else
if
(
orientation
.
contains
(
"270"
))
{
return
270
;
}
}
}
}
return
0
;
}
/**
* 调用deskew工具
* Usage:
* deskew [-o output] [-a angle] [-b color] [..] input
* input: Input image file
* Options:
* -o output: 输出图像文件路径(默认:out.png)
* -a angle: 最大期望倾斜角度(两个方向)(默认:10度)
* -b color: 背景颜色十六进制格式RRGGBB|LL|AARRGGBB(默认为黑色)
* Ext. options:
* -q filter: 用于旋转的重采样过滤器 (default: linear,
* values: nearest|linear|cubic|lanczos)
* -t a|treshold: 自动阈值或0..255的值 (default: a)
* -r rect: 仅在内容矩形中进行倾斜检测 (pixels):
* left,top,right,bottom (default: whole page)
* -f format: 输出像素格式 (values: b1|g8|rgb24|rgba32)
* -l angle: 如果倾斜角度较小,则跳过倾斜步骤 (default: 0.01)
* -g flags: Operational flags (any combination of):
* c - auto crop, d - detect only (no output to file)
* -s info: 信息转储 (any combination of):
* s - skew detection stats, p - program parameters, t - timings
* -c specs: 某些文件格式的输出压缩规格。可以定义几个规格-用逗号分隔。支持规格:
* jXX - JPEG compression quality, XX is in range [1,100(best)]
* tSCHEME - TIFF compression scheme: none|lzw|rle|deflate|jpeg|g4
* Supported file formats
* Input: BMP, JPG, PNG, JNG, GIF, DDS, TGA, PBM, PGM, PPM, PAM, PFM, TIF, PSD
* Output: BMP, JPG, PNG, JNG, GIF, DDS, TGA, PGM, PPM, PAM, PFM, TIF, PSD
*
* @return 调用结果
*/
public
static
String
deskew
(
String
src
,
String
dst
,
String
deskewUrl
,
boolean
deskewAngle
)
{
StringBuilder
str
=
new
StringBuilder
();
// 命令行位置
str
.
append
(
deskewUrl
).
append
(
" "
);
// 背景颜色
str
.
append
(
"-b"
).
append
(
" "
).
append
(
"FFFFFF"
).
append
(
" "
);
if
(
deskewAngle
)
{
// 图片旋转耗时很多,跳过图片旋转
str
.
append
(
"-l"
).
append
(
" "
).
append
(
"80"
).
append
(
" "
);
}
// 输出路径
str
.
append
(
"-o"
).
append
(
" "
).
append
(
dst
).
append
(
" "
);
// 输入路径
str
.
append
(
src
);
return
RuntimeUtil
.
execForStr
(
str
.
toString
());
}
public
static
String
deskew2
(
String
src
,
String
dst
,
String
deskewpyUrl
)
{
StringBuilder
str
=
new
StringBuilder
();
// python启动
str
.
append
(
"python"
).
append
(
" "
);
// python文件位置
str
.
append
(
deskewpyUrl
).
append
(
" "
);
// 输入文件
str
.
append
(
"-i"
).
append
(
" "
).
append
(
src
).
append
(
" "
);
// 输出文件
str
.
append
(
"-o"
).
append
(
" "
).
append
(
dst
).
append
(
" "
);
return
RuntimeUtil
.
execForStr
(
str
.
toString
());
}
public
static
void
main
(
String
[]
args
)
{
String
path1
=
"C:\\Users\\11419\\Desktop\\Deskew\\TestImages\\3.png"
;
String
path2
=
"C:\\Users\\11419\\Desktop\\Deskew\\TestImages\\res3.png"
;
String
du
=
"C:\\Users\\11419\\Desktop\\lib\\correct.py"
;
String
res
=
deskew2
(
path1
,
path2
,
du
);
System
.
out
.
println
(
res
);
}
}
imgprocessing-server/src/main/java/com/zq/processing/utils/ImageUtil.java
deleted
100644 → 0
View file @
ee256af6
package
com
.
zq
.
processing
.
utils
;
import
cn.hutool.core.convert.Convert
;
import
com.drew.imaging.ImageMetadataReader
;
import
com.drew.metadata.Directory
;
import
com.drew.metadata.Metadata
;
import
com.drew.metadata.Tag
;
import
lombok.Cleanup
;
import
org.apache.commons.imaging.ImageInfo
;
import
org.apache.commons.imaging.Imaging
;
import
org.bytedeco.opencv.opencv_core.Mat
;
import
org.bytedeco.opencv.opencv_core.Scalar
;
import
org.bytedeco.opencv.opencv_core.Size
;
import
org.bytedeco.opencv.opencv_core.UMat
;
import
java.io.File
;
import
static
org
.
bytedeco
.
opencv
.
global
.
opencv_core
.*;
import
static
org
.
bytedeco
.
opencv
.
global
.
opencv_imgcodecs
.*;
import
static
org
.
bytedeco
.
opencv
.
global
.
opencv_imgproc
.*;
public
class
ImageUtil
{
/**
* 图片灰度化
*
* @param mat 图片地址
*/
public
static
Mat
gray
(
Mat
mat
)
{
// 灰度化
Mat
gray
=
mat
.
clone
();
if
(
mat
.
channels
()
==
4
||
mat
.
channels
()
==
3
)
{
cvtColor
(
mat
,
gray
,
COLOR_BGR2GRAY
);
}
else
if
(
mat
.
channels
()
==
2
)
{
cvtColor
(
mat
,
gray
,
COLOR_BGR5652GRAY
);
}
else
{
gray
=
mat
;
}
return
gray
;
}
/**
* 图片灰度化
*
* @param mat 图片地址
*/
public
static
UMat
gray
(
UMat
mat
)
{
// 灰度化
UMat
gray
=
mat
.
clone
();
if
(
mat
.
channels
()
==
4
||
mat
.
channels
()
==
3
)
{
cvtColor
(
mat
,
gray
,
COLOR_BGR2GRAY
);
}
else
if
(
mat
.
channels
()
==
2
)
{
cvtColor
(
mat
,
gray
,
COLOR_BGR5652GRAY
);
}
else
{
gray
=
mat
;
}
return
gray
;
}
/**
* 图片灰度化
*
* @param src 图片地址
* @param dst 灰度化图片保存地址
*/
public
static
void
gray
(
String
src
,
String
dst
)
{
@Cleanup
Mat
mat
=
imread
(
src
);
// 灰度化
@Cleanup
Mat
gray
=
gray
(
mat
);
imwrite
(
dst
,
gray
);
}
/**
* 边缘检测
*
* @param src 图片地址
* @param dst 边缘检测图片保存地址
*/
public
static
void
canny
(
String
src
,
String
dst
)
{
@Cleanup
Mat
mat
=
imread
(
src
);
@Cleanup
Mat
cannyImg
=
canny
(
mat
);
imwrite
(
dst
,
cannyImg
);
}
/**
* 灰度化图片后进行canny边缘检测
*
* @param src 图片矩阵
* @return 边缘检测之后的图片矩阵
*/
private
static
Mat
canny
(
Mat
src
)
{
// 灰度化
@Cleanup
Mat
gray
=
gray
(
src
);
@Cleanup
Mat
mat
=
gray
.
clone
();
Canny
(
gray
,
mat
,
60
,
200
);
return
mat
;
}
/**
* 获取图片DPI
*
* @param file 图片文件
* @return [水平DPI,垂直DPI]
*/
public
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
;
}
/**
* 计算图片清晰度
*
* @param path 图片路径
* @return 图片清晰度
*/
public
static
double
imageSharpnessDetector
(
String
path
)
{
@Cleanup
Mat
image
=
imread
(
path
);
@Cleanup
Mat
grayImage
=
gray
(
image
);
// 计算拉普拉斯
@Cleanup
Mat
laplacian
=
new
Mat
();
Laplacian
(
grayImage
,
laplacian
,
CV_64F
);
// 计算清晰度
@Cleanup
Mat
mean
=
new
Mat
();
@Cleanup
Mat
stdDev
=
new
Mat
();
meanStdDev
(
laplacian
,
mean
,
stdDev
);
return
Math
.
pow
(
stdDev
.
createIndexer
().
getDouble
(
0
,
0
,
0
),
2.0
);
}
/**
* 计算图片平均亮度
*
* @param path 图片路径
* @return 图片平均亮度
*/
public
static
double
brightness
(
String
path
)
{
@Cleanup
Mat
grayImage
=
imread
(
path
,
IMREAD_GRAYSCALE
);
// 计算图像的平均亮度
Scalar
mean
=
mean
(
grayImage
);
return
mean
.
get
(
0
);
}
/**
* 执行高斯模糊, UnsharpMasking
*
* @param path 图片路径
* @return 执行后的结果
*/
public
static
Mat
unsharpMasking
(
String
path
)
{
@Cleanup
Mat
image
=
imread
(
path
);
// 高斯模糊
Mat
blurred
=
new
Mat
();
GaussianBlur
(
image
,
blurred
,
new
Size
(
3
,
3
),
0
);
// UnsharpMasking
@Cleanup
Mat
unsharpMasked
=
new
Mat
();
addWeighted
(
image
,
1.5
,
blurred
,
-
0.5
,
0
,
unsharpMasked
);
return
unsharpMasked
;
}
}
imgprocessing-server/src/main/java/com/zq/processing/utils/RemoveBlackUtil.java
deleted
100644 → 0
View file @
ee256af6
package
com
.
zq
.
processing
.
utils
;
import
lombok.Cleanup
;
import
org.bytedeco.javacpp.indexer.UByteIndexer
;
import
org.bytedeco.opencv.opencv_core.Mat
;
import
org.bytedeco.opencv.opencv_core.Rect
;
import
static
org
.
bytedeco
.
opencv
.
global
.
opencv_imgcodecs
.
imread
;
import
static
org
.
bytedeco
.
opencv
.
global
.
opencv_imgcodecs
.
imwrite
;
import
static
org
.
bytedeco
.
opencv
.
global
.
opencv_imgproc
.
COLOR_BGR2GRAY
;
import
static
org
.
bytedeco
.
opencv
.
global
.
opencv_imgproc
.
cvtColor
;
/**
* 去除黑边
*/
public
class
RemoveBlackUtil
{
/**
* 去黑边"全黑"阈值
*/
private
static
final
Integer
BLACK_VALUE
=
40
;
/**
* 去除黑边
*
* @param src 原图片路径
* @param dst 处理后图片路径
*/
public
static
void
remove
(
String
src
,
String
dst
)
{
@Cleanup
Mat
mat
=
imread
(
src
);
Mat
res
=
removeBlackEdge
(
mat
);
imwrite
(
dst
,
res
);
}
/**
* 去除图片黑边,若无黑边,则原图返回。默认“全黑”阈值为 {@code BLACK_VALUE}
*
* @param srcMat 预去除黑边的Mat
* @return 去除黑边之后的Mat
*/
private
static
Mat
removeBlackEdge
(
Mat
srcMat
)
{
return
removeBlackEdge
(
srcMat
,
BLACK_VALUE
);
}
private
static
Mat
removeBlackEdge
(
Mat
srcMat
,
int
blackValue
)
{
// 图片进行灰度化
@Cleanup
Mat
grayMat
=
srcMat
.
clone
();
cvtColor
(
srcMat
,
grayMat
,
COLOR_BGR2GRAY
);
// 定义边界
int
topRow
=
0
;
int
leftCol
=
0
;
int
rightCol
=
grayMat
.
cols
()
-
1
;
int
bottomRow
=
grayMat
.
rows
()
-
1
;
UByteIndexer
indexer
=
grayMat
.
createIndexer
();
// 上方黑边判断
for
(
int
row
=
0
;
row
<
grayMat
.
rows
();
row
++)
{
if
(
sum
(
grayMat
.
row
(
row
),
indexer
)
/
grayMat
.
cols
()
<
blackValue
)
{
topRow
=
row
;
}
else
{
break
;
}
}
// 左边黑边判断
for
(
int
col
=
0
;
col
<
grayMat
.
cols
();
col
++)
{
if
(
sum
(
grayMat
.
col
(
col
),
indexer
)
/
grayMat
.
rows
()
<
blackValue
)
{
leftCol
=
col
;
}
else
{
break
;
}
}
// 右边黑边判断
for
(
int
col
=
grayMat
.
cols
()
-
1
;
col
>
0
;
col
--)
{
if
(
sum
(
grayMat
.
col
(
col
),
indexer
)
/
grayMat
.
rows
()
<
blackValue
)
{
rightCol
=
col
;
}
else
{
break
;
}
}
// 下方黑边判断
for
(
int
row
=
grayMat
.
rows
()
-
1
;
row
>
0
;
row
--)
{
if
(
sum
(
grayMat
.
row
(
row
),
indexer
)
/
grayMat
.
cols
()
<
blackValue
)
{
bottomRow
=
row
;
}
else
{
break
;
}
}
int
x
=
leftCol
;
int
y
=
topRow
;
int
width
=
rightCol
-
leftCol
;
int
height
=
bottomRow
-
topRow
;
if
(
leftCol
==
0
&&
rightCol
==
grayMat
.
cols
()
-
1
&&
topRow
==
0
&&
bottomRow
==
grayMat
.
rows
()
-
1
)
{
return
srcMat
;
}
return
cut
(
srcMat
,
x
,
y
,
width
,
height
);
}
/**
* 按照指定的尺寸截取Mat,坐标原点为左上角
*
* @param src 源Mat
* @param x x
* @param y y
* @param width width
* @param height height
* @return 截取后的Mat
*/
private
static
Mat
cut
(
Mat
src
,
int
x
,
int
y
,
int
width
,
int
height
)
{
if
(
x
<
0
)
{
x
=
0
;
}
if
(
y
<
0
)
{
y
=
0
;
}
if
(
width
>
src
.
cols
())
{
width
=
src
.
cols
();
}
if
(
height
>
src
.
rows
())
{
height
=
src
.
rows
();
}
// 截取尺寸
Rect
rect
=
new
Rect
(
x
,
y
,
width
,
height
);
return
new
Mat
(
src
,
rect
);
}
/**
* 像素求和
*
* @param mat mat
* @return sum
*/
private
static
int
sum
(
Mat
mat
,
UByteIndexer
indexer
)
{
int
sum
=
0
;
for
(
int
row
=
0
;
row
<
mat
.
rows
();
row
++)
{
for
(
int
col
=
0
;
col
<
mat
.
cols
();
col
++)
{
sum
+=
indexer
.
get
(
row
,
col
,
0
);
}
}
return
sum
;
}
/**
* 黑边检测
*
* @param src 图片矩阵
* @return true表示可能存在黑边
*/
public
static
boolean
blackDetection
(
Mat
src
)
{
// 图片进行灰度化
@Cleanup
Mat
grayMat
=
src
.
clone
();
cvtColor
(
src
,
grayMat
,
COLOR_BGR2GRAY
);
int
width
=
src
.
cols
();
int
height
=
src
.
rows
();
// 定义初始边界
int
top
=
0
;
int
left
=
0
;
int
right
=
width
-
1
;
int
bottom
=
height
-
1
;
UByteIndexer
indexer
=
grayMat
.
createIndexer
();
// 上方黑边判断
for
(
int
row
=
0
;
row
<
height
;
row
++)
{
if
(
sum
(
grayMat
.
row
(
row
),
indexer
)
/
width
<
BLACK_VALUE
)
{
top
=
row
;
}
else
{
break
;
}
}
// 左边黑边判断
for
(
int
col
=
0
;
col
<
width
;
col
++)
{
if
(
sum
(
grayMat
.
col
(
col
),
indexer
)
/
height
<
BLACK_VALUE
)
{
left
=
col
;
}
else
{
break
;
}
}
// 右边黑边判断
for
(
int
col
=
width
-
1
;
col
>
0
;
col
--)
{
if
(
sum
(
grayMat
.
col
(
col
),
indexer
)
/
height
<
BLACK_VALUE
)
{
right
=
col
;
}
else
{
break
;
}
}
// 下方黑边判断
for
(
int
row
=
height
-
1
;
row
>
0
;
row
--)
{
if
(
sum
(
grayMat
.
row
(
row
),
indexer
)
/
width
<
BLACK_VALUE
)
{
bottom
=
row
;
}
else
{
break
;
}
}
// 若是边界没有被更新,认为不存在黑边
return
top
!=
0
||
left
!=
0
||
right
!=
width
-
1
||
bottom
!=
height
-
1
;
}
}
imgprocessing-server/src/main/java/com/zq/processing/utils/TestUtil.java
deleted
100644 → 0
View file @
ee256af6
package
com
.
zq
.
processing
.
utils
;
public
class
TestUtil
{
public
static
void
main
(
String
[]
args
)
{
String
testImg
=
"C:/Users/11419/Desktop/Deskew/TestImages/6.png"
;
String
resImg
=
"C:/Users/11419/Desktop/Deskew/TestImages/res.png"
;
}
}
imgprocessing-server/src/main/java/com/zq/processing/utils/UploadUtils.java
deleted
100644 → 0
View file @
ee256af6
package
com
.
zq
.
processing
.
utils
;
import
cn.hutool.core.io.FileUtil
;
import
com.zq.processing.exception.BusinessException
;
import
lombok.extern.slf4j.Slf4j
;
import
org.springframework.web.multipart.MultipartFile
;
import
java.io.File
;
import
java.io.IOException
;
import
java.text.SimpleDateFormat
;
import
java.util.Date
;
import
java.util.Objects
;
/**
* @author wilmiam
* @since 2021-07-09 18:05
*/
@Slf4j
public
class
UploadUtils
{
private
final
static
String
DATE_FORMAT
=
"/yyyyMM/dd/"
;
/**
* 保存临时文件
*
* @return 保存地址
*/
public
static
String
saveTempFile
(
MultipartFile
file
,
String
systemName
)
{
try
{
String
originalFilename
=
file
.
getOriginalFilename
();
//取文件扩展名
String
ext
=
Objects
.
requireNonNull
(
originalFilename
).
substring
(
originalFilename
.
lastIndexOf
(
"."
)
+
1
);
//生成新文件名
String
name
=
UuidUtils
.
uuidNoDash
()
+
"."
+
ext
;
String
yyyyMMdd
=
new
SimpleDateFormat
(
DATE_FORMAT
).
format
(
new
Date
());
File
dest
=
new
File
(
"/data/temp/"
+
systemName
+
yyyyMMdd
+
name
);
FileUtil
.
writeBytes
(
file
.
getBytes
(),
dest
);
return
"/data/temp/"
+
systemName
+
yyyyMMdd
+
name
;
}
catch
(
IOException
e
)
{
log
.
error
(
"文件保存失败:{}"
,
e
.
getMessage
());
throw
new
BusinessException
(
"文件保存失败"
);
}
}
}
imgprocessing-server/src/main/java/com/zq/processing/utils/ValidateUtil.java
deleted
100644 → 0
View file @
ee256af6
package
com
.
zq
.
processing
.
utils
;
import
java.math.BigDecimal
;
import
java.math.BigInteger
;
import
java.util.Arrays
;
import
java.util.Collection
;
import
java.util.regex.Matcher
;
import
java.util.regex.Pattern
;
/**
* 验证工具类
*
* @author wilmiam
* @since 2021-07-09 18:05
*/
public
class
ValidateUtil
{
/**
* URL验证正则表达式
*/
private
static
final
String
URL_REGEX
=
"^(http|https)\\://([a-zA-Z0-9\\.\\-]+(\\:[a-zA-Z0-9\\.&%\\$\\-]+)*@)*((25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9])\\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[0-9])|localhost|([a-zA-Z0-9\\-]+\\.)*[a-zA-Z0-9\\-]+\\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{1,10}))(\\:[0-9]+)*(/($|[a-zA-Z0-9\\.\\,\\?\\'\\\\\\+&%\\$#\\=~_\\-]+))*$"
;
/**
* 安全SQL验证正则表达式
*/
private
static
final
String
SAFE_SQL_REGEX
=
"[-|;|,|\\/|\\(|\\)|\\[|\\]|\\}|\\{|%|@|\\*|!|\\']"
;
/**
* 身份证验证加权因子数组,将前17位加权因子保存在数组里
*/
private
static
final
int
[]
IDCARD_WI
=
new
int
[]{
7
,
9
,
10
,
5
,
8
,
4
,
2
,
1
,
6
,
3
,
7
,
9
,
10
,
5
,
8
,
4
,
2
};
/**
* 身份证验证校验码数组,这是除以11后,可能产生的11位余数、验证码,也保存
*/
private
static
final
int
[]
IDCARD_Y
=
new
int
[]{
1
,
0
,
10
,
9
,
8
,
7
,
6
,
5
,
4
,
3
,
2
};
/**
* 身份证验证,最后一位为X时,校验码所在数组的位置
*/
private
static
final
int
IDCARD_MOD_X
=
2
;
/**
* 身份证验证,当校验码为10时,最后一位必须是X
*/
private
static
final
String
IDCARD_LAST_X
=
"X"
;
/**
* 身份证校验正则表达式
*/
private
static
final
String
IDCARD_REGEX
=
"^[1-9]\\d{7}((0\\d)|(1[0-2]))(([0|1|2]\\d)|3[0-1])\\d{3}$|^[1-9]\\d{5}[1-9]\\d{3}((0\\d)|(1[0-2]))(([0|1|2]\\d)|3[0-1])\\d{3}([0-9]|X)$"
;
/**
* 身份证号码长度
*/
private
static
final
int
IDCARD_LENG
=
18
;
/**
* 单个IP的正则表达式
*/
private
static
final
String
SINGLE_IP_REGEX
=
"([1-9]|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])(\\.(\\d|[1-9]\\d|1\\d{2}|2[0-4]\\d|25[0-5])){3}"
;
/**
* 以逗号分隔的多个IP的正则表达式
*/
private
static
final
String
IP_REGEX
=
SINGLE_IP_REGEX
+
"(,"
+
SINGLE_IP_REGEX
+
")*"
;
private
static
final
Class
<?>[]
NUMBER_TYPES
=
{
Byte
.
class
,
Short
.
class
,
Integer
.
class
,
Long
.
class
,
BigInteger
.
class
,
Float
.
class
,
Double
.
class
,
BigDecimal
.
class
};
/**
* 判断对象是否为空
*
* @param obj 要判断的对象
* @return 判断结果
*/
public
static
boolean
isBlank
(
final
Object
obj
)
{
if
(
obj
==
null
)
{
return
true
;
}
if
(
obj
instanceof
String
)
{
return
""
.
equals
(((
String
)
obj
).
trim
());
}
if
(
obj
instanceof
Collection
)
{
final
Collection
collection
=
(
Collection
)
obj
;
return
collection
.
isEmpty
();
}
return
false
;
}
/**
* 判断对象是否不为空
*
* @param obj
* @return
*/
public
static
boolean
isNotBlank
(
final
Object
obj
)
{
return
!
isBlank
(
obj
);
}
/**
* 检查参数是否有空
*
* @param objects 待验证对象,可以是多个
* @return 验证结果
*/
public
static
boolean
hasBlank
(
final
Object
...
objects
)
{
for
(
final
Object
obj
:
objects
)
{
if
(
isBlank
(
obj
))
{
return
true
;
}
}
return
false
;
}
/**
* 判断是否是Base64字符串
*
* @param str Base64字符串
* @return 判断结果
*/
public
static
boolean
isBase64
(
final
String
str
)
{
return
isMatch
(
str
,
"[A-Za-z0-9\\+\\/\\=]"
);
}
/**
* 判断是否是Email地址字符串
*
* @param strEmail URL地址字符串
* @return 判断结果
*/
public
static
boolean
isEmail
(
final
String
strEmail
)
{
return
isMatch
(
strEmail
,
"^[\\w-]+(\\.[\\w-]+)*@[\\w-]+(\\.[\\w-]+)+$"
);
}
/**
* 验证手机号码正确性
*
* @param strMobilePhone 手机号码
* @return
*/
public
static
boolean
isMobilePhoneNo
(
final
String
strMobilePhone
)
{
return
isMatch
(
strMobilePhone
,
"^0?(13[0-9]|14[5-9]|15[012356789]|166|17[0-8]|18[0-9]|19[8-9])[0-9]{8}$"
);
}
/**
* 是否为手机号码
*
* @param phone
* @return
*/
public
static
boolean
isInternationalPhoneNo
(
final
String
phone
)
{
return
isMatch
(
phone
,
"^[0-9]{4,14}$"
);
}
/**
* 判断是否为正整数字符串
*
* @param intStr 数字字符串
* @return 判断结果
*/
public
static
boolean
isInt
(
final
String
intStr
)
{
return
isMatch
(
intStr
,
"^[0-9]*$"
);
}
/**
* 判断是否为IP字符串
*
* @param ipStr ip字符串
* @return 判断结果
*/
public
static
boolean
isIp
(
final
String
ipStr
)
{
return
isMatch
(
ipStr
,
"^((2[0-4]\\d|25[0-5]|[01]?\\d\\d?)\\.){3}(2[0-4]\\d|25[0-5]|[01]?\\d\\d?)$"
);
}
/**
* 判断字符串是否和正则表达式相匹配,大小写敏感
*
* @param str 字符串
* @param regEx 正则表达式
* @return 判断结果
*/
public
static
boolean
isMatch
(
final
String
str
,
final
String
regEx
)
{
return
isMatch
(
str
,
regEx
,
false
);
}
/**
* 判断字符串是否和正则表达式相匹配
*
* @param str 字符串
* @param regEx 正则表达式
* @param caseInsensetive 是否不区分大小写, true为不区分, false为区分
* @return 判断结果
*/
public
static
boolean
isMatch
(
final
String
str
,
final
String
regEx
,
final
boolean
caseInsensetive
)
{
if
(
ValidateUtil
.
isNotBlank
(
str
)
&&
ValidateUtil
.
isNotBlank
(
regEx
))
{
Pattern
pattern
;
if
(
caseInsensetive
)
{
pattern
=
Pattern
.
compile
(
regEx
,
Pattern
.
CASE_INSENSITIVE
);
}
else
{
pattern
=
Pattern
.
compile
(
regEx
);
}
final
Matcher
matcher
=
pattern
.
matcher
(
str
);
return
matcher
.
find
();
}
return
false
;
}
/**
* 判断是否是数字
*
* @param strNumber 数字字符串
* @return 判断结果
*/
public
static
boolean
isNumber
(
final
String
strNumber
)
{
return
isMatch
(
strNumber
,
"^\\d+$"
);
}
/**
* 判断是否是Sql危险字符
*
* @param sqlStr sql字符串
* @return 判断结果
*/
public
static
boolean
isSafeSqlString
(
final
String
sqlStr
)
{
return
isMatch
(
sqlStr
,
SAFE_SQL_REGEX
);
}
/**
* 判断是否是URL地址字符串
*
* @param strUrl URL地址字符串
* @return 判断结果
*/
public
static
boolean
isURL
(
final
String
strUrl
)
{
return
isMatch
(
strUrl
,
URL_REGEX
);
}
/**
* 检查对象类型是不是Number类型
*
* @param type
* @return
*/
public
static
boolean
isNumber
(
Class
<?>
type
)
{
// Class为final类,其equals不能重写,而Class的equals方法是直接继承自Object类
// return (this == obj)
// 所以在比较Class是否相等时,使用equals和使用==是一样的
return
Arrays
.
stream
(
NUMBER_TYPES
).
anyMatch
(
numberType
->
numberType
.
equals
(
type
));
}
/**
* 身份证15位编码规则:dddddd yymmdd xx p
* dddddd:6位地区编码
* yymmdd: 出生年(两位年)月日,如:910215
* xx: 顺序编码,系统产生,无法确定
* p: 性别,奇数为男,偶数为女
* <p>
* 身份证18位编码规则:dddddd yyyymmdd xxx y
* dddddd:6位地区编码
* yyyymmdd: 出生年(四位年)月日,如:19910215
* xxx:顺序编码,系统产生,无法确定,奇数为男,偶数为女
* y: 校验码,该位数值可通过前17位计算获得
* <p>
* 前17位号码加权因子为 Wi = [ 7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2 ]
* 验证位 Y = [ 1, 0, 10, 9, 8, 7, 6, 5, 4, 3, 2 ]
* 如果验证码恰好是10,为了保证身份证是十八位,那么第十八位将用X来代替
* 校验位计算公式:Y_P = mod( ∑(Ai×Wi),11 )
* i为身份证号码1...17 位; Y_P为校验码Y所在校验码数组位置
*/
public
static
boolean
isIdCardNo
(
final
String
idCardNo
)
{
if
(!
isMatch
(
idCardNo
,
IDCARD_REGEX
))
{
return
false
;
}
if
(
idCardNo
.
length
()
==
IDCARD_LENG
)
{
// 用来保存前17位各自乖以加权因子后的总和
int
idCardWiSum
=
0
;
for
(
int
i
=
0
;
i
<
IDCARD_WI
.
length
;
i
++)
{
idCardWiSum
+=
Integer
.
parseInt
(
idCardNo
.
substring
(
i
,
i
+
1
))
*
IDCARD_WI
[
i
];
}
// 计算出校验码所在数组的位置
int
idCardMod
=
idCardWiSum
%
11
;
// 得到最后一位身份证号码
String
idCardLast
=
idCardNo
.
substring
(
17
);
if
(
idCardMod
==
IDCARD_MOD_X
)
{
return
IDCARD_LAST_X
.
equalsIgnoreCase
(
idCardLast
);
}
else
{
//用计算出的验证码与最后一位身份证号码匹配,如果一致,说明通过,否则是无效的身份证号码
return
String
.
valueOf
(
IDCARD_Y
[
idCardMod
]).
equals
(
idCardLast
);
}
}
return
true
;
}
/**
* 判断字符串是否是以逗号分隔的IP字符串。
* <p>
* 逗号必须是英文半角, 单个ip也认为符合要求, 空字符串返回false
* </p>
*
* @param ipStr
* @return
*/
public
static
boolean
isComaSplitIp
(
String
ipStr
)
{
boolean
result
=
false
;
if
(
ValidateUtil
.
isNotBlank
(
ipStr
))
{
result
=
ipStr
.
matches
(
IP_REGEX
);
}
return
result
;
}
}
imgprocessing-server/src/main/java/com/zq/processing/vo/OptimizationReq.java
deleted
100644 → 0
View file @
ee256af6
package
com
.
zq
.
processing
.
vo
;
import
lombok.Data
;
/**
* <p>
*
* </p>
*
* @author chenhao
* @since 2023/4/20
*/
@Data
public
class
OptimizationReq
{
private
String
fileContent
;
private
String
filename
;
}
imgprocessing-server/src/main/java/com/zq/processing/vo/OptimizationVO.java
deleted
100644 → 0
View file @
ee256af6
package
com
.
zq
.
processing
.
vo
;
import
io.swagger.annotations.ApiModelProperty
;
import
lombok.AllArgsConstructor
;
import
lombok.Builder
;
import
lombok.Data
;
import
lombok.NoArgsConstructor
;
/**
* <p>
* 图片优化结果类
* </p>
*
* @author chenhao
* @since 2023/4/20
*/
@NoArgsConstructor
@AllArgsConstructor
@Data
@Builder
public
class
OptimizationVO
{
@ApiModelProperty
(
"base64图片"
)
private
String
fileContent
;
@ApiModelProperty
(
"文件名称"
)
private
String
filename
;
@ApiModelProperty
(
"是否经过亮度调整"
)
private
boolean
isBrightness
;
@ApiModelProperty
(
"图片原始平均亮度值"
)
private
double
originalBrightnessVal
;
@ApiModelProperty
(
"图片修正后的平均亮度值"
)
private
double
brightnessVal
;
@ApiModelProperty
(
"是否经过清晰度调整"
)
private
boolean
isClarity
;
@ApiModelProperty
(
"图片原始清晰度值"
)
private
double
originalClarityVal
;
@ApiModelProperty
(
"图片清晰度调整过后的清晰度值"
)
private
double
clarityVal
;
@ApiModelProperty
(
"是否经过黑边处理"
)
private
boolean
isRemoveBlack
;
@ApiModelProperty
(
"图片纠偏的角度,纠偏出错返回-1"
)
private
double
deskewAngel
;
@ApiModelProperty
(
"图片是否经过弯曲矫正"
)
private
boolean
isCorrect
;
}
imgprocessing-server/src/main/resources/application.yml
deleted
100644 → 0
View file @
ee256af6
server
:
port
:
9100
spring
:
application
:
name
:
IMGPROCEING-SERVER
jackson
:
date-format
:
yyyy-MM-dd HH:mm:ss
time-zone
:
GMT+8
mvc
:
format
:
date-time
:
yyyy-MM-dd HH:mm:ss
servlet
:
multipart
:
max-file-size
:
100MB
max-request-size
:
150MB
# IP 本地解析
ip
:
local-parsing
:
true
#imgconfig:
# opencv: /opt/tianjin/lib/opencv_java460.so
# deskew: /opt/tianjin/lib/Deskew/Bin/deskew
# deskewpy: /opt/tianjin/lib/correct.py
imgconfig
:
opencv
:
C:/Users/11419/Desktop/lib/opencv_java460.dll
deskew
:
C:/Users/11419/Desktop/Deskew/Bin/deskew.exe
deskewpy
:
C:/Users/11419/Desktop/lib/correct.py
i
mgprocessing
-server/pom.xml
→
i
nterface
-server/pom.xml
View file @
87efa3b4
<?xml version="1.0" encoding="UTF-8"?>
<?xml version="1.0" encoding="UTF-8"?>
<project
xmlns
=
"http://maven.apache.org/POM/4.0.0
"
<project
xmlns
:xsi=
"http://www.w3.org/2001/XMLSchema-instance
"
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"
>
xsi:schemaLocation=
"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
>
<modelVersion>
4.0.0
</modelVersion>
<parent>
<parent>
<groupId>
com.zq
</groupId>
<groupId>
com.zq
</groupId>
<artifactId>
imgproc
</artifactId>
<artifactId>
imgproc
</artifactId>
<version>
1.0.0
</version>
<version>
1.0.0
</version>
</parent>
</parent>
<artifactId>
imgprocessing-server
</artifactId>
<modelVersion>
4.0.0
</modelVersion>
<artifactId>
interface
</artifactId>
<version>
1.0.0
</version>
<name>
interface-server
</name>
<description>
接口服务
</description>
<properties>
<properties>
<java.version>
1.8
</java.version>
<maven.compiler.source>
8
</maven.compiler.source>
<maven.compiler.target>
8
</maven.compiler.target>
<project.build.sourceEncoding>
UTF-8
</project.build.sourceEncoding>
<project.build.sourceEncoding>
UTF-8
</project.build.sourceEncoding>
<project.reporting.outputEncoding>
UTF-8
</project.reporting.outputEncoding>
<spring-boot.version>
2.4.2
</spring-boot.version>
</properties>
</properties>
<dependencies>
<dependencies>
...
@@ -27,48 +30,88 @@
...
@@ -27,48 +30,88 @@
<groupId>
org.springframework.cloud
</groupId>
<groupId>
org.springframework.cloud
</groupId>
<artifactId>
spring-cloud-starter-config
</artifactId>
<artifactId>
spring-cloud-starter-config
</artifactId>
</dependency>
</dependency>
<!-- AOP -->
<dependency>
<groupId>
org.springframework.boot
</groupId>
<artifactId>
spring-boot-starter-aop
</artifactId>
</dependency>
<!--Spring boot Web容器-->
<!--Spring boot Web容器-->
<dependency>
<dependency>
<groupId>
org.springframework.boot
</groupId>
<groupId>
org.springframework.boot
</groupId>
<artifactId>
spring-boot-starter-web
</artifactId>
<artifactId>
spring-boot-starter-web
</artifactId>
</dependency>
</dependency>
<!-- 注解执行器 -->
<dependency>
<dependency>
<groupId>
cn.hutool
</groupId>
<groupId>
org.springframework.boot
</groupId>
<artifactId>
hutool-all
</artifactId>
<artifactId>
spring-boot-configuration-processor
</artifactId>
<
version>
5.8.19
</version
>
<
optional>
true
</optional
>
</dependency>
</dependency>
<!-- redis -->
<dependency>
<dependency>
<groupId>
org.bytedeco
</groupId>
<groupId>
org.springframework.boot
</groupId>
<artifactId>
javacv-platform
</artifactId>
<artifactId>
spring-boot-starter-data-redis
</artifactId>
<version>
1.5.9
</version>
</dependency>
</dependency>
<!-- 搭配redis的对象池依赖 -->
<dependency>
<dependency>
<groupId>
org.
bytedeco
</groupId>
<groupId>
org.
apache.commons
</groupId>
<artifactId>
opencv-platform
</artifactId>
<artifactId>
commons-pool2
</artifactId>
<version>
4.7.0-1.5.9
</version>
<version>
${commons-pool2.version}
</version>
</dependency>
</dependency>
<!-- druid数据源驱动 -->
<!-- Additional dependencies required to use CUDA and cuDNN -->
<dependency>
<dependency>
<groupId>
org.bytedeco
</groupId>
<groupId>
com.alibaba
</groupId>
<artifactId>
opencv-platform-gpu
</artifactId>
<artifactId>
druid-spring-boot-starter
</artifactId>
<version>
4.7.0-1.5.9
</version>
<version>
1.2.6
</version>
</dependency>
</dependency>
<!--mybatis-plus-->
<dependency>
<dependency>
<groupId>
com.
drewnoakes
</groupId>
<groupId>
com.
baomidou
</groupId>
<artifactId>
m
etadata-extracto
r
</artifactId>
<artifactId>
m
ybatis-plus-boot-starte
r
</artifactId>
<version>
2.18.0
</version>
<version>
3.5.3.1
</version>
</dependency>
</dependency>
<!-- mysql -->
<dependency>
<dependency>
<groupId>
org.apache.commons
</groupId>
<groupId>
mysql
</groupId>
<artifactId>
commons-imaging
</artifactId>
<artifactId>
mysql-connector-java
</artifactId>
<
version>
1.0-alpha3
</version
>
<
scope>
runtime
</scope
>
</dependency>
</dependency>
<!-- swageer -->
<dependency>
<dependency>
<groupId>
org.projectlombok
</groupId>
<groupId>
io.springfox
</groupId>
<artifactId>
lombok
</artifactId>
<artifactId>
springfox-swagger2
</artifactId>
<version>
1.18.28
</version>
<version>
2.9.2
</version>
</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.22
</version>
</dependency>
<dependency>
<groupId>
io.springfox
</groupId>
<artifactId>
springfox-swagger-ui
</artifactId>
<version>
2.9.2
</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>
</dependencies>
</dependencies>
...
...
i
mgprocessing-server/src/main/java/com/zq/processing/Imgprocessing
Application.java
→
i
nterface-server/src/main/java/com/zq/im/Interface
Application.java
View file @
87efa3b4
package
com
.
zq
.
processing
;
package
com
.
zq
.
im
;
import
org.springframework.boot.SpringApplication
;
import
org.springframework.boot.SpringApplication
;
import
org.springframework.boot.autoconfigure.SpringBootApplication
;
import
org.springframework.boot.autoconfigure.SpringBootApplication
;
import
org.springframework.cloud.client.discovery.EnableDiscoveryClient
;
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
@EnableDiscoveryClient
@SpringBootApplication
@SpringBootApplication
public
class
I
mgprocessing
Application
{
public
class
I
nterface
Application
{
public
static
void
main
(
String
[]
args
)
{
public
static
void
main
(
String
[]
args
)
{
SpringApplication
.
run
(
I
mgprocessing
Application
.
class
,
args
);
SpringApplication
.
run
(
I
nterface
Application
.
class
,
args
);
}
}
}
}
interface-server/src/main/java/com/zq/im/annotation/ApiLog.java
0 → 100644
View file @
87efa3b4
package
com
.
zq
.
im
.
annotation
;
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
ApiLog
{
String
value
()
default
""
;
boolean
save
()
default
false
;
}
\ No newline at end of file
interface-server/src/main/java/com/zq/im/annotation/rest/ApiLogAspect.java
0 → 100644
View file @
87efa3b4
package
com
.
zq
.
im
.
annotation
.
rest
;
import
org.aspectj.lang.annotation.Aspect
;
import
org.springframework.core.annotation.Order
;
import
org.springframework.stereotype.Component
;
/**
* <p>
* 用户操作日志处理切面
* </p>
*
* @author chenhao
* @since 2022/10/15 17:23
*/
@Order
(
1
)
@Aspect
@Component
public
class
ApiLogAspect
{
// private static final Logger LOGGER = LoggerFactory.getLogger(com.zq.im.annotation.ApiLog.class);
// private final LogServiceImpl logService;
//
// @Autowired
// public ApiLogAspect(LogServiceImpl logService) {
// this.logService = logService;
// }
//
// /**
// * 声明切入点
// */
// @Pointcut("@annotation(com.zq.im.annotation.ApiLog)")
// public void pointCut() {}
//
// /**
// * 环绕通知
// */
// @Around("pointCut()")
// public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
// // 记录接口调用时间
// long startTime = System.currentTimeMillis();
// Object result = joinPoint.proceed();
// long endTime = System.currentTimeMillis();
// // 获取方法
// Signature signature = joinPoint.getSignature();
// MethodSignature methodSignature = (MethodSignature) signature;
// Method method = methodSignature.getMethod();
// // 获取Log注解的信息,查看是否需要保存操作记录
// boolean isSave = false;
// String value = "";
// if (method.isAnnotationPresent(com.yww.admin.annotation.Log.class)) {
// com.yww.admin.annotation.Log annotation = method.getAnnotation(com.yww.admin.annotation.Log.class);
// isSave = annotation.save();
// value = annotation.value();
// }
// // 打印注解上的消息
// if (StrUtil.isNotBlank(value)) {
// LOGGER.info(value);
// }
// // 若是选择保存到数据库,则获取信息后保存
// if (!isSave) {
// return result;
// }
// // 获取当前请求对象
// ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
// if (attributes == null) {
// return result;
// }
// HttpServletRequest request = attributes.getRequest();
// //记录请求信息
// Log.LogBuilder builder = Log.builder();
// // 记录Operation的注解信息
// if (method.isAnnotationPresent(Operation.class)) {
// Operation operation = method.getAnnotation(Operation.class);
// builder.summary(operation.summary()).description(operation.description());
// }
// String urlStr = request.getRequestURL().toString();
// builder.basePath(StrUtil.removeSuffix(urlStr, URLUtil.url(urlStr).getPath()))
// .uri(request.getRequestURI())
// .url(request.getRequestURL().toString())
// .browser(IpUtil.getBrowser(request).getBrowser().getName())
// .ip(IpUtil.getLongIpAddr(request))
// .method(request.getMethod())
// .parameter(JSONUtil.parse(getParameter(method, joinPoint.getArgs())).toString())
// .result(JSONUtil.parse(result).toString())
// .spendTime((int) (endTime - startTime))
// .startTime(LocalDateTimeUtil.of(startTime));
// Log log = builder.build();
// LOGGER.info(log.toString());
// logService.save(log);
// return result;
// }
//
// /**
// * 根据方法和传入的参数获取请求参数
// *
// * @param method 方法
// * @param args 传入参数
// * @return 请求参数
// */
// private Object getParameter(Method method, Object[] args) {
// List<Object> argsList = new ArrayList<>();
// Parameter[] parameters = method.getParameters();
// for (int i = 0; i < parameters.length; i++) {
// // 将RequestBody注解修饰的参数作为请求参数
// RequestBody requestBody = parameters[i].getAnnotation(RequestBody.class);
// if (requestBody != null) {
// argsList.add(args[i]);
// }
// // 将RequestParam注解修饰的参数作为请求参数
// RequestParam requestParam = parameters[i].getAnnotation(RequestParam.class);
// if (requestParam != null) {
// Map<String, Object> map = new HashMap<>(16);
// String key = parameters[i].getName();
// if (StrUtil.isNotBlank(requestParam.value())) {
// key = requestParam.value();
// }
// map.put(key, args[i]);
// argsList.add(map);
// }
// }
// if (argsList.size() == 0) {
// return null;
// } else if (argsList.size() == 1) {
// return argsList.get(0);
// } else {
// return argsList;
// }
// }
}
interface-server/src/main/java/com/zq/im/config/RedisConfig.java
0 → 100644
View file @
87efa3b4
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
interface-server/src/main/java/com/zq/im/config/WebMvcConfig.java
0 → 100644
View file @
87efa3b4
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/**"
);
}
}
interface-server/src/main/java/com/zq/im/config/feign/FeignConfig.java
0 → 100644
View file @
87efa3b4
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
));
}
}
interface-server/src/main/java/com/zq/im/config/feign/FeignSpringFormEncoder.java
0 → 100644
View file @
87efa3b4
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
);
}
}
interface-server/src/main/java/com/zq/im/config/mybatis/InsertBatchSqlInjector.java
0 → 100644
View file @
87efa3b4
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
interface-server/src/main/java/com/zq/im/config/mybatis/MyMetaObjectHandler.java
0 → 100644
View file @
87efa3b4
package
com
.
zq
.
im
.
config
.
mybatis
;
import
com.baomidou.mybatisplus.core.handlers.MetaObjectHandler
;
import
org.apache.ibatis.reflection.MetaObject
;
import
org.springframework.stereotype.Component
;
import
java.time.LocalDateTime
;
/**
* <p>
* Mybatis-plus的自动填充处理处理
* 注意是对象属性名不是表的字段名
* </p>
*
* @author chenhao
* @since 2022/10/12 20:57
*/
@Component
public
class
MyMetaObjectHandler
implements
MetaObjectHandler
{
/**
* 插入数据时填充创建和修改时间
*/
@Override
public
void
insertFill
(
MetaObject
metaObject
)
{
this
.
strictInsertFill
(
metaObject
,
"createTime"
,
LocalDateTime
.
class
,
LocalDateTime
.
now
());
this
.
strictInsertFill
(
metaObject
,
"updateTime"
,
LocalDateTime
.
class
,
LocalDateTime
.
now
());
}
/**
* 修改数据时更新修改时间
*/
@Override
public
void
updateFill
(
MetaObject
metaObject
)
{
this
.
strictInsertFill
(
metaObject
,
"updateTime"
,
LocalDateTime
.
class
,
LocalDateTime
.
now
());
}
}
interface-server/src/main/java/com/zq/im/config/mybatis/MybatisPlusConfig.java
0 → 100644
View file @
87efa3b4
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
();
}
}
interface-server/src/main/java/com/zq/im/constant/ApiCodeEnum.java
0 → 100644
View file @
87efa3b4
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
;
}
}
interface-server/src/main/java/com/zq/im/constant/ApiVersionEnum.java
0 → 100644
View file @
87efa3b4
package
com
.
zq
.
im
.
constant
;
/**
* API版本
*
* @author wilmiam
* @since 2021-07-14 11:37
*/
public
enum
ApiVersionEnum
{
/**
* V1
*/
v1
,
/**
* V2
*/
v2
}
interface-server/src/main/java/com/zq/im/constant/TokenConstant.java
0 → 100644
View file @
87efa3b4
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"
;
}
i
mgprocessing-server/src/main/java/com/zq/processing
/exception/BusinessException.java
→
i
nterface-server/src/main/java/com/zq/im
/exception/BusinessException.java
View file @
87efa3b4
package
com
.
zq
.
processing
.
exception
;
package
com
.
zq
.
im
.
exception
;
/**
/**
* 业务错误
* 业务错误
*
*
* @author
wilmiam
* @author
chenhao
* @since 202
1-07-09 17:58
* @since 202
2/10/12 20:57
*/
*/
public
class
BusinessException
extends
RuntimeException
{
public
class
BusinessException
extends
RuntimeException
{
...
...
interface-server/src/main/java/com/zq/im/interceptor/ApiMethodInterceptor.java
0 → 100644
View file @
87efa3b4
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
;
}
}
interface-server/src/main/java/com/zq/im/modules/api/controller/ApiV1.java
0 → 100644
View file @
87efa3b4
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
);
}
}
interface-server/src/main/java/com/zq/im/modules/api/form/ApiForm.java
0 → 100644
View file @
87efa3b4
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
;
}
}
interface-server/src/main/java/com/zq/im/modules/api/form/ApiResp.java
0 → 100644
View file @
87efa3b4
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
());
}
}
interface-server/src/main/java/com/zq/im/modules/api/service/ApiManage.java
0 → 100644
View file @
87efa3b4
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
);
}
}
interface-server/src/main/java/com/zq/im/modules/api/service/IApiCommon.java
0 → 100644
View file @
87efa3b4
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
);
}
interface-server/src/main/java/com/zq/im/modules/api/service/IApiLogic.java
0 → 100644
View file @
87efa3b4
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);
}
interface-server/src/main/java/com/zq/im/modules/api/service/impl/ApiV1Logic.java
0 → 100644
View file @
87efa3b4
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
;
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
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
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"));
// }
}
interface-server/src/main/java/com/zq/im/modules/api/service/impl/ApiV2Logic.java
0 → 100644
View file @
87efa3b4
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
{
}
interface-server/src/main/java/com/zq/im/modules/api/service/impl/BaseApiLogic.java
0 → 100644
View file @
87efa3b4
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
);
}
}
interface-server/src/main/java/com/zq/im/modules/api/utils/ApiUtils.java
0 → 100644
View file @
87efa3b4
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
);
}
}
interface-server/src/main/java/com/zq/im/modules/api/utils/EncryptUtils.java
0 → 100644
View file @
87efa3b4
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
;
}
}
}
interface-server/src/main/java/com/zq/im/modules/api/utils/ReflectionUtils.java
0 → 100644
View file @
87efa3b4
/**
* 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
;
}
}
interface-server/src/main/java/com/zq/im/modules/api/utils/RsaUtils.java
0 → 100644
View file @
87efa3b4
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
;
}
}
interface-server/src/main/java/com/zq/im/modules/api/vo/MethodVo.java
0 → 100644
View file @
87efa3b4
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
);
}
}
interface-server/src/main/java/com/zq/im/modules/im/controller/ImgProcController.java
0 → 100644
View file @
87efa3b4
package
com
.
zq
.
im
.
modules
.
im
.
controller
;
import
com.zq.im.modules.im.req.ImageReq
;
import
com.zq.im.modules.im.service.ImgProcService
;
import
com.zq.im.modules.system.vo.ResultVo
;
import
com.zq.im.utils.AssertUtils
;
import
io.swagger.annotations.Api
;
import
io.swagger.annotations.ApiOperation
;
import
lombok.RequiredArgsConstructor
;
import
lombok.extern.slf4j.Slf4j
;
import
org.springframework.web.bind.annotation.PostMapping
;
import
org.springframework.web.bind.annotation.RequestBody
;
import
org.springframework.web.bind.annotation.RequestMapping
;
import
org.springframework.web.bind.annotation.RestController
;
/**
* <p>
* 图片处理相关接口
* </p>
*
* @ClassName ImgApi
* @Author chenhao
* @Date 2022/10/27 17:55
*/
@Slf4j
@RestController
@RequestMapping
(
"/interface/api/imgproc"
)
@RequiredArgsConstructor
@Api
(
tags
=
"图片处理相关接口"
)
public
class
ImgProcController
{
private
final
ImgProcService
service
;
@ApiOperation
(
"检测图片信息"
)
@PostMapping
(
value
=
"/detection"
)
public
ResultVo
detection
(
@RequestBody
ImageReq
req
)
{
AssertUtils
.
hasText
(
req
.
getFileContent
(),
"缺少文件内容"
);
AssertUtils
.
hasText
(
req
.
getFilename
(),
"缺少文件名"
);
return
ResultVo
.
success
(
service
.
detection
(
req
));
}
@ApiOperation
(
"图片纠偏"
)
@PostMapping
(
value
=
"/deskew"
)
public
ResultVo
deskew
(
@RequestBody
ImageReq
req
)
{
AssertUtils
.
hasText
(
req
.
getFileContent
(),
"缺少文件内容"
);
AssertUtils
.
hasText
(
req
.
getFilename
(),
"缺少文件名"
);
return
ResultVo
.
success
(
service
.
apiService
(
req
,
"imageCorrection"
));
}
@ApiOperation
(
"去黑边"
)
@PostMapping
(
value
=
"/removeBlack"
)
public
ResultVo
removeBlack
(
@RequestBody
ImageReq
req
)
{
AssertUtils
.
hasText
(
req
.
getFileContent
(),
"缺少文件内容"
);
AssertUtils
.
hasText
(
req
.
getFilename
(),
"缺少文件名"
);
return
ResultVo
.
success
(
service
.
apiService
(
req
,
"removeBlack"
));
}
@ApiOperation
(
"图片灰度化"
)
@PostMapping
(
value
=
"/gray"
)
public
ResultVo
gray
(
@RequestBody
ImageReq
req
)
{
AssertUtils
.
hasText
(
req
.
getFileContent
(),
"缺少文件内容"
);
AssertUtils
.
hasText
(
req
.
getFilename
(),
"缺少文件名"
);
return
ResultVo
.
success
(
service
.
apiService
(
req
,
"gray"
));
}
@ApiOperation
(
"图片边缘检测"
)
@PostMapping
(
value
=
"/canny"
)
public
ResultVo
canny
(
@RequestBody
ImageReq
req
)
{
AssertUtils
.
hasText
(
req
.
getFileContent
(),
"缺少文件内容"
);
AssertUtils
.
hasText
(
req
.
getFilename
(),
"缺少文件名"
);
return
ResultVo
.
success
(
service
.
apiService
(
req
,
"canny"
));
}
@ApiOperation
(
"图片弯曲矫正"
)
@PostMapping
(
value
=
"/correct"
)
public
ResultVo
correct
(
@RequestBody
ImageReq
req
)
{
AssertUtils
.
hasText
(
req
.
getFileContent
(),
"缺少文件内容"
);
AssertUtils
.
hasText
(
req
.
getFilename
(),
"缺少文件名"
);
return
ResultVo
.
success
(
service
.
apiService
(
req
,
"correct"
));
}
}
interface-server/src/main/java/com/zq/im/modules/im/feign/ImgprocFeign.java
0 → 100644
View file @
87efa3b4
package
com
.
zq
.
im
.
modules
.
im
.
feign
;
import
com.zq.im.modules.im.req.ImageReq
;
import
com.zq.im.modules.im.vo.DetectionResVo
;
import
com.zq.im.modules.system.vo.ResultVo
;
import
org.springframework.cloud.openfeign.FeignClient
;
import
org.springframework.http.MediaType
;
import
org.springframework.web.bind.annotation.PostMapping
;
/**
* @author chenhao
* @since 2022/4/24 10:26
*/
@FeignClient
(
name
=
"IMGPROC-SERVER"
,
path
=
"/imgproc/v1"
)
public
interface
ImgprocFeign
{
/**
* 图片质量检测接口
*/
@PostMapping
(
value
=
"/detection"
,
consumes
=
MediaType
.
APPLICATION_JSON_UTF8_VALUE
)
ResultVo
<
DetectionResVo
>
detection
(
ImageReq
req
);
}
interface-server/src/main/java/com/zq/im/modules/im/req/ImageReq.java
0 → 100644
View file @
87efa3b4
package
com
.
zq
.
im
.
modules
.
im
.
req
;
import
lombok.AllArgsConstructor
;
import
lombok.Builder
;
import
lombok.Data
;
import
lombok.NoArgsConstructor
;
/**
* <p>
* 图片请求类
* </p>
*
* @author chenhao
* @since 2023/11/9
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public
class
ImageReq
{
/**
* 图片名称
*/
String
filename
;
/**
* 图片的Base64字符串
*/
String
fileContent
;
}
interface-server/src/main/java/com/zq/im/modules/im/service/ImgProcService.java
0 → 100644
View file @
87efa3b4
package
com
.
zq
.
im
.
modules
.
im
.
service
;
import
cn.hutool.core.codec.Base64
;
import
cn.hutool.core.io.FileUtil
;
import
cn.hutool.core.util.IdUtil
;
import
cn.hutool.http.HttpRequest
;
import
cn.hutool.http.HttpResponse
;
import
cn.hutool.http.HttpUtil
;
import
cn.hutool.json.JSONObject
;
import
cn.hutool.json.JSONUtil
;
import
com.zq.im.exception.BusinessException
;
import
com.zq.im.modules.im.feign.ImgprocFeign
;
import
com.zq.im.modules.im.req.ImageReq
;
import
com.zq.im.modules.im.vo.DetectionResVo
;
import
com.zq.im.modules.system.vo.ResultVo
;
import
lombok.RequiredArgsConstructor
;
import
lombok.extern.slf4j.Slf4j
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.stereotype.Service
;
import
java.io.File
;
/**
* <p>
* 图片处理Service
* </p>
*
* @author chenhao
* @since 2022/10/27 17:56
*/
@RequiredArgsConstructor
@Service
@Slf4j
public
class
ImgProcService
{
private
final
String
URL
=
"http://147.1.5.135:8510/imgproc/v1/"
;
private
final
String
TEST_URL
=
"http://localhost:8510/imgproc/v1/"
;
@Autowired
ImgprocFeign
feign
;
public
Object
detection
(
ImageReq
req
)
{
ResultVo
<
DetectionResVo
>
resultVo
=
feign
.
detection
(
req
);
return
resultVo
.
getData
();
}
public
Object
detection1
(
ImageReq
req
)
{
String
url
=
URL
+
"detection"
;
HttpRequest
request
=
HttpUtil
.
createPost
(
url
)
.
header
(
"Content-Type"
,
"application/json"
)
.
body
(
JSONUtil
.
toJsonStr
(
req
))
.
timeout
(
120
*
1000
);
String
body
;
try
(
HttpResponse
response
=
request
.
execute
())
{
body
=
response
.
body
();
}
JSONObject
obj
=
JSONUtil
.
parseObj
(
body
);
Integer
code
=
obj
.
getInt
(
"code"
);
if
(
code
!=
null
&&
code
!=
200
)
{
throw
new
BusinessException
(
obj
.
getStr
(
"msg"
));
}
return
obj
.
get
(
"data"
);
}
public
Object
apiService
(
ImageReq
req
,
String
method
)
{
String
url
=
URL
+
method
;
String
dir
=
IdUtil
.
fastSimpleUUID
();
File
file
=
new
File
(
dir
+
"/"
+
req
.
getFilename
());
Base64
.
decodeToFile
(
req
.
getFileContent
(),
file
);
HttpRequest
request
=
HttpUtil
.
createPost
(
url
)
.
form
(
"file"
,
file
)
.
timeout
(
120
*
1000
);
String
body
;
try
(
HttpResponse
response
=
request
.
execute
())
{
body
=
response
.
body
();
}
JSONObject
obj
=
JSONUtil
.
parseObj
(
body
);
Integer
code
=
obj
.
getInt
(
"code"
);
if
(
code
!=
null
&&
code
!=
200
)
{
throw
new
BusinessException
(
obj
.
getStr
(
"msg"
));
}
// 删除目录及文件
FileUtil
.
del
(
file
.
getAbsoluteFile
().
getParent
());
return
obj
.
get
(
"data"
);
}
}
i
mgprocessing-server/src/main/java/com/zq/processing
/vo/DetectionResVo.java
→
i
nterface-server/src/main/java/com/zq/im/modules/im
/vo/DetectionResVo.java
View file @
87efa3b4
package
com
.
zq
.
processing
.
vo
;
package
com
.
zq
.
im
.
modules
.
im
.
vo
;
import
io.swagger.annotations.ApiModelProperty
;
import
io.swagger.annotations.ApiModelProperty
;
import
lombok.AllArgsConstructor
;
import
lombok.AllArgsConstructor
;
...
@@ -11,7 +11,7 @@ import lombok.NoArgsConstructor;
...
@@ -11,7 +11,7 @@ import lombok.NoArgsConstructor;
*
*
* </p>
* </p>
*
*
* @author
chenhao
* @author
yww
* @since 2023/4/21
* @since 2023/4/21
*/
*/
@NoArgsConstructor
@NoArgsConstructor
...
...
interface-server/src/main/java/com/zq/im/modules/system/dao/ApiUserInfoDao.java
0 → 100644
View file @
87efa3b4
package
com
.
zq
.
im
.
modules
.
system
.
dao
;
import
com.baomidou.mybatisplus.core.mapper.BaseMapper
;
import
com.zq.im.modules.system.entity.ApiUserInfo
;
import
org.springframework.stereotype.Repository
;
/**
* 第三方接口调用用户信息(ApiUserInfo)表数据库访问层
*
* @author makejava
* @since 2021-12-09 17:20:44
*/
@Repository
public
interface
ApiUserInfoDao
extends
BaseMapper
<
ApiUserInfo
>
{
}
interface-server/src/main/java/com/zq/im/modules/system/entity/ApiUserInfo.java
0 → 100644
View file @
87efa3b4
package
com
.
zq
.
im
.
modules
.
system
.
entity
;
import
com.baomidou.mybatisplus.annotation.IdType
;
import
com.baomidou.mybatisplus.annotation.TableId
;
import
com.baomidou.mybatisplus.annotation.TableName
;
import
lombok.AllArgsConstructor
;
import
lombok.Builder
;
import
lombok.Data
;
import
lombok.NoArgsConstructor
;
import
java.util.Date
;
/**
* 第三方接口调用用户信息(ApiUserInfo)实体类
*
* @author makejava
* @since 2021-12-09 17:20:44
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@TableName
(
value
=
"api_user_info"
)
public
class
ApiUserInfo
{
/**
* 主键
*/
@TableId
(
type
=
IdType
.
ASSIGN_UUID
)
private
String
id
;
/**
* APP_ID
*/
private
String
appId
;
/**
* APP_SECRET
*/
private
String
appSecret
;
/**
* 申请人
*/
private
String
applyer
;
/**
* 状态
*/
private
Integer
state
;
/**
* 创建时间
*/
private
Date
createTime
;
/**
* 更新时间
*/
private
Date
updateTime
;
}
interface-server/src/main/java/com/zq/im/modules/system/service/ApiUserInfoService.java
0 → 100644
View file @
87efa3b4
package
com
.
zq
.
im
.
modules
.
system
.
service
;
import
cn.hutool.core.date.DateUtil
;
import
cn.hutool.core.util.IdUtil
;
import
cn.hutool.json.JSONUtil
;
import
com.baomidou.mybatisplus.core.toolkit.Wrappers
;
import
com.zq.im.modules.system.dao.ApiUserInfoDao
;
import
com.zq.im.modules.system.entity.ApiUserInfo
;
import
com.zq.im.utils.AssertUtils
;
import
com.zq.im.utils.RedisUtil
;
import
com.zq.im.utils.TokenUtil
;
import
com.zq.im.utils.UuidUtils
;
import
lombok.extern.slf4j.Slf4j
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.stereotype.Service
;
import
javax.annotation.Resource
;
import
java.util.HashMap
;
import
java.util.Map
;
import
java.util.concurrent.TimeUnit
;
/**
* @author chenhao
*/
@Slf4j
@Service
public
class
ApiUserInfoService
{
@Autowired
ApiUserInfoDao
apiUserInfoDao
;
@Resource
RedisUtil
redisUtil
;
public
static
void
main
(
String
[]
args
)
{
// 创建用户
ApiUserInfo
apiUserInfo
=
new
ApiUserInfo
();
apiUserInfo
.
setId
(
UuidUtils
.
uuidNoDash
());
apiUserInfo
.
setAppId
(
UuidUtils
.
uuidNoDash
());
apiUserInfo
.
setAppSecret
(
UuidUtils
.
uuidNoDash
());
apiUserInfo
.
setApplyer
(
"测试用户"
);
apiUserInfo
.
setState
(
1
);
System
.
out
.
println
(
DateUtil
.
now
());
System
.
out
.
println
(
JSONUtil
.
toJsonStr
(
apiUserInfo
));
}
public
Map
<
String
,
Object
>
getApiToken
(
String
appId
,
String
appSecret
)
{
ApiUserInfo
apiAppInfo
=
apiUserInfoDao
.
selectOne
(
Wrappers
.
lambdaQuery
(
ApiUserInfo
.
builder
().
appId
(
appId
).
appSecret
(
appSecret
).
build
())
);
AssertUtils
.
notNull
(
apiAppInfo
,
"账号或密码错误"
);
AssertUtils
.
isTrue
(
apiAppInfo
.
getState
()
==
1
,
"账号未激活"
);
String
sessionKey
=
IdUtil
.
simpleUUID
();
String
apiToken
=
TokenUtil
.
createToken
(
apiAppInfo
.
getAppId
(),
sessionKey
);
log
.
debug
(
">> [session-key]:{}"
,
sessionKey
);
redisUtil
.
setStr
(
TokenUtil
.
getApiTokenKey
(
apiAppInfo
.
getAppId
()),
apiToken
,
120L
);
Map
<
String
,
Object
>
data
=
new
HashMap
<>();
data
.
put
(
"userId"
,
apiAppInfo
.
getId
());
data
.
put
(
"username"
,
apiAppInfo
.
getApplyer
());
data
.
put
(
"key"
,
sessionKey
);
data
.
put
(
"expireTime"
,
TimeUnit
.
MINUTES
.
toSeconds
(
TokenUtil
.
API_TOKEN_EXPIRE_MINUTES
));
data
.
put
(
"unit"
,
"秒"
);
return
data
;
}
}
i
mgprocessing-server/src/main/java/com/zq/processing/utils
/ResultVo.java
→
i
nterface-server/src/main/java/com/zq/im/modules/system/vo
/ResultVo.java
View file @
87efa3b4
package
com
.
zq
.
processing
.
utils
;
package
com
.
zq
.
im
.
modules
.
system
.
vo
;
import
com.fasterxml.jackson.annotation.JsonIgnoreProperties
;
import
com.fasterxml.jackson.annotation.JsonIgnoreProperties
;
import
com.fasterxml.jackson.annotation.JsonInclude
;
import
com.fasterxml.jackson.annotation.JsonInclude
;
...
...
i
mgprocessing-server/src/main/java/com/zq/processing
/utils/AssertUtils.java
→
i
nterface-server/src/main/java/com/zq/im
/utils/AssertUtils.java
View file @
87efa3b4
package
com
.
zq
.
processing
.
utils
;
package
com
.
zq
.
im
.
utils
;
import
cn.hutool.core.util.StrUtil
;
import
cn.hutool.core.util.StrUtil
;
import
com.zq.
processing
.exception.BusinessException
;
import
com.zq.
im
.exception.BusinessException
;
import
java.util.Arrays
;
import
java.util.Arrays
;
import
java.util.Collection
;
import
java.util.Collection
;
...
...
interface-server/src/main/java/com/zq/im/utils/IpUtil.java
0 → 100644
View file @
87efa3b4
package
com
.
zq
.
im
.
utils
;
import
cn.hutool.core.util.CharUtil
;
import
cn.hutool.core.util.StrUtil
;
import
cn.hutool.http.useragent.UserAgent
;
import
cn.hutool.http.useragent.UserAgentUtil
;
import
org.springframework.web.context.request.RequestContextHolder
;
import
org.springframework.web.context.request.ServletRequestAttributes
;
import
javax.servlet.http.HttpServletRequest
;
import
java.util.Objects
;
import
java.util.regex.Matcher
;
import
java.util.regex.Pattern
;
/**
* <p>
* IP相关工具类
* </p>
*
* @author chenhao
* @since 2022/10/12 20:57
*/
public
class
IpUtil
{
public
static
long
getLongIpAddr
(
HttpServletRequest
request
)
{
String
ip
=
getIpAddr
(
request
);
return
ipv4ToLong
(
ip
);
}
/**
* 获取客户端IP
*
* @return IP
*/
public
static
String
getIpAddr
()
{
return
getIpAddr
(
getRequest
());
}
/**
* 获取客户端IP
*
* @param request 请求
* @return IP
*/
public
static
String
getIpAddr
(
HttpServletRequest
request
)
{
if
(
request
==
null
)
{
return
"unknown"
;
}
// 判断x-forwarded-for是否有IP
String
ip
=
request
.
getHeader
(
"x-forwarded-for"
);
if
(
isNotUnknown
(
ip
))
{
ip
=
request
.
getHeader
(
"Proxy-Client-IP"
);
}
// 判断Proxy-Client-IP是否有IP
if
(
isNotUnknown
(
ip
))
{
ip
=
request
.
getHeader
(
"X-Forwarded-For"
);
}
// 判断X-Forwarded-For是否有IP
if
(
isNotUnknown
(
ip
))
{
ip
=
request
.
getHeader
(
"WL-Proxy-Client-IP"
);
}
// 判断WL-Proxy-Client-IP是否有IP
if
(
isNotUnknown
(
ip
))
{
ip
=
request
.
getHeader
(
"X-Real-IP"
);
}
// 判断X-Real-IP是否有IP
if
(
isNotUnknown
(
ip
))
{
ip
=
request
.
getRemoteAddr
();
}
// 若是IP最后为0:0:0:0:0:0:0:1,就是本地回环地址,否则第一个非unknown的IP地址就是真实地址
return
"0:0:0:0:0:0:0:1"
.
equals
(
ip
)
?
"127.0.0.1"
:
getMultistageReverseProxyIp
(
ip
);
}
/**
* 从多级反向代理中获得第一个非unknown IP地址
*
* @param ip 获得的IP地址
* @return 第一个非unknown IP地址
*/
@SuppressWarnings
(
"all"
)
private
static
String
getMultistageReverseProxyIp
(
String
ip
)
{
// 多级反向代理检测
if
(
ip
!=
null
&&
ip
.
indexOf
(
","
)
>
0
)
{
final
String
[]
ips
=
ip
.
trim
().
split
(
","
);
for
(
String
subIp
:
ips
)
{
if
(
isNotUnknown
(
subIp
))
{
ip
=
subIp
;
break
;
}
}
}
return
ip
;
}
/**
* 检测给定字符串是否为未知,多用于检测HTTP请求相关
*
* @param checkString 被检测的字符串
* @return 是否未知
*/
private
static
boolean
isNotUnknown
(
String
checkString
)
{
return
StrUtil
.
isNotBlank
(
checkString
)
&&
StrUtil
.
isNotEmpty
(
checkString
)
&&
!
"unknown"
.
equalsIgnoreCase
(
checkString
);
}
/**
* 获取UserAgent信息对象
*
* @param request 请求
* @return UserAgent
*/
public
static
UserAgent
getBrowser
(
HttpServletRequest
request
)
{
return
UserAgentUtil
.
parse
(
request
.
getHeader
(
"User-Agent"
));
}
/**
* 获取当前请求
*
* @return 当前请求
*/
private
static
HttpServletRequest
getRequest
()
{
return
((
ServletRequestAttributes
)
Objects
.
requireNonNull
(
RequestContextHolder
.
getRequestAttributes
())).
getRequest
();
}
/**
* 将ipv4地址转换为long类型
* 等同于MySQL的inet_aton方法
*
* @param ipStr ipv4地址
* @return long类型的ipv地址
*/
public
static
long
ipv4ToLong
(
String
ipStr
)
{
String
regex
=
"^(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)$"
;
Pattern
pattern
=
Pattern
.
compile
(
regex
);
Matcher
matcher
=
pattern
.
matcher
(
ipStr
);
if
(
matcher
.
matches
())
{
long
addr
=
0
;
for
(
int
i
=
1
;
i
<=
4
;
i
++)
{
addr
|=
Long
.
parseLong
(
matcher
.
group
(
i
))
<<
8
*
(
4
-
i
);
}
return
addr
;
}
else
{
throw
new
RuntimeException
(
"ipv4地址格式出错!"
);
}
}
/**
* 将long类型的ip转为ipv4字符串
* 等同于MySQL的inet_ntoa方法
*
* @param longIp long类型IP
* @return IPV4字符串
*/
public
static
String
longToIpv4
(
long
longIp
)
{
StringBuilder
sb
=
new
StringBuilder
();
// 直接右移24位
sb
.
append
(
longIp
>>
24
&
0xFF
);
sb
.
append
(
CharUtil
.
DOT
);
// 将高8位置0,然后右移16位
sb
.
append
(
longIp
>>
16
&
0xFF
);
sb
.
append
(
CharUtil
.
DOT
);
sb
.
append
(
longIp
>>
8
&
0xFF
);
sb
.
append
(
CharUtil
.
DOT
);
sb
.
append
(
longIp
&
0xFF
);
return
sb
.
toString
();
}
}
interface-server/src/main/java/com/zq/im/utils/RedisUtil.java
0 → 100644
View file @
87efa3b4
package
com
.
zq
.
im
.
utils
;
import
com.alibaba.fastjson2.JSON
;
import
lombok.extern.slf4j.Slf4j
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.data.redis.connection.RedisConnection
;
import
org.springframework.data.redis.connection.RedisConnectionFactory
;
import
org.springframework.data.redis.core.*
;
import
org.springframework.stereotype.Component
;
import
java.util.*
;
import
java.util.concurrent.TimeUnit
;
/**
* <p>
* Redis的工具类
* </p>
*
* @ClassName RedisUtil
* @Author yww
* @Date 2022/10/15 10:40
*/
@Slf4j
@Component
@SuppressWarnings
(
"all"
)
public
class
RedisUtil
{
private
final
StringRedisTemplate
stringRedisTemplate
;
private
final
RedisTemplate
<
String
,
Object
>
redisTemplate
;
@Autowired
public
RedisUtil
(
StringRedisTemplate
stringRedisTemplate
,
RedisTemplate
<
String
,
Object
>
redisTemplate
)
{
this
.
stringRedisTemplate
=
stringRedisTemplate
;
this
.
redisTemplate
=
redisTemplate
;
}
/**
* 普通缓存
*
* @param key 键
*/
public
void
setStr
(
String
key
,
String
value
)
{
stringRedisTemplate
.
opsForValue
().
set
(
key
,
value
);
}
/**
* 普通缓存
*
* @param key 键
*/
public
void
setStr
(
String
key
,
String
value
,
long
time
)
{
stringRedisTemplate
.
opsForValue
().
set
(
key
,
value
,
time
,
TimeUnit
.
MINUTES
);
}
/**
* 普通缓存
*
* @param key 键
*/
public
void
setStr
(
String
key
,
String
value
,
long
time
,
TimeUnit
timeUnit
)
{
stringRedisTemplate
.
opsForValue
().
set
(
key
,
value
,
time
,
timeUnit
);
}
/**
* 普通缓存获取
*
* @param key 键
* @return 值
*/
public
String
getStr
(
String
key
)
{
return
key
==
null
?
null
:
stringRedisTemplate
.
opsForValue
().
get
(
key
);
}
/**
* 普通缓存获取
*
* @param key 键
* @return 值
*/
public
<
T
>
T
getStr
(
String
key
,
Class
<
T
>
clazz
)
{
return
key
==
null
?
null
:
JSON
.
parseObject
(
stringRedisTemplate
.
opsForValue
().
get
(
key
),
clazz
);
}
/**
* 普通缓存删除
*
* @param key 键
*/
public
void
deleteStr
(
String
key
)
{
stringRedisTemplate
.
delete
(
key
);
}
/**
* Object缓存
*
* @param key 键
*/
public
void
setObj
(
String
key
,
Object
value
)
{
redisTemplate
.
opsForValue
().
set
(
key
,
value
);
}
/**
* Object缓存
*
* @param key 键
*/
public
void
setObj
(
String
key
,
Object
value
,
long
time
)
{
redisTemplate
.
opsForValue
().
set
(
key
,
value
,
time
,
TimeUnit
.
MINUTES
);
}
/**
* Object缓存
*
* @param key 键
*/
public
void
setObj
(
String
key
,
Object
value
,
long
time
,
TimeUnit
timeUnit
)
{
redisTemplate
.
opsForValue
().
set
(
key
,
value
,
time
,
timeUnit
);
}
/**
* Object缓存获取
*
* @param key 键
* @return 值
*/
public
Object
getObj
(
String
key
)
{
return
key
==
null
?
null
:
redisTemplate
.
opsForValue
().
get
(
key
);
}
/**
* Object缓存获取
*
* @param key 键
* @return 值
*/
public
<
T
>
T
getObj
(
String
key
,
Class
<
T
>
clazz
)
{
return
key
==
null
?
null
:
(
T
)
redisTemplate
.
opsForValue
().
get
(
key
);
}
/**
* Object缓存删除
*
* @param key 键
*/
public
void
deleteObj
(
String
key
)
{
redisTemplate
.
delete
(
key
);
}
/**
* 查找匹配key
*
* @param pattern key
* @return /
*/
public
List
<
String
>
scan
(
String
pattern
)
{
ScanOptions
options
=
ScanOptions
.
scanOptions
().
match
(
pattern
).
build
();
RedisConnectionFactory
factory
=
redisTemplate
.
getConnectionFactory
();
RedisConnection
rc
=
Objects
.
requireNonNull
(
factory
).
getConnection
();
Cursor
<
byte
[]>
cursor
=
rc
.
scan
(
options
);
List
<
String
>
result
=
new
ArrayList
<>();
while
(
cursor
.
hasNext
())
{
result
.
add
(
new
String
(
cursor
.
next
()));
}
try
{
RedisConnectionUtils
.
releaseConnection
(
rc
,
factory
);
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
}
return
result
;
}
/**
* 指定缓存失效时间
*
* @param key 键
* @param time 时间(秒)
*/
public
boolean
expire
(
String
key
,
long
time
)
{
try
{
if
(
time
>
0
)
{
redisTemplate
.
expire
(
key
,
time
,
TimeUnit
.
SECONDS
);
}
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
return
false
;
}
return
true
;
}
/**
* 指定缓存失效时间
*
* @param key 键
* @param time 时间(秒)
* @param timeUnit 单位
*/
public
boolean
expire
(
String
key
,
long
time
,
TimeUnit
timeUnit
)
{
try
{
if
(
time
>
0
)
{
redisTemplate
.
expire
(
key
,
time
,
timeUnit
);
}
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
return
false
;
}
return
true
;
}
/**
* 根据 key 获取过期时间
*
* @param key 键 不能为null
* @return 时间(秒) 返回0代表为永久有效
*/
public
long
getExpire
(
String
key
)
{
return
redisTemplate
.
getExpire
(
key
,
TimeUnit
.
SECONDS
);
}
/**
* 分页查询 key
*
* @param patternKey key
* @param page 页码
* @param size 每页数目
* @return /
*/
public
List
<
String
>
findKeysForPage
(
String
patternKey
,
int
page
,
int
size
)
{
ScanOptions
options
=
ScanOptions
.
scanOptions
().
match
(
patternKey
).
build
();
RedisConnectionFactory
factory
=
redisTemplate
.
getConnectionFactory
();
RedisConnection
rc
=
Objects
.
requireNonNull
(
factory
).
getConnection
();
Cursor
<
byte
[]>
cursor
=
rc
.
scan
(
options
);
List
<
String
>
result
=
new
ArrayList
<>(
size
);
int
tmpIndex
=
0
;
int
fromIndex
=
page
*
size
;
int
toIndex
=
page
*
size
+
size
;
while
(
cursor
.
hasNext
())
{
if
(
tmpIndex
>=
fromIndex
&&
tmpIndex
<
toIndex
)
{
result
.
add
(
new
String
(
cursor
.
next
()));
tmpIndex
++;
continue
;
}
// 获取到满足条件的数据后,就可以退出了
if
(
tmpIndex
>=
toIndex
)
{
break
;
}
tmpIndex
++;
cursor
.
next
();
}
try
{
RedisConnectionUtils
.
releaseConnection
(
rc
,
factory
);
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
}
return
result
;
}
/**
* 判断key是否存在
*
* @param key 键
* @return true 存在 false不存在
*/
public
boolean
hasKey
(
String
key
)
{
try
{
return
redisTemplate
.
hasKey
(
key
);
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
return
false
;
}
}
/**
* 删除缓存
*
* @param keys 可以传一个值 或多个
*/
public
void
del
(
String
...
keys
)
{
if
(
keys
!=
null
&&
keys
.
length
>
0
)
{
if
(
keys
.
length
==
1
)
{
boolean
result
=
redisTemplate
.
delete
(
keys
[
0
]);
log
.
debug
(
"--------------------------------------------"
);
log
.
debug
(
"删除缓存:{},结果:{}"
,
keys
[
0
],
result
);
log
.
debug
(
"--------------------------------------------"
);
}
else
{
Set
<
String
>
keySet
=
new
HashSet
<>();
for
(
String
key
:
keys
)
{
keySet
.
addAll
(
redisTemplate
.
keys
(
key
));
}
long
count
=
redisTemplate
.
delete
(
keySet
);
log
.
debug
(
"--------------------------------------------"
);
log
.
debug
(
"成功删除缓存:{}"
,
keySet
);
log
.
debug
(
"缓存删除数量:{}个"
,
count
);
log
.
debug
(
"--------------------------------------------"
);
}
}
}
// ============================String=============================
/**
* 普通缓存获取
*
* @param key 键
* @return 值
*/
public
Object
get
(
String
key
)
{
return
key
==
null
?
null
:
redisTemplate
.
opsForValue
().
get
(
key
);
}
/**
* 批量获取
*/
public
List
<
Object
>
multiGet
(
List
<
String
>
keys
)
{
return
redisTemplate
.
opsForValue
().
multiGet
(
keys
);
}
/**
* 普通缓存放入
*
* @param key 键
* @param value 值
* @return true成功 false失败
*/
public
boolean
set
(
String
key
,
Object
value
)
{
try
{
redisTemplate
.
opsForValue
().
set
(
key
,
value
);
return
true
;
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
return
false
;
}
}
/**
* 普通缓存放入并设置时间
*
* @param key 键
* @param value 值
* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
* @return true成功 false 失败
*/
public
boolean
set
(
String
key
,
Object
value
,
long
time
)
{
try
{
if
(
time
>
0
)
{
redisTemplate
.
opsForValue
().
set
(
key
,
value
,
time
,
TimeUnit
.
SECONDS
);
}
else
{
set
(
key
,
value
);
}
return
true
;
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
return
false
;
}
}
/**
* 普通缓存放入并设置时间
*
* @param key 键
* @param value 值
* @param time 时间
* @param timeUnit 类型
* @return true成功 false 失败
*/
public
boolean
set
(
String
key
,
Object
value
,
long
time
,
TimeUnit
timeUnit
)
{
try
{
if
(
time
>
0
)
{
redisTemplate
.
opsForValue
().
set
(
key
,
value
,
time
,
timeUnit
);
}
else
{
set
(
key
,
value
);
}
return
true
;
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
return
false
;
}
}
// ================================Map=================================
/**
* HashGet
*
* @param key 键 不能为null
* @param item 项 不能为null
* @return 值
*/
public
Object
hget
(
String
key
,
String
item
)
{
return
redisTemplate
.
opsForHash
().
get
(
key
,
item
);
}
/**
* 获取hashKey对应的所有键值
*
* @param key 键
* @return 对应的多个键值
*/
public
Map
<
Object
,
Object
>
hmget
(
String
key
)
{
return
redisTemplate
.
opsForHash
().
entries
(
key
);
}
/**
* HashSet
*
* @param key 键
* @param map 对应多个键值
* @return true 成功 false 失败
*/
public
boolean
hmset
(
String
key
,
Map
<
String
,
Object
>
map
)
{
try
{
redisTemplate
.
opsForHash
().
putAll
(
key
,
map
);
return
true
;
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
return
false
;
}
}
/**
* HashSet 并设置时间
*
* @param key 键
* @param map 对应多个键值
* @param time 时间(秒)
* @return true成功 false失败
*/
public
boolean
hmset
(
String
key
,
Map
<
String
,
Object
>
map
,
long
time
)
{
try
{
redisTemplate
.
opsForHash
().
putAll
(
key
,
map
);
if
(
time
>
0
)
{
expire
(
key
,
time
);
}
return
true
;
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
return
false
;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
* @return true 成功 false失败
*/
public
boolean
hset
(
String
key
,
String
item
,
Object
value
)
{
try
{
redisTemplate
.
opsForHash
().
put
(
key
,
item
,
value
);
return
true
;
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
return
false
;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
* @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
* @return true 成功 false失败
*/
public
boolean
hset
(
String
key
,
String
item
,
Object
value
,
long
time
)
{
try
{
redisTemplate
.
opsForHash
().
put
(
key
,
item
,
value
);
if
(
time
>
0
)
{
expire
(
key
,
time
);
}
return
true
;
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
return
false
;
}
}
/**
* 删除hash表中的值
*
* @param key 键 不能为null
* @param item 项 可以使多个 不能为null
*/
public
void
hdel
(
String
key
,
Object
...
item
)
{
redisTemplate
.
opsForHash
().
delete
(
key
,
item
);
}
/**
* 判断hash表中是否有该项的值
*
* @param key 键 不能为null
* @param item 项 不能为null
* @return true 存在 false不存在
*/
public
boolean
hHasKey
(
String
key
,
String
item
)
{
return
redisTemplate
.
opsForHash
().
hasKey
(
key
,
item
);
}
/**
* hash递增 如果不存在,就会创建一个 并把新增后的值返回
*
* @param key 键
* @param item 项
* @param by 要增加几(大于0)
*/
public
double
hincr
(
String
key
,
String
item
,
double
by
)
{
return
redisTemplate
.
opsForHash
().
increment
(
key
,
item
,
by
);
}
/**
* hash递减
*
* @param key 键
* @param item 项
* @param by 要减少记(小于0)
*/
public
double
hdecr
(
String
key
,
String
item
,
double
by
)
{
return
redisTemplate
.
opsForHash
().
increment
(
key
,
item
,
-
by
);
}
// ============================set=============================
/**
* 根据key获取Set中的所有值
*
* @param key 键
*/
public
Set
<
Object
>
sGet
(
String
key
)
{
try
{
return
redisTemplate
.
opsForSet
().
members
(
key
);
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
return
null
;
}
}
/**
* 根据value从一个set中查询,是否存在
*
* @param key 键
* @param value 值
* @return true 存在 false不存在
*/
public
boolean
sHasKey
(
String
key
,
Object
value
)
{
try
{
return
redisTemplate
.
opsForSet
().
isMember
(
key
,
value
);
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
return
false
;
}
}
/**
* 将数据放入set缓存
*
* @param key 键
* @param values 值 可以是多个
* @return 成功个数
*/
public
long
sSet
(
String
key
,
Object
...
values
)
{
try
{
return
redisTemplate
.
opsForSet
().
add
(
key
,
values
);
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
return
0
;
}
}
/**
* 将set数据放入缓存
*
* @param key 键
* @param time 时间(秒)
* @param values 值 可以是多个
* @return 成功个数
*/
public
long
sSetAndTime
(
String
key
,
long
time
,
Object
...
values
)
{
try
{
Long
count
=
redisTemplate
.
opsForSet
().
add
(
key
,
values
);
if
(
time
>
0
)
{
expire
(
key
,
time
);
}
return
count
;
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
return
0
;
}
}
/**
* 获取set缓存的长度
*
* @param key 键
*/
public
long
sGetSetSize
(
String
key
)
{
try
{
return
redisTemplate
.
opsForSet
().
size
(
key
);
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
return
0
;
}
}
/**
* 移除值为value的
*
* @param key 键
* @param values 值 可以是多个
* @return 移除的个数
*/
public
long
setRemove
(
String
key
,
Object
...
values
)
{
try
{
Long
count
=
redisTemplate
.
opsForSet
().
remove
(
key
,
values
);
return
count
;
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
return
0
;
}
}
// ===============================list=================================
/**
* 获取list缓存的内容
*
* @param key 键
* @param start 开始
* @param end 结束 0 到 -1代表所有值
*/
public
List
<
Object
>
lGet
(
String
key
,
long
start
,
long
end
)
{
try
{
return
redisTemplate
.
opsForList
().
range
(
key
,
start
,
end
);
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
return
null
;
}
}
/**
* 获取list缓存的长度
*
* @param key 键
*/
public
long
lGetListSize
(
String
key
)
{
try
{
return
redisTemplate
.
opsForList
().
size
(
key
);
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
return
0
;
}
}
/**
* 通过索引 获取list中的值
*
* @param key 键
* @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
*/
public
Object
lGetIndex
(
String
key
,
long
index
)
{
try
{
return
redisTemplate
.
opsForList
().
index
(
key
,
index
);
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
return
null
;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
*/
public
boolean
lSet
(
String
key
,
Object
value
)
{
try
{
redisTemplate
.
opsForList
().
rightPush
(
key
,
value
);
return
true
;
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
return
false
;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @param time 时间(秒)
*/
public
boolean
lSet
(
String
key
,
Object
value
,
long
time
)
{
try
{
redisTemplate
.
opsForList
().
rightPush
(
key
,
value
);
if
(
time
>
0
)
{
expire
(
key
,
time
);
}
return
true
;
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
return
false
;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
*/
public
boolean
lSet
(
String
key
,
List
<
Object
>
value
)
{
try
{
redisTemplate
.
opsForList
().
rightPushAll
(
key
,
value
);
return
true
;
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
return
false
;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @param time 时间(秒)
*/
public
boolean
lSet
(
String
key
,
List
<
Object
>
value
,
long
time
)
{
try
{
redisTemplate
.
opsForList
().
rightPushAll
(
key
,
value
);
if
(
time
>
0
)
{
expire
(
key
,
time
);
}
return
true
;
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
return
false
;
}
}
/**
* 根据索引修改list中的某条数据
*
* @param key 键
* @param index 索引
* @param value 值
* @return /
*/
public
boolean
lUpdateIndex
(
String
key
,
long
index
,
Object
value
)
{
try
{
redisTemplate
.
opsForList
().
set
(
key
,
index
,
value
);
return
true
;
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
return
false
;
}
}
/**
* 移除N个值为value
*
* @param key 键
* @param count 移除多少个
* @param value 值
* @return 移除的个数
*/
public
long
lRemove
(
String
key
,
long
count
,
Object
value
)
{
try
{
return
redisTemplate
.
opsForList
().
remove
(
key
,
count
,
value
);
}
catch
(
Exception
e
)
{
e
.
printStackTrace
();
return
0
;
}
}
/**
* @param prefix 前缀
* @param ids id
*/
public
void
delByKeys
(
String
prefix
,
Set
<
Long
>
ids
)
{
Set
<
String
>
keys
=
new
HashSet
<>();
for
(
Long
id
:
ids
)
{
keys
.
addAll
(
redisTemplate
.
keys
(
String
.
valueOf
(
new
StringBuffer
(
prefix
).
append
(
id
))));
}
long
count
=
redisTemplate
.
delete
(
keys
);
// 此处提示可自行删除
log
.
debug
(
"--------------------------------------------"
);
log
.
debug
(
"成功删除缓存:{}"
,
keys
);
log
.
debug
(
"缓存删除数量:{}个"
,
count
);
log
.
debug
(
"--------------------------------------------"
);
}
}
interface-server/src/main/java/com/zq/im/utils/ThreadContextUtil.java
0 → 100644
View file @
87efa3b4
package
com
.
zq
.
im
.
utils
;
import
org.apache.logging.log4j.ThreadContext
;
import
org.slf4j.Logger
;
import
org.slf4j.LoggerFactory
;
import
java.util.HashMap
;
import
java.util.Map
;
/**
* <p>
* 基于ThreadLocal的线程相关上下文帮助类, 用于在同一线程下传递变量.
* </p>
*
* @author zq
* @since 2023/11/8
*/
public
class
ThreadContextUtil
{
private
static
ThreadLocal
<
Map
<
String
,
Object
>>
threadLocalMap
=
ThreadLocal
.
withInitial
(
HashMap:
:
new
);
/**
* 根据指定的key获取当前线程相关的变量值
*
* @param key 变量的key
* @param <T> 变量值的具体类型
* @return 若无此key对应的变量值, 则返回{@code null}
* @throws ClassCastException 若接收此返回值的变量类型与上下文保存的值的实际类型不匹配, 则抛出异常
*/
@SuppressWarnings
(
"unchecked"
)
public
static
<
T
>
T
get
(
String
key
)
{
return
(
T
)
threadLocalMap
.
get
().
get
(
key
);
}
/**
* 根据指定的key获取当前线程相关的变量值, 若为{@code null}则返回指定的默认值
*
* @param key 变量的key
* @param defaultValue 默认值
* @param <T> 变量值的具体类型
* @return 若无此key对应的变量值, 则返回defaultValue
* @throws ClassCastException 若接收此返回值的变量类型与上下文保存的值的实际类型不匹配, 则抛出异常
*/
public
static
<
T
>
T
get
(
String
key
,
T
defaultValue
)
{
T
value
=
get
(
key
);
return
value
==
null
?
defaultValue
:
value
;
}
/**
* 设置线程相关上下文的变量值
*
* @param key 变量的key
* @param value 变量值
*/
public
static
void
set
(
String
key
,
Object
value
)
{
threadLocalMap
.
get
().
put
(
key
,
value
);
}
/**
* 删除指定key的变量
*
* @param key 变量的key
*/
public
static
void
remove
(
String
key
)
{
threadLocalMap
.
get
().
remove
(
key
);
}
/**
* 清除当前线程相关的上下文
*/
public
static
void
close
()
{
threadLocalMap
.
remove
();
}
/**
* Don't let anyone instantiate this class
*/
private
void
ThreadContext
()
{
}
}
interface-server/src/main/java/com/zq/im/utils/TokenUtil.java
0 → 100644
View file @
87efa3b4
package
com
.
zq
.
im
.
utils
;
import
cn.hutool.core.date.DateUtil
;
import
cn.hutool.core.util.IdUtil
;
import
com.auth0.jwt.JWT
;
import
com.auth0.jwt.algorithms.Algorithm
;
import
com.auth0.jwt.interfaces.DecodedJWT
;
import
com.auth0.jwt.interfaces.JWTVerifier
;
import
com.zq.im.constant.TokenConstant
;
import
java.util.Date
;
import
java.util.HashMap
;
import
java.util.Map
;
/**
* <p>
* Token工具类
* 1. Header,记录令牌类型和签名算法
* 2. payload,携带用户信息
* (1) iss(issuer), 签发者
* (2) sub(subject), 面向的主体
* (3) aud(audience), 接收方
* (4) nbf(notBefore), 开始生效生效时间戳
* (5) exp(expiresAt), 过期时间戳
* (6) iat(issuedAt ), 签发时间
* (7) jti(jwtId), 唯一标识
* 3. signature, 签名,防止Token被篡改
* </p>
*
* @ClassName TokenUtil
* @Author yww
* @Date 2022/10/15 14:31
*/
public
class
TokenUtil
{
/**
* 用户名
*/
public
static
final
String
USER_NAME
=
"userName"
;
/**
* sessionKey
*/
public
static
final
String
SESSIONKEY
=
"sessionKey"
;
/**
* 过期时长
*/
public
static
final
long
API_TOKEN_EXPIRE_MINUTES
=
120L
;
/**
* 线程变量的键值
*/
private
static
final
String
APP_TOKEN_CONTEXT_KEY
=
"app-token"
;
/**
* 生成Token
* 当前使用HMAC512的加密算法
*
* @return Token
*/
public
static
String
createToken
(
String
username
,
String
sessionKey
)
{
// 设置Token头部(不设置也会默认有这两个值)
Map
<
String
,
Object
>
header
=
new
HashMap
<
String
,
Object
>(
2
)
{
private
static
final
long
serialVersionUID
=
1L
;
{
put
(
"alg"
,
TokenConstant
.
TOKEN_ALG
);
put
(
"typ"
,
TokenConstant
.
TOKEN_TYP
);
}
};
// 设置负载
Map
<
String
,
Object
>
payload
=
new
HashMap
<
String
,
Object
>(
1
)
{
private
static
final
long
serialVersionUID
=
1L
;
{
put
(
USER_NAME
,
username
);
put
(
SESSIONKEY
,
sessionKey
);
}
};
// 过期时间三小时
long
now
=
DateUtil
.
current
();
long
exp
=
now
+
1000
*
API_TOKEN_EXPIRE_MINUTES
*
60
;
return
JWT
.
create
()
// 设置header
.
withHeader
(
header
)
// 设置payload
.
withIssuer
(
TokenConstant
.
TOKEN_ISSUER
)
.
withSubject
(
TokenConstant
.
TOKEN_SUBJECT
)
.
withAudience
(
TokenConstant
.
TOKEN_AUDIENCE
)
.
withNotBefore
(
new
Date
(
now
))
.
withExpiresAt
(
new
Date
(
exp
))
.
withIssuedAt
(
new
Date
(
now
))
.
withJWTId
(
IdUtil
.
fastSimpleUUID
())
.
withPayload
(
payload
)
// 签名
.
sign
(
Algorithm
.
HMAC512
(
TokenConstant
.
TOKEN_SECRET
));
}
/**
* 解析Token
* 当前使用HMAC512的加密算法
*
* @param token Token
*/
public
static
DecodedJWT
parse
(
String
token
)
{
JWTVerifier
jwtVerifier
=
JWT
.
require
(
Algorithm
.
HMAC512
(
TokenConstant
.
TOKEN_SECRET
)).
build
();
return
jwtVerifier
.
verify
(
token
);
}
/**
* 获取用户名
*
* @param decoded 解析后的Token
* @return 用户名
*/
public
static
String
getUserName
(
DecodedJWT
decoded
)
{
return
decoded
.
getClaim
(
USER_NAME
).
asString
();
}
/**
* 获取用户名
*
* @param decoded 解析后的Token
* @return 用户名
*/
public
static
String
getSessionkey
(
DecodedJWT
decoded
)
{
return
decoded
.
getClaim
(
SESSIONKEY
).
asString
();
}
public
static
String
getApiTokenKey
(
String
tokenKey
)
{
return
TokenConstant
.
TOKEN_PREFIX
+
tokenKey
;
}
public
static
DecodedJWT
getUserContext
()
{
return
ThreadContextUtil
.
get
(
APP_TOKEN_CONTEXT_KEY
);
}
public
static
void
setUserContext
(
DecodedJWT
decodedJWT
)
{
if
(
decodedJWT
!=
null
)
{
ThreadContextUtil
.
set
(
APP_TOKEN_CONTEXT_KEY
,
decodedJWT
);
}
}
public
static
void
main
(
String
[]
args
)
{
// String token = createToken("ec3f54b398e64fbeb9ed00bb0144a91b",
// "4c0d6c2a99e541c8a1affc3c8d8dcddb");
String
token
=
"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhcGkiLCJhdWQiOiJhcGkiLCJuYmYiOjE2OTk0OTc1MTIsInNlc3Npb25LZXkiOiI5MjhkMmM0YjE2NTY0YTY3OTkwZjY4ZmE2ZmExZmIxNSIsImlzcyI6IkhXVEoiLCJleHAiOjE2OTk1MDQ3MTIsInVzZXJOYW1lIjoiZWMzZjU0YjM5OGU2NGZiZWI5ZWQwMGJiMDE0NGE5MWIiLCJpYXQiOjE2OTk0OTc1MTIsImp0aSI6ImNmYTBhNzNmYmM0NTRlYmE4NzNlNmY3ODA3ZDE1Y2QyIn0.0ZoVFzZWFYmUEwbymR7M75hbt6RMDlpyWlh9NzmXgwATaPZGsGCFSou5RvjQWa2vKFOOdUV065pXX3uyWJ3c0Q"
;
DecodedJWT
decodedJWT
=
parse
(
token
);
setUserContext
(
decodedJWT
);
DecodedJWT
jwt
=
getUserContext
();
System
.
out
.
println
(
jwt
.
getToken
());
System
.
out
.
println
(
getSessionkey
(
decodedJWT
));
}
}
i
mgprocessing-server/src/main/java/com/zq/processing
/utils/UuidUtils.java
→
i
nterface-server/src/main/java/com/zq/im
/utils/UuidUtils.java
View file @
87efa3b4
package
com
.
zq
.
processing
.
utils
;
package
com
.
zq
.
im
.
utils
;
import
java.util.UUID
;
import
java.util.UUID
;
...
@@ -10,9 +10,6 @@ import java.util.UUID;
...
@@ -10,9 +10,6 @@ import java.util.UUID;
*/
*/
public
class
UuidUtils
{
public
class
UuidUtils
{
private
static
final
String
URN_UUID_REGEX
=
"^[a-f0-9]{8}(-[a-f0-9]{4}){3}-[a-f0-9]{12}$"
;
private
static
final
String
NODASH_UUID_REGEX
=
"^[a-f0-9]{32}$"
;
/**
/**
* Don't let anyone instantiate this class
* Don't let anyone instantiate this class
*/
*/
...
@@ -21,8 +18,6 @@ public class UuidUtils {
...
@@ -21,8 +18,6 @@ public class UuidUtils {
/**
/**
* 返回一个随机的带有分隔符"-"的36位UUID字符串
* 返回一个随机的带有分隔符"-"的36位UUID字符串
*
* @return
*/
*/
public
static
String
uuid
()
{
public
static
String
uuid
()
{
return
UUID
.
randomUUID
().
toString
();
return
UUID
.
randomUUID
().
toString
();
...
@@ -30,32 +25,11 @@ public class UuidUtils {
...
@@ -30,32 +25,11 @@ public class UuidUtils {
/**
/**
* 返回一个随机的没有分隔符"-"的32位UUID字符串
* 返回一个随机的没有分隔符"-"的32位UUID字符串
*
* @return
*/
*/
public
static
String
uuidNoDash
()
{
public
static
String
uuidNoDash
()
{
return
uuid
().
replaceAll
(
"-"
,
""
);
return
uuid
().
replaceAll
(
"-"
,
""
);
}
}
/**
* 判断一个字符串是否是一个UUID字符串
*
* @param str 要进行判断的字符串
* @return
*/
public
static
boolean
isUuid
(
String
str
)
{
return
ValidateUtil
.
isNotBlank
(
str
)
&&
str
.
matches
(
URN_UUID_REGEX
);
}
/**
* 判断一个字符串是否是一个没有分隔符的uuid字符串
*
* @param str 要进行判断的字符串
* @return
*/
public
static
boolean
isNoDashUuid
(
String
str
)
{
return
ValidateUtil
.
isNotBlank
(
str
)
&&
str
.
matches
(
NODASH_UUID_REGEX
);
}
}
}
interface-server/src/main/java/com/zq/im/utils/V1Demo.java
0 → 100644
View file @
87efa3b4
package
com
.
zq
.
im
.
utils
;
import
cn.hutool.core.codec.Base64
;
import
cn.hutool.core.convert.Convert
;
import
cn.hutool.core.date.DatePattern
;
import
cn.hutool.core.date.DateUtil
;
import
cn.hutool.core.io.FileUtil
;
import
cn.hutool.core.net.url.UrlQuery
;
import
cn.hutool.core.util.ArrayUtil
;
import
cn.hutool.core.util.IdUtil
;
import
cn.hutool.core.util.URLUtil
;
import
cn.hutool.crypto.SecureUtil
;
import
cn.hutool.http.HttpRequest
;
import
cn.hutool.http.HttpResponse
;
import
cn.hutool.json.JSONObject
;
import
cn.hutool.json.JSONUtil
;
import
com.zq.im.modules.im.vo.DetectionResVo
;
import
lombok.Data
;
import
java.io.File
;
import
java.io.InputStream
;
import
java.util.Date
;
import
java.util.HashMap
;
import
java.util.Map
;
import
java.util.TreeMap
;
/**
* 使用的请求工具为hutool
* <dependency>
* <groupId>cn.hutool</groupId>
* <artifactId>hutool-all</artifactId>
* <version>${hutool.version}</version>
* </dependency>
*
* @author wilmiam
* @since 2021/12/13 9:21
*/
public
class
V1Demo
{
private
static
final
Map
<
String
,
ApiInfo
>
API_INFO_MAP
=
new
HashMap
<>();
private
static
final
String
APP_ID
=
"ec3f54b398e64fbeb9ed00bb0144a91b"
;
private
static
final
String
APP_SECRET
=
"4c0d6c2a99e541c8a1affc3c8d8dcddb"
;
private
static
final
String
URL
=
"http://127.0.0.1:9900/api/v1/action"
;
public
static
ApiInfo
getApiInfo
()
{
ApiInfo
apiInfo
=
API_INFO_MAP
.
get
(
APP_ID
);
if
(
apiInfo
!=
null
&&
(
apiInfo
.
getDueTime
().
getTime
()
-
3000
)
>
System
.
currentTimeMillis
())
{
return
apiInfo
;
}
Map
<
String
,
Object
>
bizContentMap
=
new
HashMap
<>();
bizContentMap
.
put
(
"appId"
,
APP_ID
);
bizContentMap
.
put
(
"appSecret"
,
APP_SECRET
);
String
apiNo
=
IdUtil
.
simpleUUID
();
String
timestamp
=
DateUtil
.
format
(
DateUtil
.
date
(),
DatePattern
.
PURE_DATETIME_PATTERN
);
//JSON字符串
String
bizContent
=
JSONUtil
.
toJsonStr
(
bizContentMap
);
Map
<
String
,
Object
>
params
=
new
HashMap
<>(
6
);
params
.
put
(
"appId"
,
APP_ID
);
params
.
put
(
"apiNo"
,
apiNo
);
params
.
put
(
"method"
,
"getApiToken"
);
params
.
put
(
"timestamp"
,
timestamp
);
params
.
put
(
"bizContent"
,
URLUtil
.
encode
(
Base64
.
encode
(
bizContent
)));
HttpRequest
request
=
HttpRequest
.
post
(
URL
)
.
contentType
(
"application/x-www-form-urlencoded"
)
.
form
(
params
);
System
.
out
.
println
(
"请求报文 => "
+
UrlQuery
.
of
(
params
));
HttpResponse
execute
=
request
.
execute
();
String
body
=
execute
.
body
();
System
.
out
.
println
(
"响应报文 => "
+
body
);
int
status
=
execute
.
getStatus
();
if
(
status
==
200
)
{
JSONObject
obj
=
JSONUtil
.
parseObj
(
body
);
Integer
code
=
obj
.
getInt
(
"code"
);
if
(
code
==
200
)
{
JSONObject
data
=
obj
.
getJSONObject
(
"data"
);
apiInfo
=
data
.
toBean
(
ApiInfo
.
class
);
apiInfo
.
setDueTime
(
new
Date
(
System
.
currentTimeMillis
()
+
(
data
.
getInt
(
"expireTime"
)
*
1000
)));
API_INFO_MAP
.
put
(
APP_ID
,
apiInfo
);
}
}
return
apiInfo
;
}
public
static
Object
doPost
(
String
method
,
Map
<
String
,
Object
>
bizContentMap
,
String
sessionKey
)
{
return
doPost
(
method
,
bizContentMap
,
sessionKey
,
null
,
null
);
}
public
static
Object
doPost
(
String
method
,
Map
<
String
,
Object
>
bizContentMap
,
String
sessionKey
,
File
file
,
File
[]
fileList
)
{
String
apiNo
=
IdUtil
.
simpleUUID
();
String
timestamp
=
DateUtil
.
format
(
DateUtil
.
date
(),
DatePattern
.
PURE_DATETIME_PATTERN
);
//JSON字符串
String
bizContent
=
bizContentMap
==
null
?
""
:
JSONUtil
.
toJsonStr
(
bizContentMap
);
Map
<
String
,
Object
>
params
=
new
HashMap
<>(
6
);
params
.
put
(
"appId"
,
APP_ID
);
params
.
put
(
"apiNo"
,
apiNo
);
params
.
put
(
"method"
,
method
);
params
.
put
(
"timestamp"
,
timestamp
);
params
.
put
(
"bizContent"
,
URLUtil
.
encode
(
Base64
.
encode
(
bizContent
)));
params
.
put
(
"sign"
,
getSign
(
bizContent
,
method
,
timestamp
,
apiNo
,
sessionKey
,
file
,
fileList
));
params
.
put
(
"file"
,
file
);
params
.
put
(
"fileList"
,
fileList
);
// POST的Content-Type默认是application/x-www-form-urlencoded,有文件则是multipart/form-data
HttpRequest
request
=
HttpRequest
.
post
(
URL
)
.
form
(
params
);
System
.
out
.
println
(
"请求报文 => "
+
UrlQuery
.
of
(
params
));
HttpResponse
execute
=
request
.
execute
();
String
body
=
execute
.
body
();
System
.
out
.
println
(
"响应报文 => "
+
body
);
// 失败 => {"apiNo":"09c1ad82ec0f4b2d80cae0cfb1d7059b","code":"103","msg":"调用方法异常","timestamp":1638176339560,"data":null,"success":false}
// 成功 => {"apiNo":"bc070a7c31ac4b8eb1180b2d82a2096b","code":"200","msg":"成功","timestamp":1638176552353,"data":{"userId":"123","username":"admin@gxfy.com"},"success":true}
int
status
=
execute
.
getStatus
();
if
(
status
==
200
)
{
JSONObject
obj
=
JSONUtil
.
parseObj
(
body
);
Integer
code
=
obj
.
getInt
(
"code"
);
if
(
code
==
200
)
{
return
obj
.
get
(
"data"
);
}
}
return
null
;
}
/**
* 下载文件
*
* @param method
* @param bizContentMap
* @param sessionKey
* @return
*/
public
static
InputStream
download
(
String
method
,
Map
<
String
,
Object
>
bizContentMap
,
String
sessionKey
)
{
String
apiNo
=
IdUtil
.
simpleUUID
();
String
timestamp
=
DateUtil
.
format
(
DateUtil
.
date
(),
DatePattern
.
PURE_DATETIME_PATTERN
);
//JSON字符串
String
bizContent
=
bizContentMap
==
null
?
""
:
JSONUtil
.
toJsonStr
(
bizContentMap
);
Map
<
String
,
Object
>
params
=
new
HashMap
<>(
6
);
params
.
put
(
"appId"
,
APP_ID
);
params
.
put
(
"apiNo"
,
apiNo
);
params
.
put
(
"method"
,
method
);
params
.
put
(
"timestamp"
,
timestamp
);
params
.
put
(
"bizContent"
,
URLUtil
.
encode
(
Base64
.
encode
(
bizContent
)));
params
.
put
(
"sign"
,
getSign
(
bizContent
,
method
,
timestamp
,
apiNo
,
sessionKey
,
null
,
null
));
// POST的Content-Type默认是application/x-www-form-urlencoded,有文件则是multipart/form-data
HttpRequest
request
=
HttpRequest
.
post
(
URL
)
.
form
(
params
);
System
.
out
.
println
(
"请求报文 => "
+
UrlQuery
.
of
(
params
));
HttpResponse
execute
=
request
.
execute
();
if
(!
execute
.
isOk
())
{
String
body
=
execute
.
body
();
System
.
out
.
println
(
"响应报文 => "
+
body
);
return
null
;
}
return
execute
.
bodyStream
();
}
/**
* 获取签名
*
* @param bizContent
* @param method
* @param timestamp
* @param apiNo
* @param sessionKey
* @return
*/
public
static
String
getSign
(
String
bizContent
,
String
method
,
String
timestamp
,
String
apiNo
,
String
sessionKey
,
File
file
,
File
[]
fileList
)
{
// 参与签名的参数排序
TreeMap
<
String
,
String
>
signTreeMap
=
new
TreeMap
<>();
signTreeMap
.
put
(
"apiNo"
,
apiNo
);
signTreeMap
.
put
(
"timestamp"
,
timestamp
);
signTreeMap
.
put
(
"method"
,
method
);
signTreeMap
.
put
(
"bizContent"
,
bizContent
);
if
(
file
!=
null
)
{
signTreeMap
.
put
(
"fileMd5"
,
SecureUtil
.
md5
(
file
));
}
if
(
ArrayUtil
.
isNotEmpty
(
fileList
))
{
StringBuilder
md5
=
new
StringBuilder
();
for
(
File
f
:
fileList
)
{
md5
.
append
(
SecureUtil
.
md5
(
f
));
}
signTreeMap
.
put
(
"fileMd5"
,
md5
.
toString
());
}
// 拼接签名参数
StringBuilder
src
=
new
StringBuilder
();
for
(
Map
.
Entry
<
String
,
String
>
entry
:
signTreeMap
.
entrySet
())
{
src
.
append
(
entry
.
getKey
()).
append
(
"="
).
append
(
entry
.
getValue
()).
append
(
"&"
);
}
src
.
append
(
"key="
).
append
(
sessionKey
);
return
SecureUtil
.
md5
(
src
.
toString
());
}
public
static
void
main
(
String
[]
args
)
{
// 这两个值可以存起来,不需每次都获取
// ApiInfo apiInfo = getApiInfo();
// String sessionKey = apiInfo.getKey();
// System.out.println(sessionKey);
String
sessionKey
=
"9bd298e1e8c544358e592f736b37b0a3"
;
Map
<
String
,
Object
>
params
=
new
HashMap
<>(
2
);
params
.
put
(
"filename"
,
"6.png"
);
params
.
put
(
"fileContent"
,
Base64
.
encode
(
FileUtil
.
file
(
"C:\\Users\\11419\\Desktop\\Deskew\\TestImages\\6.png"
)));
Object
o
=
doPost
(
"detection"
,
params
,
sessionKey
);
DetectionResVo
convert
=
Convert
.
convert
(
DetectionResVo
.
class
,
o
);
System
.
out
.
println
(
JSONUtil
.
toJsonStr
(
convert
));
}
@Data
public
static
class
ApiInfo
{
private
String
userId
;
private
String
username
;
private
String
key
;
private
String
expireTime
;
private
String
unit
;
private
Date
dueTime
;
}
}
interface-server/src/main/resources/application.yml
0 → 100644
View file @
87efa3b4
server
:
port
:
9900
tomcat
:
max-http-post-size
:
-1
#配置数据源
spring
:
application
:
name
:
INTERFACE-SERVER
servlet
:
#上传文件限制
multipart
:
#单个文件大小
max-file-size
:
20MB
#设置总上传的数据大小
max-request-size
:
100MB
mvc
:
hiddenmethod
:
filter
:
enabled
:
true
#启用Servlet的Filter过滤器,需要的地方HttpServletRequestFilter
jackson
:
date-format
:
yyyy-MM-dd HH:mm:ss
time-zone
:
GMT+8
redis
:
#数据库索引
database
:
5
host
:
${redis.url}
port
:
${redis.port}
password
:
${redis.password}
lettuce
:
pool
:
min-idle
:
10
max-active
:
50
max-idle
:
16
max-wait
:
5000
time-between-eviction-runs
:
1s
datasource
:
druid
:
driver-class-name
:
${db.fypt.driver-class-name}
username
:
${db.fypt.username}
password
:
${db.fypt.password}
url
:
${db.fypt.url.image}
initial-size
:
3
# 初始化时建立物理连接的个数
min-idle
:
5
# 最小连接池数量
max-active
:
200
# 最大连接池数量
max-wait
:
5000
# 获取连接时最大等待时间,单位毫秒
time-between-eviction-runs-millis
:
60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
min-evictable-idle-time-millis
:
100000
# 连接保持空闲而不被驱逐的最小时间
max-evictable-idle-time-millis
:
300000
# 连接保持空闲而不被驱逐的最大时间
test-while-idle
:
true
test-on-borrow
:
false
test-on-return
:
false
validation-query
:
select 1
webStatFilter
:
enabled
:
true
#安全配置
stat-view-servlet
:
enabled
:
true
url-pattern
:
/druid/*
reset-enable
:
false
login-username
:
admin
login-password
:
GXfy2021
filter
:
stat
:
enabled
:
true
log-slow-sql
:
true
slow-sql-millis
:
1000
merge-sql
:
true
wall
:
config
:
multi-statement-allow
:
true
# mybatis plus 配置
mybatis-plus
:
global-config
:
db-config
:
select-strategy
:
not_empty
update-strategy
:
not_empty
i
mgprocessing
-server/src/main/resources/bootstrap.yml
→
i
nterface
-server/src/main/resources/bootstrap.yml
View file @
87efa3b4
...
@@ -18,3 +18,13 @@ eureka:
...
@@ -18,3 +18,13 @@ eureka:
client
:
client
:
service-url
:
service-url
:
defaultZone
:
@
eureka.server.url@
defaultZone
:
@
eureka.server.url@
feign
:
client
:
config
:
# 单位ms
default
:
# 建立连接后从服务器读取到可用资源所用的时间
connectTimeout
:
6000
# 建立连接建立所用时间
readTimeout
:
6000
\ No newline at end of file
i
mgprocessing
-server/src/main/resources/logback-spring.xml
→
i
nterface
-server/src/main/resources/logback-spring.xml
View file @
87efa3b4
...
@@ -6,7 +6,7 @@
...
@@ -6,7 +6,7 @@
<include
resource=
"org/springframework/boot/logging/logback/console-appender.xml"
/>
<include
resource=
"org/springframework/boot/logging/logback/console-appender.xml"
/>
<property
name=
"default_log_path"
value=
"logs"
/>
<property
name=
"default_log_path"
value=
"logs"
/>
<property
name=
"default_log_file"
value=
"i
mgproceing
"
/>
<!--主要日志文件名-->
<property
name=
"default_log_file"
value=
"i
nterface
"
/>
<!--主要日志文件名-->
<property
name=
"LOG_PATH"
value=
"${LOG_PATH:-${default_log_path}}"
/>
<property
name=
"LOG_PATH"
value=
"${LOG_PATH:-${default_log_path}}"
/>
<property
name=
"LOG_FILE"
value=
"${LOG_FILE:-${default_log_file}}"
/>
<property
name=
"LOG_FILE"
value=
"${LOG_FILE:-${default_log_file}}"
/>
...
@@ -178,8 +178,8 @@
...
@@ -178,8 +178,8 @@
</appender>
</appender>
<!--ERROR错误日志配置 结束-->
<!--ERROR错误日志配置 结束-->
<logger
name=
"com.zq.
proceing
"
level=
"DEBUG"
/>
<logger
name=
"com.zq.
im
"
level=
"DEBUG"
/>
<logger
name=
"com.zq.
proceing
.dao"
level=
"INFO"
/>
<logger
name=
"com.zq.
im
.dao"
level=
"INFO"
/>
<root
level=
"INFO"
>
<root
level=
"INFO"
>
<appender-ref
ref=
"CONSOLE"
/>
<appender-ref
ref=
"CONSOLE"
/>
<appender-ref
ref=
"main-logger-appender"
/>
<appender-ref
ref=
"main-logger-appender"
/>
...
...
pom.xml
View file @
87efa3b4
...
@@ -26,7 +26,7 @@
...
@@ -26,7 +26,7 @@
<pagehelper.version>
5.2.0
</pagehelper.version>
<pagehelper.version>
5.2.0
</pagehelper.version>
<jjwt.version>
0.9.1
</jjwt.version>
<jjwt.version>
0.9.1
</jjwt.version>
<fastjson.version>
1.2.83
</fastjson.version>
<fastjson.version>
1.2.83
</fastjson.version>
<hutool.version>
5.8.
18
</hutool.version>
<hutool.version>
5.8.
22
</hutool.version>
<logback.version>
1.2.10
</logback.version>
<logback.version>
1.2.10
</logback.version>
<log4j2.version>
2.17.1
</log4j2.version>
<log4j2.version>
2.17.1
</log4j2.version>
...
@@ -36,8 +36,8 @@
...
@@ -36,8 +36,8 @@
<module>
gateway-server
</module>
<module>
gateway-server
</module>
<module>
eureka-server
</module>
<module>
eureka-server
</module>
<module>
config-server
</module>
<module>
config-server
</module>
<module>
imgprocessing-server
</module>
<module>
imgproc-server
</module>
<module>
imgproc-server
</module>
<module>
interface-server
</module>
</modules>
</modules>
<dependencies>
<dependencies>
...
@@ -46,14 +46,6 @@
...
@@ -46,14 +46,6 @@
<artifactId>
spring-boot-starter-test
</artifactId>
<artifactId>
spring-boot-starter-test
</artifactId>
</dependency>
</dependency>
<dependency>
<dependency>
<groupId>
io.springfox
</groupId>
<artifactId>
springfox-swagger2
</artifactId>
</dependency>
<dependency>
<groupId>
io.springfox
</groupId>
<artifactId>
springfox-swagger-ui
</artifactId>
</dependency>
<dependency>
<groupId>
com.alibaba
</groupId>
<groupId>
com.alibaba
</groupId>
<artifactId>
fastjson
</artifactId>
<artifactId>
fastjson
</artifactId>
<version>
${fastjson.version}
</version>
<version>
${fastjson.version}
</version>
...
@@ -73,17 +65,6 @@
...
@@ -73,17 +65,6 @@
<type>
pom
</type>
<type>
pom
</type>
<scope>
import
</scope>
<scope>
import
</scope>
</dependency>
</dependency>
<!--这里的依赖子模块根据需求选择, 使用时不用写版本号-->
<dependency>
<groupId>
io.springfox
</groupId>
<artifactId>
springfox-swagger2
</artifactId>
<version>
${swagger.version}
</version>
</dependency>
<dependency>
<groupId>
io.springfox
</groupId>
<artifactId>
springfox-swagger-ui
</artifactId>
<version>
${swagger.version}
</version>
</dependency>
</dependencies>
</dependencies>
</dependencyManagement>
</dependencyManagement>
...
@@ -99,6 +80,15 @@
...
@@ -99,6 +80,15 @@
<activeByDefault>
true
</activeByDefault>
<activeByDefault>
true
</activeByDefault>
</activation>
</activation>
</profile>
</profile>
<profile>
<!--生产环境-->
<id>
product
</id>
<properties>
<profiles.active>
product
</profiles.active>
<logging.level>
info
</logging.level>
<eureka.server.url>
http://admin:Gxfy2022@147.1.5.135:8800/eureka/
</eureka.server.url>
</properties>
</profile>
</profiles>
</profiles>
</project>
</project>
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment