Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
B
basic-api-boot
概览
概览
详情
活动
周期分析
版本库
存储库
文件
提交
分支
标签
贡献者
分支图
比较
统计图
问题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程表
图表
维基
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
Basic
basic-api-boot
Commits
d4674ff8
提交
d4674ff8
authored
3月 25, 2024
作者:
方治民
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
feat: 优化接口排序配置、限流补充 Token 校验、添加分片上传实现
上级
174f735f
显示空白字符变更
内嵌
并排
正在显示
20 个修改的文件
包含
385 行增加
和
98 行删除
+385
-98
UploadProcessServiceImpl.java
...m/yiring/app/service/upload/UploadProcessServiceImpl.java
+20
-11
HealthController.java
...main/java/com/yiring/app/web/common/HealthController.java
+2
-2
ExampleController.java
...in/java/com/yiring/app/web/example/ExampleController.java
+1
-1
AuthController.java
...rc/main/java/com/yiring/auth/web/auth/AuthController.java
+3
-7
PermissionController.java
.../yiring/auth/web/sys/permission/PermissionController.java
+3
-7
RoleController.java
...ain/java/com/yiring/auth/web/sys/role/RoleController.java
+3
-7
UserController.java
...ain/java/com/yiring/auth/web/sys/user/UserController.java
+7
-8
UserViewController.java
...ain/java/com/yiring/auth/web/user/UserViewController.java
+3
-7
RateLimiter.java
...c/main/java/com/yiring/common/annotation/RateLimiter.java
+1
-0
RateLimiterAspect.java
...main/java/com/yiring/common/aspect/RateLimiterAspect.java
+36
-12
RequestAspect.java
...src/main/java/com/yiring/common/aspect/RequestAspect.java
+2
-9
Contexts.java
.../core/src/main/java/com/yiring/common/utils/Contexts.java
+29
-0
build.gradle
basic-common/minio/build.gradle
+3
-0
UploadChunkParam.java
...c/main/java/com/yiring/common/param/UploadChunkParam.java
+73
-0
UploadProcessService.java
.../java/com/yiring/common/service/UploadProcessService.java
+12
-0
MinioController.java
.../src/main/java/com/yiring/common/web/MinioController.java
+154
-7
FileUtils.java
.../util/src/main/java/com/yiring/common/util/FileUtils.java
+15
-0
DictController.java
...ict/src/main/java/com/yiring/dict/web/DictController.java
+3
-7
DictItemController.java
...src/main/java/com/yiring/dict/web/DictItemController.java
+3
-7
StompReceiver.java
...src/main/java/com/yiring/websocket/web/StompReceiver.java
+12
-6
没有找到文件。
app/src/main/java/com/yiring/app/service/upload/UploadProcessServiceImpl.java
浏览文件 @
d4674ff8
...
...
@@ -10,6 +10,7 @@ import io.minio.ObjectWriteResponse;
import
java.awt.image.BufferedImage
;
import
java.io.ByteArrayInputStream
;
import
java.io.ByteArrayOutputStream
;
import
java.io.File
;
import
java.io.InputStream
;
import
java.nio.charset.StandardCharsets
;
import
java.nio.file.Files
;
...
...
@@ -69,13 +70,23 @@ public class UploadProcessServiceImpl implements UploadProcessService {
// Video/Audio: 在文件名上追加时长,视频生成封面图
if
(
isSupportiveMedia
(
suffix
))
{
object
=
handleMedia
(
object
,
suffix
,
file
);
// 将上传的文件转存一份到本地临时目录
Path
tempFile
=
Paths
.
get
(
FileUtil
.
getTmpDirPath
(),
"T_"
+
Commons
.
uuid
(),
file
.
getOriginalFilename
());
FileUtil
.
mkParentDirs
(
tempFile
);
file
.
transferTo
(
tempFile
);
object
=
handleMedia
(
object
,
suffix
,
tempFile
.
toFile
());
}
return
object
;
}
@SneakyThrows
@Override
public
String
handleMedia
(
String
object
,
File
file
)
{
return
handleMedia
(
object
,
FileUtil
.
getSuffix
(
file
.
getName
()),
file
);
}
@SneakyThrows
public
String
handleImage
(
String
object
,
MultipartFile
file
)
{
@Cleanup
InputStream
is
=
file
.
getInputStream
();
...
...
@@ -111,7 +122,7 @@ public class UploadProcessServiceImpl implements UploadProcessService {
}
@SneakyThrows
public
String
handleMedia
(
String
object
,
String
suffix
,
Multipart
File
file
)
{
public
String
handleMedia
(
String
object
,
String
suffix
,
File
file
)
{
// 判断是否配置 ffmpeg 环境
FFmpeg
ffmpeg
=
new
FFmpeg
();
FFprobe
ffprobe
=
new
FFprobe
();
...
...
@@ -124,13 +135,11 @@ public class UploadProcessServiceImpl implements UploadProcessService {
return
object
;
}
// 将上传的文件转存一份到本地临时目录
Path
tempFile
=
Paths
.
get
(
FileUtil
.
getTmpDirPath
(),
"T_"
+
Commons
.
uuid
(),
file
.
getOriginalFilename
());
FileUtil
.
mkParentDirs
(
tempFile
);
file
.
transferTo
(
tempFile
);
// 获取文件路径
Path
path
=
file
.
toPath
();
// 解析媒体文件
FFmpegProbeResult
probeResult
=
ffprobe
.
probe
(
tempFile
.
toString
());
FFmpegProbeResult
probeResult
=
ffprobe
.
probe
(
path
.
toString
());
FFmpegFormat
format
=
probeResult
.
getFormat
();
// 构建具有时长(秒)标记的存储地址
...
...
@@ -141,16 +150,16 @@ public class UploadProcessServiceImpl implements UploadProcessService {
if
(
isSupportiveVideo
(
suffix
))
{
// 大视频文件切片上传(> 5s)
if
(
sec
>
5
)
{
filepath
=
handleVideoToHls
(
filepath
,
tempFile
,
ffmpeg
,
ffprobe
);
filepath
=
handleVideoToHls
(
filepath
,
path
,
ffmpeg
,
ffprobe
);
}
// 使用 ffmpeg 截取视频首帧图片
Path
path
=
Paths
.
get
(
tempFile
.
getParent
().
toString
(),
FileUtil
.
getName
(
filepath
)
+
".jpg"
);
handleVideoScreenshot
(
tempFile
.
toString
(),
p
ath
.
toString
(),
filepath
,
ffmpeg
,
ffprobe
);
Path
imagePath
=
Paths
.
get
(
path
.
getParent
().
toString
(),
FileUtil
.
getName
(
filepath
)
+
".jpg"
);
handleVideoScreenshot
(
path
.
toString
(),
imageP
ath
.
toString
(),
filepath
,
ffmpeg
,
ffprobe
);
}
// 删除为本次上传进行本地处理所创建的整个临时文件夹目录
FileUtil
.
del
(
tempFile
.
getParent
());
FileUtil
.
del
(
path
.
getParent
());
return
filepath
;
}
...
...
app/src/main/java/com/yiring/app/web/common/HealthController.java
浏览文件 @
d4674ff8
...
...
@@ -24,8 +24,8 @@ import org.springframework.web.bind.annotation.RestController;
*/
@Slf4j
@Validated
@ApiSupport
(
order
=
0
)
@Tag
(
name
=
"
健康指标
"
,
description
=
"Health"
)
@ApiSupport
(
order
=
-
99999
)
@Tag
(
name
=
"
Health
"
,
description
=
"Health"
)
@RequestMapping
(
"/"
)
@RestController
@RequiredArgsConstructor
...
...
app/src/main/java/com/yiring/app/web/example/ExampleController.java
浏览文件 @
d4674ff8
...
...
@@ -50,7 +50,7 @@ import org.springframework.web.multipart.MultipartFile;
@Slf4j
@Validated
@ApiSupport
(
order
=
0
)
@Tag
(
name
=
"示例"
,
description
=
"Example"
)
@Tag
(
name
=
"
【Examples】
示例"
,
description
=
"Example"
)
@RequestMapping
(
"/example/"
)
@RestController
@RequiredArgsConstructor
...
...
basic-auth/src/main/java/com/yiring/auth/web/auth/AuthController.java
浏览文件 @
d4674ff8
...
...
@@ -5,6 +5,7 @@ import cn.dev33.satoken.annotation.SaCheckLogin;
import
cn.dev33.satoken.secure.SaSecureUtil
;
import
cn.dev33.satoken.stp.StpUtil
;
import
cn.hutool.core.util.StrUtil
;
import
com.github.xiaoymin.knife4j.annotations.ApiSupport
;
import
com.yiring.auth.domain.user.User
;
import
com.yiring.auth.domain.user.UserRepository
;
import
com.yiring.auth.param.auth.LoginParam
;
...
...
@@ -17,8 +18,6 @@ import com.yiring.common.core.Result;
import
com.yiring.common.exception.BusinessException
;
import
com.yiring.common.util.Commons
;
import
io.swagger.v3.oas.annotations.Operation
;
import
io.swagger.v3.oas.annotations.extensions.Extension
;
import
io.swagger.v3.oas.annotations.extensions.ExtensionProperty
;
import
io.swagger.v3.oas.annotations.tags.Tag
;
import
jakarta.servlet.http.HttpServletRequest
;
import
java.time.LocalDateTime
;
...
...
@@ -42,11 +41,8 @@ import org.springframework.web.bind.annotation.RestController;
@Slf4j
@Validated
@Tag
(
name
=
"Auth"
,
description
=
"身份认证"
,
extensions
=
{
@Extension
(
properties
=
{
@ExtensionProperty
(
name
=
"x-order"
,
value
=
"-9999"
)
})
}
)
@ApiSupport
(
order
=
-
9999
)
@Tag
(
name
=
"Auth"
,
description
=
"身份认证"
)
@RestController
@RequestMapping
(
"/auth/"
)
@RequiredArgsConstructor
...
...
basic-auth/src/main/java/com/yiring/auth/web/sys/permission/PermissionController.java
浏览文件 @
d4674ff8
...
...
@@ -2,6 +2,7 @@
package
com
.
yiring
.
auth
.
web
.
sys
.
permission
;
import
cn.hutool.core.util.StrUtil
;
import
com.github.xiaoymin.knife4j.annotations.ApiSupport
;
import
com.yiring.auth.domain.permission.Permission
;
import
com.yiring.auth.domain.permission.PermissionRepository
;
import
com.yiring.auth.param.permission.PermissionParam
;
...
...
@@ -16,8 +17,6 @@ import com.yiring.common.utils.RepositoryUtil;
import
com.yiring.common.validation.group.Group
;
import
com.yiring.common.vo.PageVo
;
import
io.swagger.v3.oas.annotations.Operation
;
import
io.swagger.v3.oas.annotations.extensions.Extension
;
import
io.swagger.v3.oas.annotations.extensions.ExtensionProperty
;
import
io.swagger.v3.oas.annotations.tags.Tag
;
import
java.util.ArrayList
;
import
java.util.List
;
...
...
@@ -41,11 +40,8 @@ import org.springframework.web.bind.annotation.*;
@Slf4j
@Validated
@Tag
(
name
=
"权限管理"
,
description
=
"Permission"
,
extensions
=
{
@Extension
(
properties
=
{
@ExtensionProperty
(
name
=
"x-order"
,
value
=
"-99"
)
})
}
)
@ApiSupport
(
order
=
-
99
)
@Tag
(
name
=
"权限管理"
,
description
=
"Permission"
)
@RestController
@RequestMapping
(
"/sys/permission/"
)
@RequiredArgsConstructor
...
...
basic-auth/src/main/java/com/yiring/auth/web/sys/role/RoleController.java
浏览文件 @
d4674ff8
/* (C) 2022 YiRing, Inc. */
package
com
.
yiring
.
auth
.
web
.
sys
.
role
;
import
com.github.xiaoymin.knife4j.annotations.ApiSupport
;
import
com.yiring.auth.domain.permission.Permission
;
import
com.yiring.auth.domain.permission.PermissionRepository
;
import
com.yiring.auth.domain.role.Role
;
...
...
@@ -17,8 +18,6 @@ import com.yiring.common.utils.RepositoryUtil;
import
com.yiring.common.validation.group.Group
;
import
com.yiring.common.vo.PageVo
;
import
io.swagger.v3.oas.annotations.Operation
;
import
io.swagger.v3.oas.annotations.extensions.Extension
;
import
io.swagger.v3.oas.annotations.extensions.ExtensionProperty
;
import
io.swagger.v3.oas.annotations.tags.Tag
;
import
java.io.Serializable
;
import
java.util.*
;
...
...
@@ -44,11 +43,8 @@ import org.springframework.web.bind.annotation.RestController;
@Slf4j
@Validated
@Tag
(
name
=
"角色管理"
,
description
=
"Role"
,
extensions
=
{
@Extension
(
properties
=
{
@ExtensionProperty
(
name
=
"x-order"
,
value
=
"-98"
)
})
}
)
@ApiSupport
(
order
=
-
98
)
@Tag
(
name
=
"角色管理"
,
description
=
"Role"
)
@RestController
@RequestMapping
(
"/sys/role/"
)
@RequiredArgsConstructor
...
...
basic-auth/src/main/java/com/yiring/auth/web/sys/user/UserController.java
浏览文件 @
d4674ff8
/* (C) 2022 YiRing, Inc. */
package
com
.
yiring
.
auth
.
web
.
sys
.
user
;
import
com.github.xiaoymin.knife4j.annotations.ApiSupport
;
import
com.yiring.auth.domain.role.Role
;
import
com.yiring.auth.domain.role.RoleRepository
;
import
com.yiring.auth.domain.user.User
;
...
...
@@ -15,11 +16,12 @@ import com.yiring.common.utils.RepositoryUtil;
import
com.yiring.common.utils.Specifications
;
import
com.yiring.common.vo.PageVo
;
import
io.swagger.v3.oas.annotations.Operation
;
import
io.swagger.v3.oas.annotations.extensions.Extension
;
import
io.swagger.v3.oas.annotations.extensions.ExtensionProperty
;
import
io.swagger.v3.oas.annotations.tags.Tag
;
import
java.io.Serializable
;
import
java.util.*
;
import
java.util.Collection
;
import
java.util.HashSet
;
import
java.util.List
;
import
java.util.Set
;
import
java.util.stream.Collectors
;
import
lombok.RequiredArgsConstructor
;
import
lombok.extern.slf4j.Slf4j
;
...
...
@@ -41,11 +43,8 @@ import org.springframework.web.bind.annotation.RestController;
@Slf4j
@Validated
@Tag
(
name
=
"用户管理"
,
description
=
"User"
,
extensions
=
{
@Extension
(
properties
=
{
@ExtensionProperty
(
name
=
"x-order"
,
value
=
"-97"
)
})
}
)
@ApiSupport
(
order
=
-
97
)
@Tag
(
name
=
"用户管理"
,
description
=
"User"
)
@RestController
@RequestMapping
(
"/sys/user/"
)
@RequiredArgsConstructor
...
...
basic-auth/src/main/java/com/yiring/auth/web/user/UserViewController.java
浏览文件 @
d4674ff8
/* (C) 2022 YiRing, Inc. */
package
com
.
yiring
.
auth
.
web
.
user
;
import
com.github.xiaoymin.knife4j.annotations.ApiSupport
;
import
com.yiring.auth.domain.permission.Permission
;
import
com.yiring.auth.domain.permission.PermissionRepository
;
import
com.yiring.auth.domain.user.User
;
...
...
@@ -10,8 +11,6 @@ import com.yiring.auth.vo.permission.MenuVo;
import
com.yiring.auth.vo.user.UserInfoVo
;
import
com.yiring.common.core.Result
;
import
io.swagger.v3.oas.annotations.Operation
;
import
io.swagger.v3.oas.annotations.extensions.Extension
;
import
io.swagger.v3.oas.annotations.extensions.ExtensionProperty
;
import
io.swagger.v3.oas.annotations.tags.Tag
;
import
java.util.ArrayList
;
import
java.util.List
;
...
...
@@ -33,11 +32,8 @@ import org.springframework.web.bind.annotation.RestController;
@Slf4j
@Validated
@Tag
(
name
=
"用户信息"
,
description
=
"UserView"
,
extensions
=
{
@Extension
(
properties
=
{
@ExtensionProperty
(
name
=
"x-order"
,
value
=
"-9998"
)
})
}
)
@ApiSupport
(
order
=
-
9998
)
@Tag
(
name
=
"用户信息"
,
description
=
"UserView"
)
@RestController
@RequestMapping
(
"/user/"
)
@RequiredArgsConstructor
...
...
basic-common/core/src/main/java/com/yiring/common/annotation/RateLimiter.java
浏览文件 @
d4674ff8
...
...
@@ -8,6 +8,7 @@ import java.lang.annotation.Target;
/**
* 流控注解
* eg: time = 3, count = 1 表示 3 秒内只能请求一次
*
* @author Jim
* @version 0.1
...
...
basic-common/core/src/main/java/com/yiring/common/aspect/RateLimiterAspect.java
浏览文件 @
d4674ff8
/* (C) 2023 YiRing, Inc. */
package
com
.
yiring
.
common
.
aspect
;
import
cn.hutool.core.util.StrUtil
;
import
com.yiring.common.annotation.RateLimiter
;
import
com.yiring.common.core.Redis
;
import
com.yiring.common.core.Status
;
import
com.yiring.common.util.Commons
;
import
com.yiring.common.utils.Contexts
;
import
jakarta.servlet.http.Cookie
;
import
jakarta.servlet.http.HttpServletRequest
;
import
java.lang.reflect.Method
;
import
java.util.Arrays
;
import
java.util.Objects
;
import
java.util.concurrent.TimeUnit
;
import
lombok.RequiredArgsConstructor
;
...
...
@@ -15,10 +19,9 @@ import org.aspectj.lang.JoinPoint;
import
org.aspectj.lang.annotation.Aspect
;
import
org.aspectj.lang.annotation.Before
;
import
org.aspectj.lang.reflect.MethodSignature
;
import
org.springframework.core.env.Environment
;
import
org.springframework.data.redis.core.ZSetOperations
;
import
org.springframework.stereotype.Component
;
import
org.springframework.web.context.request.RequestAttributes
;
import
org.springframework.web.context.request.RequestContextHolder
;
/**
* 流控切面
...
...
@@ -34,6 +37,7 @@ import org.springframework.web.context.request.RequestContextHolder;
public
class
RateLimiterAspect
{
final
Redis
redis
;
final
Environment
environment
;
/**
* 带有注解的方法之前执行
...
...
@@ -73,21 +77,41 @@ public class RateLimiterAspect {
*/
private
String
getRateLimiterKey
(
String
prefixKey
,
JoinPoint
point
)
{
StringBuilder
sb
=
new
StringBuilder
(
prefixKey
);
HttpServletRequest
request
=
getRequest
();
HttpServletRequest
request
=
Contexts
.
getRequest
();
// 获取请求的 IP 地址
sb
.
append
(
Commons
.
getClientIpAddress
(
request
));
// 考虑登录用户的 token
String
tokenName
=
environment
.
getProperty
(
"sa-token.token-name"
);
if
(
StrUtil
.
isNotBlank
(
tokenName
))
{
// 1. 优先从请求头中获取 token
String
token
=
request
.
getHeader
(
tokenName
);
// 2. 其次从 cookie 中获取 token
if
(
StrUtil
.
isBlank
(
token
))
{
token
=
Arrays
.
stream
(
request
.
getCookies
())
.
filter
(
Objects:
:
nonNull
)
.
filter
(
cookie
->
tokenName
.
equals
(
cookie
.
getName
()))
.
map
(
Cookie:
:
getValue
)
.
findFirst
()
.
orElse
(
null
);
}
if
(
StrUtil
.
isBlank
(
token
))
{
// 3. 再其次从请求参数中获取 token
token
=
request
.
getParameter
(
tokenName
);
}
// 设置 token 作为 key 的一部分
if
(
StrUtil
.
isNotBlank
(
token
))
{
sb
.
append
(
"_"
).
append
(
token
);
}
}
// 获取类名和方法名
MethodSignature
signature
=
(
MethodSignature
)
point
.
getSignature
();
Method
method
=
signature
.
getMethod
();
Class
<?>
targetClass
=
method
.
getDeclaringClass
();
return
sb
.
append
(
"_"
).
append
(
targetClass
.
getName
()).
append
(
"_"
).
append
(
method
.
getName
()).
toString
();
}
/**
* 获取 HttpServletRequest
*/
private
HttpServletRequest
getRequest
()
{
RequestAttributes
requestAttributes
=
RequestContextHolder
.
getRequestAttributes
();
assert
requestAttributes
!=
null
;
return
(
HttpServletRequest
)
requestAttributes
.
resolveReference
(
RequestAttributes
.
REFERENCE_REQUEST
);
}
}
basic-common/core/src/main/java/com/yiring/common/aspect/RequestAspect.java
浏览文件 @
d4674ff8
...
...
@@ -7,6 +7,7 @@ import com.alibaba.fastjson2.JSONWriter;
import
com.yiring.common.constant.DateFormatter
;
import
com.yiring.common.core.Result
;
import
com.yiring.common.util.Commons
;
import
com.yiring.common.utils.Contexts
;
import
jakarta.servlet.http.HttpServletRequest
;
import
java.time.LocalDateTime
;
import
java.util.List
;
...
...
@@ -17,8 +18,6 @@ import org.aspectj.lang.annotation.Aspect;
import
org.aspectj.lang.annotation.Pointcut
;
import
org.springframework.beans.factory.annotation.Value
;
import
org.springframework.stereotype.Component
;
import
org.springframework.web.context.request.RequestAttributes
;
import
org.springframework.web.context.request.RequestContextHolder
;
/**
* 请求接口切面,记录接口耗时相关
...
...
@@ -47,7 +46,7 @@ public class RequestAspect {
@Around
(
"apiPointCut()"
)
public
Object
around
(
ProceedingJoinPoint
point
)
throws
Throwable
{
HttpServletRequest
request
=
getRequest
();
HttpServletRequest
request
=
Contexts
.
getRequest
();
// 放行白名单
for
(
String
path
:
IGNORE_LIST
)
{
if
(
request
.
getServletPath
().
startsWith
(
path
))
{
...
...
@@ -108,10 +107,4 @@ public class RequestAspect {
return
result
;
}
private
HttpServletRequest
getRequest
()
{
RequestAttributes
requestAttributes
=
RequestContextHolder
.
getRequestAttributes
();
assert
requestAttributes
!=
null
;
return
(
HttpServletRequest
)
requestAttributes
.
resolveReference
(
RequestAttributes
.
REFERENCE_REQUEST
);
}
}
basic-common/core/src/main/java/com/yiring/common/utils/Contexts.java
0 → 100644
浏览文件 @
d4674ff8
/* (C) 2024 YiRing, Inc. */
package
com
.
yiring
.
common
.
utils
;
import
jakarta.servlet.http.HttpServletRequest
;
import
lombok.experimental.UtilityClass
;
import
org.springframework.web.context.request.RequestAttributes
;
import
org.springframework.web.context.request.RequestContextHolder
;
/**
* 作用域工具类
*
* @author Jim
* @version 0.1
*/
@UtilityClass
public
class
Contexts
{
/**
* 获取当前请求
*
* @return HttpServletRequest
*/
public
HttpServletRequest
getRequest
()
{
RequestAttributes
requestAttributes
=
RequestContextHolder
.
getRequestAttributes
();
assert
requestAttributes
!=
null
;
return
(
HttpServletRequest
)
requestAttributes
.
resolveReference
(
RequestAttributes
.
REFERENCE_REQUEST
);
}
}
basic-common/minio/build.gradle
浏览文件 @
d4674ff8
...
...
@@ -18,4 +18,7 @@ dependencies {
// hutool
implementation
"cn.hutool:hutool-core:${hutoolVersion}"
// fastjson
implementation
"com.alibaba.fastjson2:fastjson2:${fastJsonVersion}"
}
basic-common/minio/src/main/java/com/yiring/common/param/UploadChunkParam.java
0 → 100644
浏览文件 @
d4674ff8
/* (C) 2023 YiRing, Inc. */
package
com
.
yiring
.
common
.
param
;
import
io.swagger.v3.oas.annotations.media.Schema
;
import
jakarta.validation.constraints.Min
;
import
jakarta.validation.constraints.NotEmpty
;
import
java.io.Serial
;
import
java.io.Serializable
;
import
lombok.*
;
import
lombok.experimental.FieldDefaults
;
import
org.springframework.validation.annotation.Validated
;
/**
* 上传分片参数
*
* @author Jim
* @version 0.1
* 2023/7/27 18:16
*/
@Schema
(
name
=
"UploadChunkParam"
,
description
=
"分片上传参数"
)
@Validated
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@FieldDefaults
(
level
=
AccessLevel
.
PRIVATE
)
public
class
UploadChunkParam
implements
Serializable
{
@Serial
private
static
final
long
serialVersionUID
=
3118356781129185420L
;
@Schema
(
description
=
"文件名"
,
defaultValue
=
"test.png"
,
example
=
"test.png"
)
@NotEmpty
(
message
=
"文件名不能为空"
)
String
name
;
@Schema
(
description
=
"文件大小"
,
defaultValue
=
"1024"
,
example
=
"1024"
,
type
=
"long"
)
@NotEmpty
(
message
=
"文件大小不能为空"
)
@Min
(
0
)
Long
size
;
@Schema
(
description
=
"文件类型"
,
defaultValue
=
"image/png"
,
example
=
"image/png"
)
@NotEmpty
(
message
=
"文件类型不能为空"
)
String
type
;
@Schema
(
description
=
"文件扩展"
,
defaultValue
=
"png"
,
example
=
"png"
)
@NotEmpty
(
message
=
"文件扩展不能为空"
)
String
ext
;
@Schema
(
description
=
"文件 md5"
,
defaultValue
=
"md5"
,
example
=
"md5"
)
@NotEmpty
(
message
=
"文件 md5不能为空"
)
String
md5
;
@Schema
(
description
=
"文件分片总数"
,
defaultValue
=
"10"
,
example
=
"10"
,
type
=
"integer"
)
@NotEmpty
(
message
=
"文件分片总数不能为空"
)
@Min
(
1
)
Integer
chunks
;
@Schema
(
description
=
"当前分片"
,
defaultValue
=
"1"
,
example
=
"1"
,
type
=
"integer"
)
@NotEmpty
(
message
=
"当前分片不能为空"
)
@Min
(
0
)
Integer
chunkIndex
;
@Schema
(
description
=
"分片文件大小"
,
defaultValue
=
"1024"
,
example
=
"1024"
,
type
=
"long"
)
@NotEmpty
(
message
=
"分片文件大小不能为空"
)
@Min
(
0
)
Long
chunkSize
;
/**
* 可以记录与业务相关的数据标识等描述信息
*/
@Schema
(
description
=
"自定义扩展参数"
)
String
extra
;
}
basic-common/minio/src/main/java/com/yiring/common/service/UploadProcessService.java
浏览文件 @
d4674ff8
/* (C) 2022 YiRing, Inc. */
package
com
.
yiring
.
common
.
service
;
import
java.io.File
;
import
org.springframework.web.multipart.MultipartFile
;
/**
...
...
@@ -25,4 +26,15 @@ public interface UploadProcessService {
default
String
handle
(
String
object
,
MultipartFile
file
)
{
return
object
;
}
/**
* 对媒体文件进行预处理,例如: 音视频
*
* @param object 文件存储对象
* @param file 文件
* @return 预处理后的文件地址
*/
default
String
handleMedia
(
String
object
,
File
file
)
{
return
object
;
}
}
basic-common/minio/src/main/java/com/yiring/common/web/MinioController.java
浏览文件 @
d4674ff8
...
...
@@ -2,13 +2,21 @@
package
com
.
yiring
.
common
.
web
;
import
cn.hutool.core.io.FileUtil
;
import
cn.hutool.core.io.IoUtil
;
import
cn.hutool.core.util.IdUtil
;
import
cn.hutool.core.util.StrUtil
;
import
com.alibaba.fastjson2.JSONObject
;
import
com.github.xiaoymin.knife4j.annotations.ApiSupport
;
import
com.yiring.common.annotation.DownloadResponse
;
import
com.yiring.common.config.MinioConfig
;
import
com.yiring.common.constant.DateFormatter
;
import
com.yiring.common.core.Minio
;
import
com.yiring.common.core.Result
;
import
com.yiring.common.core.Status
;
import
com.yiring.common.param.DownloadParam
;
import
com.yiring.common.param.UploadChunkParam
;
import
com.yiring.common.service.FileManageService
;
import
com.yiring.common.service.UploadProcessService
;
import
com.yiring.common.util.FileUtils
;
import
com.yiring.common.vo.ImageInfo
;
import
io.minio.GetObjectResponse
;
...
...
@@ -16,16 +24,29 @@ import io.minio.StatObjectResponse;
import
io.swagger.v3.oas.annotations.Operation
;
import
io.swagger.v3.oas.annotations.Parameter
;
import
io.swagger.v3.oas.annotations.enums.ParameterIn
;
import
io.swagger.v3.oas.annotations.extensions.Extension
;
import
io.swagger.v3.oas.annotations.extensions.ExtensionProperty
;
import
io.swagger.v3.oas.annotations.tags.Tag
;
import
jakarta.servlet.http.HttpServletResponse
;
import
jakarta.validation.constraints.NotBlank
;
import
java.io.ByteArrayInputStream
;
import
java.io.File
;
import
java.io.FileOutputStream
;
import
java.io.InputStream
;
import
java.nio.charset.StandardCharsets
;
import
java.nio.file.Files
;
import
java.nio.file.Path
;
import
java.nio.file.Paths
;
import
java.sql.Timestamp
;
import
java.time.LocalDateTime
;
import
java.util.List
;
import
java.util.Objects
;
import
java.util.stream.Stream
;
import
lombok.Cleanup
;
import
lombok.RequiredArgsConstructor
;
import
lombok.SneakyThrows
;
import
lombok.extern.slf4j.Slf4j
;
import
org.springdoc.core.annotations.ParameterObject
;
import
org.springframework.http.MediaType
;
import
org.springframework.util.FileCopyUtils
;
import
org.springframework.validation.annotation.Validated
;
import
org.springframework.web.bind.annotation.*
;
import
org.springframework.web.multipart.MultipartFile
;
...
...
@@ -38,17 +59,16 @@ import org.springframework.web.multipart.MultipartFile;
@Slf4j
@Validated
@Tag
(
name
=
"文件管理"
,
description
=
"file"
,
extensions
=
{
@Extension
(
properties
=
{
@ExtensionProperty
(
name
=
"x-order"
,
value
=
"-9997"
)
})
}
)
@ApiSupport
(
order
=
-
9997
)
@Tag
(
name
=
"文件管理"
,
description
=
"file"
)
@RequiredArgsConstructor
@RestController
@RequestMapping
(
"/common/file/"
)
public
class
MinioController
{
final
Minio
minio
;
final
MinioConfig
config
;
final
UploadProcessService
service
;
final
FileManageService
fileManageService
;
/**
...
...
@@ -66,6 +86,133 @@ public class MinioController {
}
}
/**
* 文件分片上传
* 说明:此接口需要前端配合切片和处理上传逻辑,后端只负责接收和合并
*/
@SneakyThrows
@Operation
(
summary
=
"文件分片上传"
)
@PostMapping
(
value
=
"uploadChunk"
,
consumes
=
MediaType
.
MULTIPART_FORM_DATA_VALUE
)
public
Result
<
String
>
chunk
(
@ParameterObject
@Validated
UploadChunkParam
chunkParam
,
@Parameter
(
name
=
"分片文件"
,
required
=
true
)
@RequestPart
(
"chunk"
)
MultipartFile
chunk
)
{
// 获取临时目录
String
base
=
"Upload"
;
String
tmpDir
=
System
.
getProperty
(
"java.io.tmpdir"
);
// 存储桶
String
bucket
=
config
.
getBucket
();
// 获取文件临时目录
Path
fileTempDir
=
Paths
.
get
(
tmpDir
,
base
,
chunkParam
.
getMd5
());
if
(!
fileTempDir
.
toFile
().
exists
())
{
// 文件临时目录不存在则创建
Files
.
createDirectories
(
fileTempDir
);
}
// 获取分片文件路径
String
partSuffix
=
".part"
;
String
chunkFileName
=
chunkParam
.
getName
()
+
"_"
+
chunkParam
.
getChunkIndex
()
+
partSuffix
;
String
chunkFilePath
=
Paths
.
get
(
fileTempDir
.
toString
(),
chunkFileName
).
toString
();
boolean
isLastChunk
=
chunkParam
.
getChunkIndex
().
equals
(
chunkParam
.
getChunks
()
-
1
);
// 校验分片文件是否存在
File
chunkFile
=
new
File
(
chunkFilePath
);
if
(!
isLastChunk
&&
chunkFile
.
exists
()
&&
chunkFile
.
length
()
==
chunkParam
.
getChunkSize
())
{
// 分片文件已存在,直接返回
return
Result
.
ok
();
}
// 保存分片文件
chunk
.
transferTo
(
chunkFile
);
// 校验分片文件是否为最后一个分片
if
(
isLastChunk
)
{
// 获取目录下所有分片文件
List
<
File
>
parts
=
Stream
.
of
(
FileUtil
.
ls
(
fileTempDir
.
toString
()))
.
filter
(
temp
->
temp
.
getName
().
endsWith
(
partSuffix
))
.
sorted
((
a
,
b
)
->
{
// 按照分片索引排序
String
aIndex
=
a
.
getName
().
replaceAll
(
".*_(\\d+)\\"
+
partSuffix
,
"$1"
);
String
bIndex
=
b
.
getName
().
replaceAll
(
".*_(\\d+)\\"
+
partSuffix
,
"$1"
);
return
Integer
.
compare
(
Integer
.
parseInt
(
aIndex
),
Integer
.
parseInt
(
bIndex
));
})
.
toList
();
// 校验分片数是否一致
if
(
parts
.
size
()
!=
chunkParam
.
getChunks
())
{
// 文件分片数不一致,无法合并
throw
Status
.
BAD_REQUEST
.
exception
();
}
// 合并后的文件
String
object
=
StrUtil
.
join
(
"/"
,
base
,
chunkParam
.
getMd5
(),
chunkParam
.
getName
());
// 获取合并后的文件
File
file
=
Paths
.
get
(
fileTempDir
.
toString
(),
chunkParam
.
getName
()).
toFile
();
if
(
file
.
exists
())
{
// 合并后的文件已存在,校验 MD5 是否一致
if
(
Objects
.
equals
(
chunkParam
.
getMd5
(),
FileUtils
.
getFileMd5
(
file
)))
{
// MD5 一致
GetObjectResponse
response
=
minio
.
getObject
(
bucket
,
object
+
".info"
);
JSONObject
info
=
JSONObject
.
parseObject
(
IoUtil
.
read
(
response
,
StandardCharsets
.
UTF_8
));
return
Result
.
ok
(
minio
.
getURI
(
info
.
getString
(
"targetObject"
),
bucket
));
}
}
// 循环以追加的方式合并分片文件
@Cleanup
FileOutputStream
outputStream
=
new
FileOutputStream
(
file
);
for
(
File
part
:
parts
)
{
byte
[]
chunkData
=
FileCopyUtils
.
copyToByteArray
(
part
);
outputStream
.
write
(
chunkData
);
}
// 合并完成后上传文件
minio
.
putObject
(
file
,
object
);
// 上传文件描述信息
String
infoObject
=
object
+
".info"
;
JSONObject
info
=
new
JSONObject
();
info
.
put
(
"name"
,
chunkParam
.
getName
());
info
.
put
(
"size"
,
chunkParam
.
getSize
());
info
.
put
(
"type"
,
chunkParam
.
getType
());
info
.
put
(
"ext"
,
chunkParam
.
getExt
());
info
.
put
(
"md5"
,
chunkParam
.
getMd5
());
info
.
put
(
"chunks"
,
chunkParam
.
getChunks
());
info
.
put
(
"extra"
,
chunkParam
.
getExtra
());
info
.
put
(
"sourceObject"
,
object
);
// Video/Audio: 在文件名上追加时长,视频生成封面图
object
=
service
.
handleMedia
(
object
,
file
);
// 补充转换后的文件信息
info
.
put
(
"targetObject"
,
object
);
info
.
put
(
"datetime"
,
LocalDateTime
.
now
().
format
(
DateFormatter
.
DATE_TIME
));
// 上传文件描述信息
@Cleanup
InputStream
infoInputStream
=
new
ByteArrayInputStream
(
info
.
toJSONString
().
getBytes
(
StandardCharsets
.
UTF_8
)
);
minio
.
putObject
(
infoInputStream
,
MediaType
.
APPLICATION_JSON_VALUE
,
infoObject
);
try
{
// 删除临时文件
FileUtil
.
del
(
fileTempDir
.
toFile
());
}
catch
(
Exception
e
)
{
// 删除失败,设置为在 JVM 退出时删除
fileTempDir
.
toFile
().
deleteOnExit
();
}
// 返回文件路径
return
Result
.
ok
(
minio
.
getURI
(
object
,
bucket
));
}
// 非合并操作时,仅返回成功
return
Result
.
ok
();
}
@Operation
(
summary
=
"Base64 图片上传"
)
@Parameter
(
name
=
"base64Image"
,
...
...
basic-common/util/src/main/java/com/yiring/common/util/FileUtils.java
浏览文件 @
d4674ff8
...
...
@@ -12,9 +12,11 @@ import java.nio.charset.StandardCharsets;
import
java.nio.file.Files
;
import
java.nio.file.attribute.BasicFileAttributes
;
import
javax.imageio.ImageIO
;
import
lombok.Cleanup
;
import
lombok.SneakyThrows
;
import
lombok.experimental.UtilityClass
;
import
org.springframework.http.HttpHeaders
;
import
org.springframework.util.DigestUtils
;
import
org.springframework.util.FileCopyUtils
;
/**
...
...
@@ -116,4 +118,17 @@ public class FileUtils {
.
contentType
(
contentType
)
.
build
();
}
/**
* 获取文件 MD5
*
* @param file 文件
* @return MD5 Hash
*/
@SneakyThrows
public
String
getFileMd5
(
File
file
)
{
@Cleanup
FileInputStream
fis
=
new
FileInputStream
(
file
);
return
DigestUtils
.
md5DigestAsHex
(
fis
);
}
}
basic-dict/src/main/java/com/yiring/dict/web/DictController.java
浏览文件 @
d4674ff8
/* (C) 2023 YiRing, Inc. */
package
com
.
yiring
.
dict
.
web
;
import
com.github.xiaoymin.knife4j.annotations.ApiSupport
;
import
com.yiring.common.core.Result
;
import
com.yiring.common.exception.BusinessException
;
import
com.yiring.common.param.IdParam
;
...
...
@@ -17,8 +18,6 @@ import com.yiring.dict.domain.DictRepository;
import
com.yiring.dict.param.DictParam
;
import
com.yiring.dict.vo.DictVo
;
import
io.swagger.v3.oas.annotations.Operation
;
import
io.swagger.v3.oas.annotations.extensions.Extension
;
import
io.swagger.v3.oas.annotations.extensions.ExtensionProperty
;
import
io.swagger.v3.oas.annotations.tags.Tag
;
import
java.util.ArrayList
;
import
java.util.List
;
...
...
@@ -45,11 +44,8 @@ import org.springframework.web.bind.annotation.RestController;
@Slf4j
@Validated
@Tag
(
name
=
"字典管理"
,
description
=
"Dict"
,
extensions
=
{
@Extension
(
properties
=
{
@ExtensionProperty
(
name
=
"x-order"
,
value
=
"-96"
)
})
}
)
@ApiSupport
(
order
=
-
96
)
@Tag
(
name
=
"字典管理"
,
description
=
"Dict"
)
@RestController
@RequestMapping
(
"/sys/dict/"
)
@RequiredArgsConstructor
...
...
basic-dict/src/main/java/com/yiring/dict/web/DictItemController.java
浏览文件 @
d4674ff8
...
...
@@ -2,6 +2,7 @@
package
com
.
yiring
.
dict
.
web
;
import
cn.hutool.core.util.StrUtil
;
import
com.github.xiaoymin.knife4j.annotations.ApiSupport
;
import
com.yiring.common.core.Result
;
import
com.yiring.common.domain.BasicEntity
;
import
com.yiring.common.exception.BusinessException
;
...
...
@@ -21,8 +22,6 @@ import com.yiring.dict.param.DictItemParam;
import
com.yiring.dict.param.SelectorDictItemParam
;
import
com.yiring.dict.vo.DictItemVo
;
import
io.swagger.v3.oas.annotations.Operation
;
import
io.swagger.v3.oas.annotations.extensions.Extension
;
import
io.swagger.v3.oas.annotations.extensions.ExtensionProperty
;
import
io.swagger.v3.oas.annotations.tags.Tag
;
import
java.util.ArrayList
;
import
java.util.List
;
...
...
@@ -50,11 +49,8 @@ import org.springframework.web.bind.annotation.RestController;
@Slf4j
@Validated
@Tag
(
name
=
"字典选项管理"
,
description
=
"DictItem"
,
extensions
=
{
@Extension
(
properties
=
{
@ExtensionProperty
(
name
=
"x-order"
,
value
=
"-95"
)
})
}
)
@ApiSupport
(
order
=
-
95
)
@Tag
(
name
=
"字典选项管理"
,
description
=
"DictItem"
)
@RestController
@RequestMapping
(
"/sys/dict/item/"
)
@RequiredArgsConstructor
...
...
basic-websocket/src/main/java/com/yiring/websocket/web/StompReceiver.java
浏览文件 @
d4674ff8
/* (C) 2022 YiRing, Inc. */
package
com
.
yiring
.
websocket
.
web
;
import
cn.hutool.core.lang.UUID
;
import
com.alibaba.fastjson2.JSON
;
import
com.alibaba.fastjson2.JSONObject
;
import
com.yiring.auth.domain.user.User
;
import
com.yiring.auth.util.Auths
;
import
com.yiring.common.constant.DateFormatter
;
import
com.yiring.common.core.Result
;
import
com.yiring.common.core.Status
;
import
com.yiring.websocket.domain.StompPrincipal
;
import
com.yiring.websocket.registry.StompUserRegistry
;
import
java.time.LocalDateTime
;
import
java.util.Objects
;
import
java.util.Set
;
import
lombok.RequiredArgsConstructor
;
...
...
@@ -43,7 +45,7 @@ public class StompReceiver {
*
* @param accessor StompHeaderAccessor
*/
@MessageMapping
(
"
/
login"
)
@MessageMapping
(
"login"
)
public
void
login
(
StompHeaderAccessor
accessor
,
String
token
)
{
try
{
User
user
=
auths
.
getUserByToken
(
token
);
...
...
@@ -68,7 +70,7 @@ public class StompReceiver {
*
* @param accessor 访问器
*/
@MessageMapping
(
"
/
state"
)
@MessageMapping
(
"state"
)
public
void
state
(
StompHeaderAccessor
accessor
,
String
message
)
{
log
.
info
(
"收到来自 STOMP Client `/app/state` 消息:{}"
,
message
);
...
...
@@ -81,17 +83,21 @@ public class StompReceiver {
stompUserRegistry
.
updateUser
(
accessor
.
getSessionId
(),
principal
);
}
@MessageMapping
(
"
/test
"
)
@MessageMapping
(
"
ping
"
)
public
void
test
(
StompHeaderAccessor
accessor
,
String
message
)
{
log
.
info
(
"收到来自 STOMP Client `/app/
test
` 消息:{}"
,
message
);
log
.
info
(
"收到来自 STOMP Client `/app/
ping
` 消息:{}"
,
message
);
Set
<
SimpUser
>
users
=
simpUserRegistry
.
getUsers
();
log
.
info
(
"{}"
,
users
);
JSONObject
body
=
new
JSONObject
();
body
.
put
(
"message"
,
"pong"
);
body
.
put
(
"time"
,
DateFormatter
.
DATE_TIME
.
format
(
LocalDateTime
.
now
()));
simpMessagingTemplate
.
convertAndSendToUser
(
Objects
.
requireNonNull
(
accessor
.
getSessionId
()),
"/topic/reply"
,
Result
.
ok
(
UUID
.
fastUUID
().
toString
(
true
)
)
Result
.
ok
(
body
)
);
}
}
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论