Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
B
basic-api-boot
概览
概览
详情
活动
周期分析
版本库
存储库
文件
提交
分支
标签
贡献者
分支图
比较
统计图
问题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程表
图表
维基
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
Basic
basic-api-boot
Commits
075e438f
提交
075e438f
authored
2月 13, 2023
作者:
方治民
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
feat: 新增数据字典/分类相关实现、i18n 配置优化、文件上传现在支持对大视频文件进行切片处理,以及诸多细节优化
上级
4d47ee14
隐藏空白字符变更
内嵌
并排
正在显示
52 个修改的文件
包含
1277 行增加
和
147 行删除
+1277
-147
build.gradle
app/build.gradle
+5
-0
UserExtension.java
...c/main/java/com/yiring/app/domain/user/UserExtension.java
+3
-6
UploadProcessServiceImpl.java
...m/yiring/app/service/upload/UploadProcessServiceImpl.java
+83
-5
ExampleController.java
...in/java/com/yiring/app/web/example/ExampleController.java
+22
-0
application-dev-postgresql.yml
app/src/main/resources/application-dev-postgresql.yml
+11
-4
application.yml
app/src/main/resources/application.yml
+2
-5
AuthExceptionHandler.java
...ain/java/com/yiring/auth/config/AuthExceptionHandler.java
+1
-1
StpInterfaceImpl.java
...rc/main/java/com/yiring/auth/config/StpInterfaceImpl.java
+3
-1
Permission.java
...in/java/com/yiring/auth/domain/permission/Permission.java
+4
-4
Role.java
...-auth/src/main/java/com/yiring/auth/domain/role/Role.java
+2
-1
User.java
...-auth/src/main/java/com/yiring/auth/domain/user/User.java
+5
-6
RegisterParam.java
...c/main/java/com/yiring/auth/param/auth/RegisterParam.java
+0
-3
PermissionParam.java
...ava/com/yiring/auth/param/permission/PermissionParam.java
+15
-15
AuthController.java
...rc/main/java/com/yiring/auth/web/auth/AuthController.java
+1
-2
PermissionController.java
.../yiring/auth/web/sys/permission/PermissionController.java
+5
-13
RoleController.java
...ain/java/com/yiring/auth/web/sys/role/RoleController.java
+2
-2
DownloadResponse.java
...n/java/com/yiring/common/annotation/DownloadResponse.java
+1
-1
EnvConfig.java
...ore/src/main/java/com/yiring/common/config/EnvConfig.java
+23
-0
Result.java
...mon/core/src/main/java/com/yiring/common/core/Result.java
+1
-1
PageParam.java
...core/src/main/java/com/yiring/common/param/PageParam.java
+1
-4
Group.java
...c/main/java/com/yiring/common/validation/group/Group.java
+5
-0
KeyValueVo.java
...n/core/src/main/java/com/yiring/common/vo/KeyValueVo.java
+6
-0
SwaggerConfig.java
...rc/main/java/com/yiring/common/swagger/SwaggerConfig.java
+40
-21
I18nConfig.java
...8n/src/main/java/com/yiring/common/config/I18nConfig.java
+23
-3
SmReloadableResourceBundleMessageSource.java
...ommon/config/SmReloadableResourceBundleMessageSource.java
+45
-0
I18n.java
...ommon/i18n/src/main/java/com/yiring/common/core/I18n.java
+1
-2
FileManageService.java
...ain/java/com/yiring/common/service/FileManageService.java
+21
-0
UploadProcessService.java
.../java/com/yiring/common/service/UploadProcessService.java
+2
-2
DefaultUploadProcessServiceImpl.java
.../common/service/impl/DefaultUploadProcessServiceImpl.java
+14
-0
FileManageServiceImpl.java
...com/yiring/common/service/impl/FileManageServiceImpl.java
+50
-0
MinioController.java
.../src/main/java/com/yiring/common/web/MinioController.java
+4
-18
Times.java
...til/src/main/java/com/yiring/common/annotation/Times.java
+5
-0
TimesAspect.java
...l/src/main/java/com/yiring/common/aspect/TimesAspect.java
+16
-5
FileUtils.java
.../util/src/main/java/com/yiring/common/util/FileUtils.java
+20
-14
build.gradle
basic-dict/build.gradle
+17
-0
Category.java
...c-dict/src/main/java/com/yiring/dict/domain/Category.java
+78
-0
CategoryRepository.java
.../main/java/com/yiring/dict/domain/CategoryRepository.java
+15
-0
Dict.java
basic-dict/src/main/java/com/yiring/dict/domain/Dict.java
+76
-0
DictItem.java
...c-dict/src/main/java/com/yiring/dict/domain/DictItem.java
+79
-0
DictItemRepository.java
.../main/java/com/yiring/dict/domain/DictItemRepository.java
+15
-0
DictRepository.java
.../src/main/java/com/yiring/dict/domain/DictRepository.java
+15
-0
DictItemParam.java
...ct/src/main/java/com/yiring/dict/param/DictItemParam.java
+58
-0
DictParam.java
...c-dict/src/main/java/com/yiring/dict/param/DictParam.java
+46
-0
SelectorDictItemParam.java
...ain/java/com/yiring/dict/param/SelectorDictItemParam.java
+38
-0
DictItemVo.java
basic-dict/src/main/java/com/yiring/dict/vo/DictItemVo.java
+46
-0
DictVo.java
basic-dict/src/main/java/com/yiring/dict/vo/DictVo.java
+40
-0
DictController.java
...ict/src/main/java/com/yiring/dict/web/DictController.java
+142
-0
DictItemController.java
...src/main/java/com/yiring/dict/web/DictItemController.java
+150
-0
messages.properties
basic-dict/src/main/resources/i18n/messages.properties
+3
-0
messages_zh_CN.properties
basic-dict/src/main/resources/i18n/messages_zh_CN.properties
+3
-0
build.gradle
build.gradle
+13
-8
settings.gradle
settings.gradle
+1
-0
没有找到文件。
app/build.gradle
浏览文件 @
075e438f
...
...
@@ -42,6 +42,9 @@ dependencies {
implementation
project
(
":basic-auth"
)
implementation
"cn.dev33:sa-token-spring-boot3-starter:${saTokenVersion}"
// Optional: Dict - 数据字典
implementation
project
(
":basic-dict"
)
// Optional: WebSocket && STOMP 依赖 Auth + Redis 模块
implementation
project
(
":basic-websocket"
)
...
...
@@ -57,6 +60,8 @@ dependencies {
implementation
'org.bytedeco:ffmpeg-platform:5.0-1.5.7'
// https://mvnrepository.com/artifact/org.apache.pdfbox/pdfbox
implementation
"org.apache.pdfbox:pdfbox:${pdfboxVersion}"
// https://mvnrepository.com/artifact/net.bramp.ffmpeg/ffmpeg
implementation
"net.bramp.ffmpeg:ffmpeg:${ffmpegWrapperVersion}"
// fastjson
implementation
"com.alibaba.fastjson2:fastjson2:${fastJsonVersion}"
...
...
app/src/main/java/com/yiring/app/domain/user/UserExtension.java
浏览文件 @
075e438f
...
...
@@ -3,10 +3,7 @@ package com.yiring.app.domain.user;
import
com.yiring.auth.domain.user.User
;
import
com.yiring.common.domain.BasicEntity
;
import
jakarta.persistence.Entity
;
import
jakarta.persistence.JoinColumn
;
import
jakarta.persistence.OneToOne
;
import
jakarta.persistence.Table
;
import
jakarta.persistence.*
;
import
java.io.Serial
;
import
java.io.Serializable
;
import
lombok.*
;
...
...
@@ -31,7 +28,7 @@ import org.hibernate.annotations.Comment;
@FieldNameConstants
@FieldDefaults
(
level
=
AccessLevel
.
PRIVATE
)
@Entity
@Table
(
name
=
"SYS_USER_EXTENSION"
)
@Table
(
name
=
"SYS_USER_EXTENSION"
,
uniqueConstraints
=
@UniqueConstraint
(
columnNames
=
"user_id"
)
)
@Comment
(
"用户扩展表"
)
public
class
UserExtension
extends
BasicEntity
implements
Serializable
{
...
...
@@ -40,7 +37,7 @@ public class UserExtension extends BasicEntity implements Serializable {
@Comment
(
"用户"
)
@OneToOne
@JoinColumn
(
nullable
=
false
,
unique
=
true
)
@JoinColumn
(
nullable
=
false
,
name
=
"user_id"
)
User
user
;
@Comment
(
"性别"
)
...
...
app/src/main/java/com/yiring/app/service/upload/UploadProcessServiceImpl.java
浏览文件 @
075e438f
...
...
@@ -4,22 +4,28 @@ package com.yiring.app.service.upload;
import
cn.hutool.core.io.FileUtil
;
import
com.yiring.common.core.Minio
;
import
com.yiring.common.service.UploadProcessService
;
import
com.yiring.common.util.Commons
;
import
io.minio.ObjectWriteResponse
;
import
java.awt.image.BufferedImage
;
import
java.awt.image.RenderedImage
;
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.Path
;
import
java.nio.file.Paths
;
import
java.util.List
;
import
java.util.regex.Pattern
;
import
javax.imageio.ImageIO
;
import
lombok.Cleanup
;
import
lombok.RequiredArgsConstructor
;
import
lombok.SneakyThrows
;
import
lombok.extern.slf4j.Slf4j
;
import
org.apache.pdfbox.pdmodel.PDDocument
;
import
org.apache.pdfbox.rendering.PDFRenderer
;
import
org.bytedeco.javacv.FFmpegFrameGrabber
;
import
org.bytedeco.javacv.Frame
;
import
org.bytedeco.javacv.Java2DFrameConverter
;
import
org.springframework.boot.autoconfigure.condition.ConditionalOnClass
;
import
org.bytedeco.javacv.*
;
import
org.springframework.context.annotation.Primary
;
import
org.springframework.http.MediaType
;
import
org.springframework.stereotype.Component
;
...
...
@@ -29,13 +35,16 @@ import org.springframework.stereotype.Component;
* 2022/9/23 16:44
*/
@ConditionalOnClass
({
PDDocument
.
class
,
FFmpegFrameGrabber
.
class
})
@Slf4j
@Primary
@Component
@RequiredArgsConstructor
public
class
UploadProcessServiceImpl
implements
UploadProcessService
{
final
Minio
minio
;
Pattern
pattern
=
Pattern
.
compile
(
"^.*\\.ts$"
);
@Override
public
String
handle
(
String
object
,
InputStream
is
)
{
String
suffix
=
FileUtil
.
getSuffix
(
object
);
...
...
@@ -110,13 +119,82 @@ public class UploadProcessServiceImpl implements UploadProcessService {
ImageIO
.
write
(
frameToBufferedImage
(
frame
),
format
,
os
);
@Cleanup
InputStream
io
=
new
ByteArrayInputStream
(
os
.
toByteArray
());
int
size
=
io
.
available
();
minio
.
putObject
(
io
,
MediaType
.
IMAGE_JPEG_VALUE
,
filepath
+
"."
+
format
);
// 大视频文件切片上传(> 10M)
if
(
size
>
(
10
*
10
*
1024
))
{
filepath
=
fillSuffix
(
handleToM3u8
(
object
,
suffix
,
ff
),
"T"
+
(
ff
.
getLengthInTime
()
/
(
1000
*
1000
)));
}
}
ff
.
stop
();
return
filepath
;
}
@SneakyThrows
public
String
handleToM3u8
(
String
object
,
String
suffix
,
FFmpegFrameGrabber
ff
)
{
File
tmpDir
=
FileUtil
.
getTmpDir
();
String
sourceName
=
FileUtil
.
getName
(
object
);
String
objectFolder
=
object
.
replace
(
"/"
+
sourceName
,
""
);
Path
m3u8
=
Paths
.
get
(
tmpDir
.
getPath
(),
Commons
.
uuid
(),
sourceName
.
replaceAll
(
"^(.*)\\."
+
suffix
+
"$"
,
"$1.m3u8"
)
);
FileUtil
.
mkParentDirs
(
m3u8
);
String
out
=
m3u8
.
toFile
().
getPath
();
long
start
=
System
.
currentTimeMillis
();
@Cleanup
FFmpegFrameRecorder
recorder
=
new
FFmpegFrameRecorder
(
out
,
ff
.
getImageWidth
(),
ff
.
getImageHeight
(),
ff
.
getAudioChannels
()
);
recorder
.
setFormat
(
"hls"
);
recorder
.
setOption
(
"hls_wrap"
,
"0"
);
recorder
.
setOption
(
"hls_time"
,
"5"
);
recorder
.
setOption
(
"hls_list_size"
,
"0"
);
recorder
.
setOption
(
"hls_flags"
,
"delete_segments"
);
recorder
.
setOption
(
"hls_segment_type"
,
"mpegts"
);
recorder
.
setOption
(
"hls_segment_filename"
,
out
.
replace
(
".m3u8"
,
"-%d.ts"
));
recorder
.
setOption
(
"hls_delete_threshold"
,
"1"
);
recorder
.
setOption
(
"vsync"
,
"2"
);
recorder
.
setOption
(
"c:v"
,
"copy"
);
recorder
.
setOption
(
"c:a"
,
"copy"
);
recorder
.
setOption
(
"tune"
,
"fastdecode"
);
recorder
.
setOption
(
"threads"
,
"8"
);
recorder
.
start
();
Frame
frame
;
while
((
frame
=
ff
.
grabImage
())
!=
null
)
{
try
{
recorder
.
record
(
frame
);
}
catch
(
FrameRecorder
.
Exception
e
)
{
log
.
error
(
e
.
getMessage
(),
e
);
}
}
recorder
.
setTimestamp
(
ff
.
getTimestamp
());
recorder
.
flush
();
long
end
=
System
.
currentTimeMillis
();
long
times
=
end
-
start
;
log
.
info
(
"[Times] {}: {} ms"
,
"video convert to m3u8"
,
times
);
// 解析 m3u8 文件
List
<
String
>
lines
=
FileUtil
.
readLines
(
m3u8
.
toString
(),
StandardCharsets
.
UTF_8
);
// 获取 ts 切片文件
List
<
String
>
tss
=
lines
.
stream
().
filter
(
line
->
pattern
.
matcher
(
line
).
matches
()).
toList
();
// 上传 ts 切片文件
for
(
String
ts
:
tss
)
{
Path
path
=
Paths
.
get
(
m3u8
.
getParent
().
toString
(),
ts
);
minio
.
putObject
(
path
.
toFile
(),
objectFolder
);
}
// 上传 m3u8 索引文件
ObjectWriteResponse
objectWriteResponse
=
minio
.
putObject
(
m3u8
.
toFile
(),
objectFolder
);
return
objectWriteResponse
.
object
();
}
public
static
Frame
getPictureFrame
(
FFmpegFrameGrabber
grabber
)
throws
FFmpegFrameGrabber
.
Exception
{
int
ftp
=
grabber
.
getLengthInFrames
();
int
flag
=
0
;
...
...
app/src/main/java/com/yiring/app/web/example/ExampleController.java
浏览文件 @
075e438f
...
...
@@ -15,12 +15,15 @@ import com.yiring.common.core.I18n;
import
com.yiring.common.core.Result
;
import
com.yiring.common.core.Status
;
import
com.yiring.common.exception.BusinessException
;
import
com.yiring.common.param.IdParam
;
import
com.yiring.common.param.PageParam
;
import
com.yiring.common.service.FileManageService
;
import
com.yiring.common.util.Commons
;
import
com.yiring.common.util.FileUtils
;
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.Parameter
;
import
io.swagger.v3.oas.annotations.tags.Tag
;
import
jakarta.servlet.http.HttpServletResponse
;
import
java.io.IOException
;
...
...
@@ -36,6 +39,7 @@ import org.springframework.data.domain.Example;
import
org.springframework.http.MediaType
;
import
org.springframework.validation.annotation.Validated
;
import
org.springframework.web.bind.annotation.*
;
import
org.springframework.web.multipart.MultipartFile
;
/**
* 示例接口
...
...
@@ -55,6 +59,7 @@ public class ExampleController {
final
I18n
i18n
;
final
Auths
auths
;
final
UserExtensionRepository
userExtensionRepository
;
final
FileManageService
fileManageService
;
@Operation
(
summary
=
"Hello World"
)
@GetMapping
...
...
@@ -101,6 +106,23 @@ public class ExampleController {
FileUtils
.
download
(
response
,
resource
.
getFile
());
}
@Operation
(
summary
=
"文件上传"
)
@PostMapping
(
value
=
"upload"
,
consumes
=
MediaType
.
MULTIPART_FORM_DATA_VALUE
)
public
Result
<
String
>
upload
(
@Parameter
(
name
=
"文件"
,
required
=
true
)
@RequestPart
(
"file"
)
MultipartFile
file
,
@ParameterObject
@Validated
IdParam
param
)
{
log
.
info
(
"upload params: {}"
,
param
);
try
{
String
link
=
fileManageService
.
upload
(
file
);
return
Result
.
ok
(
link
);
}
catch
(
Exception
e
)
{
log
.
error
(
e
.
getMessage
(),
e
);
throw
Status
.
BAD_REQUEST
.
exception
();
}
}
@SaCheckSafe
@SaCheckLogin
@Operation
(
summary
=
"查询用户属性"
)
...
...
app/src/main/resources/application-dev-postgresql.yml
浏览文件 @
075e438f
# 环境变量
env
:
host
:
192.168.0.156
# host: 192.168.0.156
host
:
127.0.0.1
prod
:
false
extra
:
username
:
admin
#
username: admin
password
:
123456
username
:
postgres
ffmpeg
:
path
:
D:\Environments\FFmpeg\bin
# ----------------------------------------------
spring
:
...
...
@@ -17,10 +21,13 @@ spring:
open-in-view
:
true
hibernate
:
ddl-auto
:
update
show-sql
:
fals
e
show-sql
:
tru
e
properties
:
hibernate
:
format_sql
:
true
# https://stackoverflow.com/questions/49283069/columnunique-true-produces-a-warn-o-h-engine-jdbc-spi-sqlexceptionhelper
schema_update
:
unique_constraint_strategy
:
RECREATE_QUIETLY
# https://github.com/spring-projects/spring-data-jpa/issues/2717
# https://hibernate.atlassian.net/browse/HHH-15827
jakarta
:
...
...
@@ -40,7 +47,7 @@ spring:
springdoc
:
default-consumes-media-type
:
"
application/x-www-form-urlencoded"
default-produces-media-type
:
"
application/json"
default-flat-param-object
:
tru
e
default-flat-param-object
:
fals
e
override-with-generic-response
:
false
api-docs
:
resolve-schema-properties
:
true
...
...
app/src/main/resources/application.yml
浏览文件 @
075e438f
...
...
@@ -13,11 +13,8 @@ spring:
name
:
"
basic-api-app"
servlet
:
multipart
:
max-file-size
:
50MB
max-request-size
:
100MB
messages
:
basename
:
i18n/status,i18n/validation,i18n/messages
always-use-message-format
:
true
max-file-size
:
1024MB
max-request-size
:
1048MB
profiles
:
include
:
auth, conf-patch
active
:
dev-postgresql
...
...
basic-auth/src/main/java/com/yiring/auth/config/AuthExceptionHandler.java
浏览文件 @
075e438f
...
...
@@ -18,7 +18,7 @@ import org.springframework.web.bind.annotation.RestControllerAdvice;
* 2023/1/12 14:06
*/
@Slf4j
@Order
(
0
)
@Order
(
1
)
@RestControllerAdvice
@RequiredArgsConstructor
public
class
AuthExceptionHandler
{
...
...
basic-auth/src/main/java/com/yiring/auth/config/StpInterfaceImpl.java
浏览文件 @
075e438f
...
...
@@ -14,6 +14,7 @@ import java.util.Set;
import
java.util.stream.Collectors
;
import
lombok.RequiredArgsConstructor
;
import
org.springframework.stereotype.Component
;
import
org.springframework.transaction.annotation.Transactional
;
/**
* 获取登录用户权限信息实现
...
...
@@ -23,6 +24,7 @@ import org.springframework.stereotype.Component;
* 2022/3/25 9:37
*/
@Transactional
(
readOnly
=
true
)
@Component
@RequiredArgsConstructor
public
class
StpInterfaceImpl
implements
StpInterface
{
...
...
@@ -54,7 +56,7 @@ public class StpInterfaceImpl implements StpInterface {
* @param loginId 登录 ID
* @return 用户信息
*/
p
rivate
User
getUser
(
Object
loginId
)
{
p
ublic
User
getUser
(
Object
loginId
)
{
String
id
=
Objects
.
toString
(
loginId
);
Optional
<
User
>
optional
=
userRepository
.
findById
(
id
);
if
(
optional
.
isEmpty
())
{
...
...
basic-auth/src/main/java/com/yiring/auth/domain/permission/Permission.java
浏览文件 @
075e438f
...
...
@@ -41,10 +41,10 @@ import org.hibernate.annotations.Where;
@Table
(
name
=
TABLE_NAME
,
indexes
=
{
@Index
(
columnList
=
"deleteTime"
),
@Index
(
columnList
=
"type"
),
@Index
(
columnList
=
"pid"
),
@Index
(
columnList
=
"tree"
),
@Index
(
columnList
=
BasicEntity
.
Fields
.
deleteTime
),
@Index
(
columnList
=
Permission
.
Fields
.
type
),
@Index
(
columnList
=
Permission
.
Fields
.
pid
),
@Index
(
columnList
=
Permission
.
Fields
.
tree
),
}
)
@Comment
(
"系统权限"
)
...
...
basic-auth/src/main/java/com/yiring/auth/domain/role/Role.java
浏览文件 @
075e438f
...
...
@@ -43,7 +43,8 @@ import org.hibernate.annotations.Where;
@Entity
@Table
(
name
=
TABLE_NAME
,
indexes
=
{
@Index
(
columnList
=
"deleteTime"
),
@Index
(
columnList
=
"uid,deleteTime"
,
unique
=
true
)
}
indexes
=
@Index
(
columnList
=
BasicEntity
.
Fields
.
deleteTime
),
uniqueConstraints
=
{
@UniqueConstraint
(
columnNames
=
{
Role
.
Fields
.
uid
,
BasicEntity
.
Fields
.
deleteTime
})
}
)
@Comment
(
"系统角色"
)
public
class
Role
extends
BasicEntity
implements
Serializable
{
...
...
basic-auth/src/main/java/com/yiring/auth/domain/user/User.java
浏览文件 @
075e438f
...
...
@@ -40,12 +40,11 @@ import org.hibernate.annotations.Where;
@Entity
@Table
(
name
=
User
.
TABLE_NAME
,
indexes
=
{
@Index
(
columnList
=
"enabled"
),
@Index
(
columnList
=
"deleteTime"
),
@Index
(
columnList
=
"username,deleteTime"
,
unique
=
true
),
@Index
(
columnList
=
"mobile,deleteTime"
,
unique
=
true
),
@Index
(
columnList
=
"email,deleteTime"
,
unique
=
true
),
indexes
=
{
@Index
(
columnList
=
User
.
Fields
.
enabled
),
@Index
(
columnList
=
BasicEntity
.
Fields
.
deleteTime
)
},
uniqueConstraints
=
{
@UniqueConstraint
(
columnNames
=
{
User
.
Fields
.
username
,
BasicEntity
.
Fields
.
deleteTime
}),
@UniqueConstraint
(
columnNames
=
{
User
.
Fields
.
mobile
,
BasicEntity
.
Fields
.
deleteTime
}),
@UniqueConstraint
(
columnNames
=
{
User
.
Fields
.
email
,
BasicEntity
.
Fields
.
deleteTime
}),
}
)
@Comment
(
"系统用户"
)
...
...
basic-auth/src/main/java/com/yiring/auth/param/auth/RegisterParam.java
浏览文件 @
075e438f
...
...
@@ -51,9 +51,6 @@ public class RegisterParam implements Serializable {
@Parameter
(
description
=
"邮箱"
,
example
=
"developer@yiring.com"
)
String
email
;
@Parameter
(
description
=
"简介"
,
example
=
"平台管理员"
)
String
introduction
;
@Parameter
(
description
=
"是否启用"
,
example
=
"true"
)
Boolean
enable
;
}
basic-auth/src/main/java/com/yiring/auth/param/permission/PermissionParam.java
浏览文件 @
075e438f
/* (C) 2022 YiRing, Inc. */
package
com
.
yiring
.
auth
.
param
.
permission
;
import
com.alibaba.fastjson2.JSONObject
;
import
com.fasterxml.jackson.annotation.JsonInclude
;
import
com.yiring.auth.domain.permission.Permission
;
import
com.yiring.common.validation.group.Group
;
import
io.swagger.v3.oas.annotations.Parameter
;
import
io.swagger.v3.oas.annotations.media.Schema
;
import
jakarta.validation.constraints.NotBlank
;
import
jakarta.validation.constraints.NotEmpty
;
...
...
@@ -34,47 +34,47 @@ public class PermissionParam implements Serializable {
@Serial
private
static
final
long
serialVersionUID
=
-
6781934969837655538L
;
@
Parameter
(
description
=
"id"
,
example
=
"1"
)
@
Schema
(
description
=
"id"
,
example
=
"1"
)
@NotBlank
(
groups
=
{
Group
.
Edit
.
class
})
String
id
;
@
Parameter
(
description
=
"权限类型"
,
example
=
"MENU"
)
@
Schema
(
description
=
"权限类型"
,
example
=
"MENU"
)
@NotNull
(
message
=
"权限类型不能为空"
)
Permission
.
Type
type
;
@
Parameter
(
description
=
"序号"
,
example
=
"1"
)
@
Schema
(
description
=
"序号"
,
example
=
"1"
)
Integer
serial
;
@
Parameter
(
description
=
"标识"
,
example
=
"Dashboard"
)
@
Schema
(
description
=
"标识"
,
example
=
"Dashboard"
)
@NotEmpty
(
message
=
"权限标识不能为空"
)
String
uid
;
@
Parameter
(
description
=
"名称"
,
example
=
"Dashboard"
)
@
Schema
(
description
=
"名称"
,
example
=
"Dashboard"
)
@NotEmpty
(
message
=
"权限名称不能为空"
)
String
name
;
@
Parameter
(
description
=
"路径"
,
example
=
"/dashboard"
)
@
Schema
(
description
=
"路径"
,
example
=
"/dashboard"
)
String
path
;
@
Parameter
(
description
=
"重定向"
,
example
=
"/dashboard/workbench"
)
@
Schema
(
description
=
"重定向"
,
example
=
"/dashboard/workbench"
)
String
redirect
;
@
Parameter
(
description
=
"组件"
,
example
=
"LAYOUT"
)
@
Schema
(
description
=
"组件"
,
example
=
"LAYOUT"
)
String
component
;
@
Parameter
(
description
=
"图标"
,
example
=
"ion:grid-outline"
)
@
Schema
(
description
=
"图标"
,
example
=
"ion:grid-outline"
)
String
icon
;
@
Parameter
(
description
=
"是否隐藏"
,
example
=
"false"
)
@
Schema
(
description
=
"是否隐藏"
,
example
=
"false"
)
Boolean
hidden
;
@
Parameter
(
description
=
"是否启用"
,
example
=
"true"
)
@
Schema
(
description
=
"是否启用"
,
example
=
"true"
)
Boolean
enable
;
@
Parameter
(
description
=
"父级ID"
,
example
=
"0"
)
@
Schema
(
description
=
"父级ID"
,
example
=
"0"
)
@Builder
.
Default
String
pid
=
"0"
;
@
Parameter
(
description
=
"元数据"
,
example
=
"{\"title\": \"routes.dashboard.dashboard\"}"
)
String
meta
;
@
Schema
(
description
=
"元数据"
,
example
=
"{\"title\": \"routes.dashboard.dashboard\"}"
)
JSONObject
meta
;
}
basic-auth/src/main/java/com/yiring/auth/web/auth/AuthController.java
浏览文件 @
075e438f
...
...
@@ -86,7 +86,6 @@ public class AuthController {
.
username
(
param
.
getUsername
())
.
password
(
SaSecureUtil
.
sha256
(
param
.
getPassword
()))
.
enabled
(
param
.
getEnable
())
.
createTime
(
LocalDateTime
.
now
())
.
build
();
userRepository
.
saveAndFlush
(
user
);
return
Result
.
ok
();
...
...
@@ -156,7 +155,7 @@ public class AuthController {
public
Result
<
String
>
safe
(
@ParameterObject
@Validated
SafeParam
param
)
{
User
user
=
auths
.
getLoginUser
();
if
(
SaSecureUtil
.
sha256
(
param
.
getPassword
()).
equals
(
user
.
getPassword
()))
{
StpUtil
.
openSafe
(
12
0
);
StpUtil
.
openSafe
(
36
0
);
return
Result
.
ok
();
}
...
...
basic-auth/src/main/java/com/yiring/auth/web/sys/permission/PermissionController.java
浏览文件 @
075e438f
...
...
@@ -2,8 +2,6 @@
package
com
.
yiring
.
auth
.
web
.
sys
.
permission
;
import
cn.hutool.core.util.StrUtil
;
import
com.alibaba.fastjson2.JSON
;
import
com.alibaba.fastjson2.JSONValidator
;
import
com.yiring.auth.domain.permission.Permission
;
import
com.yiring.auth.domain.permission.PermissionRepository
;
import
com.yiring.auth.param.permission.PermissionParam
;
...
...
@@ -31,10 +29,7 @@ import org.springframework.beans.BeanUtils;
import
org.springframework.data.domain.Example
;
import
org.springframework.data.domain.Page
;
import
org.springframework.validation.annotation.Validated
;
import
org.springframework.web.bind.annotation.GetMapping
;
import
org.springframework.web.bind.annotation.PostMapping
;
import
org.springframework.web.bind.annotation.RequestMapping
;
import
org.springframework.web.bind.annotation.RestController
;
import
org.springframework.web.bind.annotation.*
;
/**
* 系统权限管理控制器
...
...
@@ -60,7 +55,7 @@ public class PermissionController {
@Operation
(
summary
=
"新增"
)
@PostMapping
(
"add"
)
public
Result
<
String
>
add
(
@
ParameterObject
@Validated
({
Group
.
Add
.
class
})
PermissionParam
param
)
{
public
Result
<
String
>
add
(
@
RequestBody
@Validated
({
Group
.
Add
.
class
})
PermissionParam
param
)
{
if
(
has
(
param
.
getUid
()))
{
throw
BusinessException
.
i18n
(
"Code.1001"
);
}
...
...
@@ -72,7 +67,7 @@ public class PermissionController {
@Operation
(
summary
=
"修改"
)
@PostMapping
(
"modify"
)
public
Result
<
String
>
modify
(
@
ParameterObject
@Validated
({
Group
.
Edit
.
class
})
PermissionParam
param
)
{
public
Result
<
String
>
modify
(
@
RequestBody
@Validated
({
Group
.
Edit
.
class
})
PermissionParam
param
)
{
Optional
<
Permission
>
optional
=
permissionRepository
.
findById
(
param
.
getId
());
if
(
optional
.
isEmpty
())
{
throw
Status
.
NOT_FOUND
.
exception
();
...
...
@@ -91,8 +86,8 @@ public class PermissionController {
}
@Operation
(
summary
=
"删除"
)
@PostMapping
(
"
deleted
"
)
public
Result
<
String
>
deleted
(
@ParameterObject
@Validated
IdParam
param
)
{
@PostMapping
(
"
remove
"
)
public
Result
<
String
>
remove
(
@ParameterObject
@Validated
IdParam
param
)
{
Optional
<
Permission
>
optional
=
permissionRepository
.
findById
(
param
.
getId
());
if
(
optional
.
isEmpty
())
{
throw
Status
.
NOT_FOUND
.
exception
();
...
...
@@ -173,9 +168,6 @@ public class PermissionController {
private
void
save
(
Permission
entity
,
PermissionParam
param
)
{
BeanUtils
.
copyProperties
(
param
,
entity
,
Permission
.
Fields
.
meta
);
entity
.
setTree
(
getTreeNode
(
param
.
getPid
()));
if
(
JSONValidator
.
from
(
param
.
getMeta
()).
validate
())
{
entity
.
setMeta
(
JSON
.
parseObject
(
param
.
getMeta
()));
}
permissionRepository
.
saveAndFlush
(
entity
);
}
}
basic-auth/src/main/java/com/yiring/auth/web/sys/role/RoleController.java
浏览文件 @
075e438f
...
...
@@ -113,8 +113,8 @@ public class RoleController {
}
@Operation
(
summary
=
"删除"
)
@PostMapping
(
"
deleted
"
)
public
Result
<
String
>
deleted
(
@ParameterObject
@Validated
IdsParam
param
)
{
@PostMapping
(
"
remove
"
)
public
Result
<
String
>
remove
(
@ParameterObject
@Validated
IdsParam
param
)
{
List
<
Role
>
roles
=
roleRepository
.
findAllById
(
param
.
toIds
());
roleRepository
.
deleteAll
(
roles
);
return
Result
.
ok
();
...
...
basic-common/core/src/main/java/com/yiring/common/annotation/DownloadResponse.java
浏览文件 @
075e438f
...
...
@@ -30,7 +30,7 @@ public @interface DownloadResponse {
@AliasFor
(
annotation
=
ApiResponse
.
class
)
Content
content
()
default
@Content
(
schema
=
@Schema
(
type
=
"
string
"
,
format
=
"binary"
),
schema
=
@Schema
(
type
=
"
file
"
,
format
=
"binary"
),
mediaType
=
MediaType
.
APPLICATION_OCTET_STREAM_VALUE
);
}
basic-common/core/src/main/java/com/yiring/common/config/EnvConfig.java
浏览文件 @
075e438f
...
...
@@ -37,11 +37,34 @@ public class EnvConfig implements Serializable {
boolean
prod
;
/**
* FFmpeg 配置
*/
FFmpeg
ffmpeg
;
/**
* 扩展配置
*/
Extra
extra
;
/**
* FFmpeg 路径相关配置
*/
@Data
@FieldDefaults
(
level
=
AccessLevel
.
PRIVATE
)
@Configuration
(
"env.config.ffmpeg"
)
@ConfigurationProperties
(
prefix
=
"env.ffmpeg"
)
public
static
class
FFmpeg
implements
Serializable
{
@Serial
private
static
final
long
serialVersionUID
=
972365906869473179L
;
/**
* ffmpeg 路径
*/
String
path
;
}
/**
* 扩展环境配置变量
*/
@Data
...
...
basic-common/core/src/main/java/com/yiring/common/core/Result.java
浏览文件 @
075e438f
...
...
@@ -192,7 +192,7 @@ public class Result<T> implements Serializable {
@PropertyKey
(
resourceBundle
=
I18n
.
RESOURCE_BUNDLE
)
String
details
,
Throwable
error
)
{
if
(
Objects
.
isNull
(
code
))
{
if
(
Objects
.
isNull
(
code
)
&&
Objects
.
nonNull
(
details
)
)
{
String
prefix
=
"Code."
;
if
(
details
.
startsWith
(
prefix
))
{
String
codeText
=
details
.
replace
(
prefix
,
""
);
...
...
basic-common/core/src/main/java/com/yiring/common/param/PageParam.java
浏览文件 @
075e438f
/* (C) 2021 YiRing, Inc. */
package
com
.
yiring
.
common
.
param
;
import
io.swagger.v3.oas.annotations.Parameter
;
import
io.swagger.v3.oas.annotations.media.Schema
;
import
jakarta.validation.constraints.Min
;
import
jakarta.validation.constraints.NotNull
;
...
...
@@ -37,13 +36,11 @@ public class PageParam implements Serializable {
@Serial
private
static
final
long
serialVersionUID
=
6103761701912769946L
;
@Parameter
@Schema
(
description
=
"分页条数"
,
defaultValue
=
"10"
,
example
=
"10"
,
type
=
"integer"
)
@NotNull
@Range
(
min
=
1
,
max
=
100
)
Integer
pageSize
;
@Parameter
@Schema
(
description
=
"当前页数"
,
defaultValue
=
"1"
,
example
=
"1"
,
type
=
"integer"
)
@NotNull
@Min
(
1
)
...
...
@@ -84,6 +81,6 @@ public class PageParam implements Serializable {
if
(
Objects
.
nonNull
(
sortField
))
{
sort
=
Sort
.
by
(
new
Sort
.
Order
(
sortOrder
,
sortField
));
}
return
PageRequest
.
of
(
page
Size
-
1
,
pageNo
,
sort
);
return
PageRequest
.
of
(
page
No
-
1
,
pageSize
,
sort
);
}
}
basic-common/core/src/main/java/com/yiring/common/validation/group/Group.java
浏览文件 @
075e438f
...
...
@@ -24,6 +24,11 @@ public interface Group {
interface
Edit
extends
Default
{}
/**
* 数据查询分组
*/
interface
Query
extends
Default
{}
/**
* 通用的必填分组
*/
interface
Required
extends
Default
{}
...
...
basic-common/core/src/main/java/com/yiring/common/vo/KeyValueVo.java
浏览文件 @
075e438f
...
...
@@ -37,4 +37,10 @@ public class KeyValueVo implements Serializable {
*/
@Schema
(
description
=
"label"
,
example
=
"label"
)
String
label
;
/**
* 扩展字段,可用于显示禁用状态
*/
@Schema
(
description
=
"是否启用"
,
example
=
"true"
)
String
enable
;
}
basic-common/doc/src/main/java/com/yiring/common/swagger/SwaggerConfig.java
浏览文件 @
075e438f
...
...
@@ -5,23 +5,27 @@ import cn.hutool.core.convert.Convert;
import
cn.hutool.core.lang.Console
;
import
cn.hutool.core.net.NetUtil
;
import
com.yiring.common.core.I18n
;
import
io.swagger.v3.oas.annotations.Operation
;
import
io.swagger.v3.oas.models.ExternalDocumentation
;
import
io.swagger.v3.oas.models.OpenAPI
;
import
io.swagger.v3.oas.models.info.Contact
;
import
io.swagger.v3.oas.models.info.Info
;
import
io.swagger.v3.oas.models.tags.Tag
;
import
java.lang.annotation.Annotation
;
import
java.util.*
;
import
java.util.stream.Collectors
;
import
lombok.RequiredArgsConstructor
;
import
lombok.extern.slf4j.Slf4j
;
import
org.springdoc.core.customizers.OpenApiCustomizer
;
import
org.springdoc.core.customizers.OperationCustomizer
;
import
org.springdoc.core.models.GroupedOpenApi
;
import
org.springframework.beans.factory.annotation.Value
;
import
org.springframework.boot.CommandLineRunner
;
import
org.springframework.context.annotation.Bean
;
import
org.springframework.context.annotation.Configuration
;
import
org.springframework.context.annotation.Profile
;
import
org.springframework.web.bind.annotation.DeleteMapping
;
import
org.springframework.web.bind.annotation.PostMapping
;
import
org.springframework.web.bind.annotation.PutMapping
;
/**
* Swagger Config
...
...
@@ -81,7 +85,12 @@ public class SwaggerConfig implements CommandLineRunner {
@Bean
(
name
=
"api.manage"
)
public
GroupedOpenApi
manage
()
{
return
api
(
"③ 系统管理"
,
List
.
of
(
"com.yiring.auth.web.sys"
),
List
.
of
(
"/**"
),
Collections
.
emptyList
());
return
api
(
"③ 系统管理"
,
List
.
of
(
"com.yiring.auth.web.sys"
,
"com.yiring.dict.web"
),
List
.
of
(
"/**"
),
Collections
.
emptyList
()
);
}
@Bean
(
name
=
"api.example"
)
...
...
@@ -115,24 +124,33 @@ public class SwaggerConfig implements CommandLineRunner {
};
}
// @Bean
// public OperationCustomizer addGlobalStatusResponse() {
// return (operation, handlerMethod) -> {
// ApiResponses responses = operation.getResponses();
// for (Status status : Status.values()) {
// if (Status.OK.equals(status)) {
// continue;
// }
//
// ApiResponse response = new ApiResponse()
// .description(i18n.get(status.getReasonPhrase()))
// .$ref("Response");
// responses.addApiResponse(String.valueOf(status.value()), response);
// }
//
// return operation;
// };
// }
/**
* 自定义 OperationId 用于优化 Pont 接口文件生成
*/
@Bean
public
OperationCustomizer
operationIdCustomizer
()
{
return
(
operation
,
handlerMethod
)
->
{
Class
<?>
superClazz
=
handlerMethod
.
getBeanType
().
getSuperclass
();
if
(
Objects
.
nonNull
(
superClazz
))
{
Annotation
[]
annotations
=
handlerMethod
.
getMethod
().
getAnnotations
();
String
methodType
=
"GET"
;
for
(
Annotation
annotation
:
annotations
)
{
if
(
annotation
instanceof
PostMapping
)
{
methodType
=
"POST"
;
}
else
if
(
annotation
instanceof
PutMapping
)
{
methodType
=
"PUT"
;
}
else
if
(
annotation
instanceof
DeleteMapping
)
{
methodType
=
"DELETE"
;
}
}
String
beanName
=
handlerMethod
.
getBeanType
().
getSimpleName
();
String
methodName
=
handlerMethod
.
getMethod
().
getName
();
operation
.
setOperationId
(
String
.
format
(
"%sUsing%s_%s"
,
methodName
,
methodType
,
beanName
));
}
return
operation
;
};
}
private
GroupedOpenApi
api
(
String
group
,
...
...
@@ -144,9 +162,10 @@ public class SwaggerConfig implements CommandLineRunner {
.
builder
()
.
group
(
group
)
.
packagesToScan
(
basePackages
.
toArray
(
new
String
[
0
]))
.
addOpenApiMethodFilter
(
method
->
method
.
isAnnotationPresent
(
Operation
.
class
))
.
addOpenApiMethodFilter
(
method
->
method
.
isAnnotationPresent
(
io
.
swagger
.
v3
.
oas
.
annotations
.
Operation
.
class
))
.
pathsToMatch
(
pathsToMatch
.
toArray
(
new
String
[
0
]))
.
pathsToExclude
(
pathsToExclude
.
toArray
(
new
String
[
0
]))
.
addOperationCustomizer
(
operationIdCustomizer
())
.
build
();
}
...
...
basic-common/i18n/src/main/java/com/yiring/common/config/I18nConfig.java
浏览文件 @
075e438f
...
...
@@ -3,9 +3,11 @@ package com.yiring.common.config;
import
java.util.Locale
;
import
lombok.RequiredArgsConstructor
;
import
org.springframework.context.MessageSource
;
import
org.springframework.context.annotation.Bean
;
import
org.springframework.context.annotation.Configuration
;
import
org.springframework.context.support.ReloadableResourceBundleMessageSource
;
import
org.springframework.core.annotation.Order
;
import
org.springframework.validation.beanvalidation.LocalValidatorFactoryBean
;
import
org.springframework.web.servlet.LocaleResolver
;
import
org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
;
...
...
@@ -18,15 +20,34 @@ import org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver;
* 2022/8/17 10:33
*/
@Order
@Configuration
@RequiredArgsConstructor
public
class
I18nConfig
{
public
static
final
Locale
DEFAULT_LOCALE
=
Locale
.
SIMPLIFIED_CHINESE
;
/**
* 解决多模块 i18n/messages 重复文件的读取问题
* <a href="https://stackoverflow.com/questions/17661252/spring-multi-module-i18n-with-modules-extending-the-messagesource-contents?noredirect=1&lq=1">...</a>
*
* @return MessageSource
*/
@Bean
public
MessageSource
messageSource
()
{
SmReloadableResourceBundleMessageSource
messageSource
=
new
SmReloadableResourceBundleMessageSource
();
messageSource
.
setBasenames
(
"classpath*:i18n/messages"
,
"classpath:i18n/status"
,
"classpath:i18n/validation"
);
messageSource
.
setDefaultEncoding
(
"UTF-8"
);
messageSource
.
setAlwaysUseMessageFormat
(
true
);
messageSource
.
setDefaultLocale
(
DEFAULT_LOCALE
);
return
messageSource
;
}
@Bean
public
LocaleResolver
localeResolver
()
{
// header accept-language
AcceptHeaderLocaleResolver
resolver
=
new
AcceptHeaderLocaleResolver
();
resolver
.
setDefaultLocale
(
Locale
.
SIMPLIFIED_CHINES
E
);
resolver
.
setDefaultLocale
(
DEFAULT_LOCAL
E
);
return
resolver
;
}
...
...
@@ -34,9 +55,8 @@ public class I18nConfig {
public
LocalValidatorFactoryBean
getValidator
()
{
ReloadableResourceBundleMessageSource
messageSource
=
new
ReloadableResourceBundleMessageSource
();
messageSource
.
setDefaultEncoding
(
"UTF-8"
);
messageSource
.
setDefaultLocale
(
Locale
.
SIMPLIFIED_CHINES
E
);
messageSource
.
setDefaultLocale
(
DEFAULT_LOCAL
E
);
messageSource
.
setBasenames
(
"classpath:/ValidationMessages"
,
"classpath:i18n/validation"
);
LocalValidatorFactoryBean
bean
=
new
LocalValidatorFactoryBean
();
bean
.
setValidationMessageSource
(
messageSource
);
return
bean
;
...
...
basic-common/i18n/src/main/java/com/yiring/common/config/SmReloadableResourceBundleMessageSource.java
0 → 100644
浏览文件 @
075e438f
/* (C) 2023 YiRing, Inc. */
package
com
.
yiring
.
common
.
config
;
import
java.io.IOException
;
import
java.util.Properties
;
import
lombok.NonNull
;
import
org.springframework.context.support.ReloadableResourceBundleMessageSource
;
import
org.springframework.core.io.Resource
;
import
org.springframework.core.io.support.PathMatchingResourcePatternResolver
;
/**
* @author Jim
* @version 0.1
* 2023/1/20 18:00
*/
public
class
SmReloadableResourceBundleMessageSource
extends
ReloadableResourceBundleMessageSource
{
private
static
final
String
PROPERTIES_SUFFIX
=
".properties"
;
private
final
PathMatchingResourcePatternResolver
resolver
=
new
PathMatchingResourcePatternResolver
();
@Override
protected
@NonNull
PropertiesHolder
refreshProperties
(
String
filename
,
PropertiesHolder
propHolder
)
{
if
(
filename
.
startsWith
(
PathMatchingResourcePatternResolver
.
CLASSPATH_ALL_URL_PREFIX
))
{
return
refreshClassPathProperties
(
filename
,
propHolder
);
}
else
{
return
super
.
refreshProperties
(
filename
,
propHolder
);
}
}
private
PropertiesHolder
refreshClassPathProperties
(
String
filename
,
PropertiesHolder
propHolder
)
{
Properties
properties
=
new
Properties
();
long
lastModified
=
-
1
;
try
{
Resource
[]
resources
=
resolver
.
getResources
(
filename
+
PROPERTIES_SUFFIX
);
for
(
Resource
resource
:
resources
)
{
String
sourcePath
=
resource
.
getURI
().
toString
().
replace
(
PROPERTIES_SUFFIX
,
""
);
PropertiesHolder
holder
=
super
.
refreshProperties
(
sourcePath
,
propHolder
);
properties
.
putAll
(
holder
.
getProperties
());
if
(
lastModified
<
resource
.
lastModified
())
lastModified
=
resource
.
lastModified
();
}
}
catch
(
IOException
ignored
)
{}
return
new
PropertiesHolder
(
properties
,
lastModified
);
}
}
basic-common/i18n/src/main/java/com/yiring/common/core/I18n.java
浏览文件 @
075e438f
...
...
@@ -7,7 +7,6 @@ import org.jetbrains.annotations.PropertyKey;
import
org.springframework.context.MessageSource
;
import
org.springframework.context.MessageSourceResolvable
;
import
org.springframework.context.i18n.LocaleContextHolder
;
import
org.springframework.core.Ordered
;
import
org.springframework.core.annotation.Order
;
import
org.springframework.stereotype.Component
;
...
...
@@ -20,7 +19,7 @@ import org.springframework.stereotype.Component;
*/
@Slf4j
@Order
(
Ordered
.
HIGHEST_PRECEDENCE
)
@Order
@Component
@RequiredArgsConstructor
public
class
I18n
{
...
...
basic-common/minio/src/main/java/com/yiring/common/service/FileManageService.java
0 → 100644
浏览文件 @
075e438f
/* (C) 2023 YiRing, Inc. */
package
com
.
yiring
.
common
.
service
;
import
org.springframework.web.multipart.MultipartFile
;
/**
* 文件管理服务
*
* @author Jim
* @version 0.1
* 2023/1/29 16:18
*/
public
interface
FileManageService
{
/**
* 文件上传
*
* @param file 文件
* @return 文件地址
*/
String
upload
(
MultipartFile
file
)
throws
Exception
;
}
basic-common/minio/src/main/java/com/yiring/common/service/UploadProcessService.java
浏览文件 @
075e438f
...
...
@@ -17,11 +17,11 @@ public interface UploadProcessService {
* PDF:haha.pdf -> haha.P12.pdf 记录 PDF 文件总页数,P12 即代表 PDF 总共 12 页,同时可通过 haha.P12.pdf.1.jpg... 按序号读取到每一页 PDF 对应的图片
* 音视频:haha.mp3/4 -> haha.T12.mp3/4 记录音视频的时长(秒),T12 即代表音视频时长为 12 秒
* 视频封面:haha.mp4 -> haha.mp4.jpg 截取视频封面
*
* @param object 上传文件存储地址
* @param is 文件流
* @param is
文件流
* @return 预处理后的文件地址(可能对文件名追加了时长、页数、分辨率等标识)
*/
@SuppressWarnings
(
"unused"
)
default
String
handle
(
String
object
,
InputStream
is
)
{
return
object
;
}
...
...
basic-common/minio/src/main/java/com/yiring/common/service/impl/DefaultUploadProcessServiceImpl.java
0 → 100644
浏览文件 @
075e438f
/* (C) 2023 YiRing, Inc. */
package
com
.
yiring
.
common
.
service
.
impl
;
import
com.yiring.common.service.UploadProcessService
;
import
org.springframework.stereotype.Service
;
/**
* @author Jim
* @version 0.1
* 2023/2/13 11:14
*/
@Service
public
class
DefaultUploadProcessServiceImpl
implements
UploadProcessService
{}
basic-common/minio/src/main/java/com/yiring/common/service/impl/FileManageServiceImpl.java
0 → 100644
浏览文件 @
075e438f
/* (C) 2023 YiRing, Inc. */
package
com
.
yiring
.
common
.
service
.
impl
;
import
cn.hutool.core.util.IdUtil
;
import
cn.hutool.core.util.StrUtil
;
import
com.yiring.common.core.Minio
;
import
com.yiring.common.service.FileManageService
;
import
com.yiring.common.service.UploadProcessService
;
import
lombok.RequiredArgsConstructor
;
import
lombok.extern.slf4j.Slf4j
;
import
org.springframework.stereotype.Service
;
import
org.springframework.web.multipart.MultipartFile
;
/**
* 文件管理服务实现
*
* @author Jim
* @version 0.1
* 2023/1/29 16:22
*/
@Slf4j
@Service
@RequiredArgsConstructor
public
class
FileManageServiceImpl
implements
FileManageService
{
final
Minio
minio
;
final
UploadProcessService
service
;
@Override
public
String
upload
(
MultipartFile
file
)
throws
Exception
{
// 获取文件名信息
String
filename
=
file
.
getOriginalFilename
();
if
(
StrUtil
.
isBlank
(
filename
))
{
throw
new
RuntimeException
(
"上传文件名不能为空"
);
}
// 获取文件信息以及默认存储地址
String
uuid
=
IdUtil
.
fastSimpleUUID
();
String
object
=
minio
.
buildUploadPath
(
filename
,
""
,
uuid
);
// 预处理(默认不做任何处理,具体逻辑需自行在外部实现)
object
=
service
.
handle
(
object
,
file
.
getInputStream
());
// 上传原文件
minio
.
putObject
(
file
.
getInputStream
(),
file
.
getContentType
(),
object
);
return
minio
.
getDefaultURI
(
object
);
}
}
basic-common/minio/src/main/java/com/yiring/common/web/MinioController.java
浏览文件 @
075e438f
...
...
@@ -8,7 +8,7 @@ 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.service.
UploadProcess
Service
;
import
com.yiring.common.service.
FileManage
Service
;
import
com.yiring.common.util.FileUtils
;
import
com.yiring.common.vo.ImageInfo
;
import
io.minio.GetObjectResponse
;
...
...
@@ -49,7 +49,7 @@ import org.springframework.web.multipart.MultipartFile;
public
class
MinioController
{
final
Minio
minio
;
final
UploadProcessService
s
ervice
;
final
FileManageService
fileManageS
ervice
;
/**
* minio 上传文件,成功返回文件 url
...
...
@@ -58,22 +58,8 @@ public class MinioController {
@PostMapping
(
value
=
"upload"
,
consumes
=
MediaType
.
MULTIPART_FORM_DATA_VALUE
)
public
Result
<
String
>
upload
(
@Parameter
(
name
=
"文件"
,
required
=
true
)
@RequestPart
(
"file"
)
MultipartFile
file
)
{
try
{
// 获取文件名信息
String
filename
=
file
.
getOriginalFilename
();
if
(
filename
==
null
)
{
throw
Status
.
BAD_REQUEST
.
exception
();
}
// 获取文件信息以及默认存储地址
String
uuid
=
IdUtil
.
fastSimpleUUID
();
String
object
=
minio
.
buildUploadPath
(
filename
,
""
,
uuid
);
// 预处理(默认不做任何处理,具体逻辑需自行在外部实现)
object
=
service
.
handle
(
object
,
file
.
getInputStream
());
// 上传原文件
minio
.
putObject
(
file
.
getInputStream
(),
file
.
getContentType
(),
object
);
return
Result
.
ok
(
minio
.
getDefaultURI
(
object
));
String
link
=
fileManageService
.
upload
(
file
);
return
Result
.
ok
(
link
);
}
catch
(
Exception
e
)
{
log
.
error
(
e
.
getMessage
(),
e
);
throw
Status
.
BAD_REQUEST
.
exception
();
...
...
basic-common/util/src/main/java/com/yiring/common/annotation/Times.java
浏览文件 @
075e438f
...
...
@@ -20,4 +20,9 @@ public @interface Times {
* 描述
*/
String
value
()
default
""
;
/**
* 阈值(毫秒),当计算耗时超过阈值时,打印警告日志
*/
long
threshold
()
default
0
;
}
basic-common/util/src/main/java/com/yiring/common/aspect/TimesAspect.java
浏览文件 @
075e438f
...
...
@@ -10,6 +10,7 @@ import org.aspectj.lang.ProceedingJoinPoint;
import
org.aspectj.lang.annotation.Around
;
import
org.aspectj.lang.annotation.Aspect
;
import
org.aspectj.lang.annotation.Pointcut
;
import
org.aspectj.lang.reflect.MethodSignature
;
import
org.springframework.stereotype.Component
;
/**
...
...
@@ -30,12 +31,22 @@ public class TimesAspect {
@Around
(
"times()"
)
public
Object
around
(
ProceedingJoinPoint
point
)
throws
Throwable
{
long
start
=
System
.
currentTimeMillis
();
try
{
return
point
.
proceed
();
}
finally
{
long
end
=
System
.
currentTimeMillis
();
log
.
info
(
"[Times] {}: {} ms"
,
getTimesValue
(
point
),
end
-
start
);
Object
result
=
point
.
proceed
();
long
end
=
System
.
currentTimeMillis
();
long
times
=
end
-
start
;
// 计算是否超过阈值,打印日志
MethodSignature
signature
=
(
MethodSignature
)
point
.
getSignature
();
Method
method
=
signature
.
getMethod
();
Times
annotation
=
method
.
getAnnotation
(
Times
.
class
);
String
name
=
StrUtil
.
isEmpty
(
annotation
.
value
())
?
method
.
getName
()
:
annotation
.
value
();
if
(
annotation
.
threshold
()
>
0
&&
times
>
annotation
.
threshold
())
{
log
.
warn
(
"[Times] {}: {} ms"
,
name
,
times
);
}
else
{
log
.
info
(
"[Times] {}: {} ms"
,
name
,
times
);
}
return
result
;
}
public
String
getTimesValue
(
JoinPoint
joinPoint
)
{
...
...
basic-common/util/src/main/java/com/yiring/common/util/FileUtils.java
浏览文件 @
075e438f
...
...
@@ -3,14 +3,10 @@ package com.yiring.common.util;
import
cn.hutool.core.codec.Base64
;
import
cn.hutool.core.io.FileUtil
;
import
cn.hutool.core.io.file.FileReader
;
import
com.yiring.common.vo.ImageInfo
;
import
jakarta.servlet.http.HttpServletResponse
;
import
java.awt.image.BufferedImage
;
import
java.io.ByteArrayInputStream
;
import
java.io.File
;
import
java.io.IOException
;
import
java.io.InputStream
;
import
java.io.*
;
import
java.net.URLEncoder
;
import
java.nio.charset.StandardCharsets
;
import
java.nio.file.Files
;
...
...
@@ -18,8 +14,8 @@ import java.nio.file.attribute.BasicFileAttributes;
import
javax.imageio.ImageIO
;
import
lombok.SneakyThrows
;
import
lombok.experimental.UtilityClass
;
import
org.apache.tomcat.util.http.fileupload.IOUtils
;
import
org.springframework.http.HttpHeaders
;
import
org.springframework.util.FileCopyUtils
;
/**
* 文件工具类
...
...
@@ -37,9 +33,9 @@ public class FileUtils {
*
* @param response HttpServletResponse
* @param file File
* @throws IOException IOException
*/
public
void
download
(
HttpServletResponse
response
,
File
file
)
throws
IOException
{
@SneakyThrows
public
void
download
(
HttpServletResponse
response
,
File
file
)
{
String
filename
=
URLEncoder
.
encode
(
file
.
getName
(),
StandardCharsets
.
UTF_8
);
BasicFileAttributes
basicFileAttributes
=
Files
.
readAttributes
(
file
.
toPath
(),
BasicFileAttributes
.
class
);
long
lastModified
=
basicFileAttributes
.
lastModifiedTime
().
toMillis
();
...
...
@@ -47,7 +43,13 @@ public class FileUtils {
response
.
setHeader
(
HttpHeaders
.
CONTENT_LENGTH
,
String
.
valueOf
(
basicFileAttributes
.
size
()));
response
.
setHeader
(
HttpHeaders
.
CONTENT_DISPOSITION
,
"attachment;filename="
+
filename
);
response
.
setContentType
(
FileUtil
.
getMimeType
(
file
.
toPath
()));
FileReader
.
create
(
file
).
writeToStream
(
response
.
getOutputStream
(),
true
);
try
(
FileInputStream
object
=
new
FileInputStream
(
file
);
BufferedInputStream
bis
=
new
BufferedInputStream
(
object
);
BufferedOutputStream
bos
=
new
BufferedOutputStream
(
response
.
getOutputStream
())
)
{
FileCopyUtils
.
copy
(
bis
,
bos
);
}
}
/**
...
...
@@ -59,8 +61,8 @@ public class FileUtils {
* @param filename 文件名称(带后缀)
* @param contentType 文档类型
* @param lastModified 最后修改时间
* @throws IOException IOException
*/
@SneakyThrows
public
void
download
(
HttpServletResponse
response
,
InputStream
object
,
...
...
@@ -68,15 +70,19 @@ public class FileUtils {
String
filename
,
String
contentType
,
long
lastModified
)
throws
IOException
{
)
{
filename
=
URLEncoder
.
encode
(
filename
,
StandardCharsets
.
UTF_8
);
response
.
setHeader
(
HttpHeaders
.
LAST_MODIFIED
,
String
.
valueOf
(
lastModified
));
response
.
setHeader
(
HttpHeaders
.
CONTENT_LENGTH
,
String
.
valueOf
(
length
));
response
.
setHeader
(
HttpHeaders
.
CONTENT_DISPOSITION
,
"attachment;filename="
+
filename
);
response
.
setContentType
(
contentType
);
IOUtils
.
copy
(
object
,
response
.
getOutputStream
());
object
.
close
();
response
.
flushBuffer
();
try
(
object
;
BufferedInputStream
bis
=
new
BufferedInputStream
(
object
);
BufferedOutputStream
bos
=
new
BufferedOutputStream
(
response
.
getOutputStream
())
)
{
FileCopyUtils
.
copy
(
bis
,
bos
);
}
}
/**
...
...
basic-dict/build.gradle
0 → 100644
浏览文件 @
075e438f
dependencies
{
implementation
project
(
':basic-common:core'
)
implementation
project
(
':basic-common:util'
)
implementation
'org.springframework.boot:spring-boot-starter-data-jpa'
implementation
'org.springframework.boot:spring-boot-starter-validation'
// 本地依赖
implementation
fileTree
(
dir:
project
.
rootDir
.
getPath
()
+
'\\libs'
,
includes:
[
'*jar'
])
// swagger(knife4j)
implementation
"com.github.xiaoymin:knife4j-openapi3-jakarta-spring-boot-starter:${knife4jOpen3Version}"
// hutool-core
implementation
"cn.hutool:hutool-core:${hutoolVersion}"
}
basic-dict/src/main/java/com/yiring/dict/domain/Category.java
0 → 100644
浏览文件 @
075e438f
/* (C) 2023 YiRing, Inc. */
package
com
.
yiring
.
dict
.
domain
;
import
static
com
.
yiring
.
dict
.
domain
.
Category
.
DELETE_SQL
;
import
static
com
.
yiring
.
dict
.
domain
.
Category
.
TABLE_NAME
;
import
com.yiring.common.domain.BasicEntity
;
import
jakarta.persistence.Column
;
import
jakarta.persistence.Entity
;
import
jakarta.persistence.Index
;
import
jakarta.persistence.Table
;
import
java.io.Serial
;
import
java.io.Serializable
;
import
lombok.*
;
import
lombok.experimental.FieldDefaults
;
import
lombok.experimental.FieldNameConstants
;
import
lombok.experimental.SuperBuilder
;
import
org.hibernate.annotations.Comment
;
import
org.hibernate.annotations.SQLDelete
;
import
org.hibernate.annotations.SQLDeleteAll
;
import
org.hibernate.annotations.Where
;
/**
* 分类字典
*
* @author Jim
* @version 0.1
* 2023/1/20 13:58
*/
@Getter
@Setter
@EqualsAndHashCode
(
callSuper
=
false
)
@SuperBuilder
(
toBuilder
=
true
)
@NoArgsConstructor
@AllArgsConstructor
@FieldNameConstants
@FieldDefaults
(
level
=
AccessLevel
.
PRIVATE
)
@SQLDelete
(
sql
=
DELETE_SQL
)
@SQLDeleteAll
(
sql
=
DELETE_SQL
)
@Where
(
clause
=
BasicEntity
.
Where
.
EXIST
)
@Entity
@Table
(
name
=
TABLE_NAME
,
indexes
=
{
@Index
(
columnList
=
BasicEntity
.
Fields
.
deleteTime
),
@Index
(
columnList
=
Category
.
Fields
.
name
),
@Index
(
columnList
=
Category
.
Fields
.
code
),
}
)
@Comment
(
"分类字典"
)
public
class
Category
extends
BasicEntity
implements
Serializable
{
public
static
final
String
TABLE_NAME
=
"SYS_CATEGORY"
;
public
static
final
String
DELETE_SQL
=
"update "
+
TABLE_NAME
+
Where
.
DELETE_SET
;
public
static
final
String
DEFAULT_TOP_PID
=
"0"
;
@Serial
private
static
final
long
serialVersionUID
=
-
3537807812313662319L
;
@Comment
(
"分类名称"
)
@Column
(
nullable
=
false
)
String
name
;
@Comment
(
"分类编号"
)
@Column
(
nullable
=
false
,
unique
=
true
)
String
code
;
@Comment
(
"分类描述"
)
String
description
;
@Comment
(
"分类父级 ID"
)
String
pid
;
@Comment
(
"分类是否启用"
)
@Column
(
nullable
=
false
)
Boolean
enable
;
}
basic-dict/src/main/java/com/yiring/dict/domain/CategoryRepository.java
0 → 100644
浏览文件 @
075e438f
/* (C) 2023 YiRing, Inc. */
package
com
.
yiring
.
dict
.
domain
;
import
java.io.Serializable
;
import
org.springframework.data.jpa.repository.JpaRepository
;
import
org.springframework.data.jpa.repository.JpaSpecificationExecutor
;
import
org.springframework.stereotype.Repository
;
/**
* @author Jim
* @version 0.1
* 2023/1/20 14:10
*/
@Repository
public
interface
CategoryRepository
extends
JpaRepository
<
Category
,
Serializable
>,
JpaSpecificationExecutor
<
Category
>
{}
basic-dict/src/main/java/com/yiring/dict/domain/Dict.java
0 → 100644
浏览文件 @
075e438f
/* (C) 2023 YiRing, Inc. */
package
com
.
yiring
.
dict
.
domain
;
import
static
com
.
yiring
.
dict
.
domain
.
Dict
.
DELETE_SQL
;
import
static
com
.
yiring
.
dict
.
domain
.
Dict
.
TABLE_NAME
;
import
com.fasterxml.jackson.annotation.JsonIgnore
;
import
com.yiring.common.domain.BasicEntity
;
import
jakarta.persistence.*
;
import
java.io.Serial
;
import
java.io.Serializable
;
import
java.util.ArrayList
;
import
java.util.List
;
import
lombok.*
;
import
lombok.experimental.FieldDefaults
;
import
lombok.experimental.FieldNameConstants
;
import
lombok.experimental.SuperBuilder
;
import
org.hibernate.annotations.Comment
;
import
org.hibernate.annotations.SQLDelete
;
import
org.hibernate.annotations.SQLDeleteAll
;
import
org.hibernate.annotations.Where
;
/**
* 字典
*
* @author Jim
* @version 0.1
* 2023/1/20 13:58
*/
@Getter
@Setter
@EqualsAndHashCode
(
callSuper
=
false
)
@SuperBuilder
(
toBuilder
=
true
)
@NoArgsConstructor
@AllArgsConstructor
@FieldNameConstants
@FieldDefaults
(
level
=
AccessLevel
.
PRIVATE
)
@SQLDelete
(
sql
=
DELETE_SQL
)
@SQLDeleteAll
(
sql
=
DELETE_SQL
)
@Where
(
clause
=
BasicEntity
.
Where
.
EXIST
)
@Entity
@Table
(
name
=
TABLE_NAME
,
indexes
=
{
@Index
(
columnList
=
BasicEntity
.
Fields
.
deleteTime
),
@Index
(
columnList
=
Dict
.
Fields
.
name
),
@Index
(
columnList
=
Dict
.
Fields
.
code
),
}
)
@Comment
(
"数据字典"
)
public
class
Dict
extends
BasicEntity
implements
Serializable
{
public
static
final
String
TABLE_NAME
=
"SYS_DICT"
;
public
static
final
String
DELETE_SQL
=
"update "
+
TABLE_NAME
+
BasicEntity
.
Where
.
DELETE_SET
;
@Serial
private
static
final
long
serialVersionUID
=
-
6780729527484051504L
;
@Comment
(
"字典名称"
)
@Column
(
nullable
=
false
)
String
name
;
@Comment
(
"字典编号"
)
@Column
(
nullable
=
false
,
unique
=
true
)
String
code
;
@Comment
(
"字典描述"
)
String
description
;
@JsonIgnore
@Builder
.
Default
@Comment
(
"字典选项集合"
)
@OneToMany
(
cascade
=
CascadeType
.
ALL
,
mappedBy
=
"dict"
)
List
<
DictItem
>
items
=
new
ArrayList
<>(
0
);
}
basic-dict/src/main/java/com/yiring/dict/domain/DictItem.java
0 → 100644
浏览文件 @
075e438f
/* (C) 2023 YiRing, Inc. */
package
com
.
yiring
.
dict
.
domain
;
import
static
com
.
yiring
.
dict
.
domain
.
DictItem
.
DELETE_SQL
;
import
static
com
.
yiring
.
dict
.
domain
.
DictItem
.
TABLE_NAME
;
import
com.yiring.common.domain.BasicEntity
;
import
jakarta.persistence.*
;
import
java.io.Serial
;
import
java.io.Serializable
;
import
lombok.*
;
import
lombok.experimental.FieldDefaults
;
import
lombok.experimental.FieldNameConstants
;
import
lombok.experimental.SuperBuilder
;
import
org.hibernate.annotations.Comment
;
import
org.hibernate.annotations.SQLDelete
;
import
org.hibernate.annotations.SQLDeleteAll
;
import
org.hibernate.annotations.Where
;
/**
* 字典选项
*
* @author Jim
* @version 0.1
* 2023/1/20 13:58
*/
@Getter
@Setter
@EqualsAndHashCode
(
callSuper
=
false
)
@SuperBuilder
(
toBuilder
=
true
)
@NoArgsConstructor
@AllArgsConstructor
@FieldNameConstants
@FieldDefaults
(
level
=
AccessLevel
.
PRIVATE
)
@SQLDelete
(
sql
=
DELETE_SQL
)
@SQLDeleteAll
(
sql
=
DELETE_SQL
)
@Where
(
clause
=
BasicEntity
.
Where
.
EXIST
)
@Entity
@Table
(
name
=
TABLE_NAME
,
indexes
=
{
@Index
(
columnList
=
BasicEntity
.
Fields
.
deleteTime
),
@Index
(
columnList
=
DictItem
.
Fields
.
name
),
@Index
(
columnList
=
DictItem
.
Fields
.
enable
),
}
)
@Comment
(
"数据字典选项"
)
public
class
DictItem
extends
BasicEntity
implements
Serializable
{
public
static
final
String
TABLE_NAME
=
"SYS_DICT_ITEM"
;
public
static
final
String
DELETE_SQL
=
"update "
+
TABLE_NAME
+
Where
.
DELETE_SET
;
@Serial
private
static
final
long
serialVersionUID
=
-
7321430621819435483L
;
@Comment
(
"字典"
)
@ManyToOne
@JoinColumn
(
nullable
=
false
)
Dict
dict
;
@Comment
(
"字典选项名称"
)
@Column
(
nullable
=
false
)
String
name
;
@Comment
(
"字典选项值"
)
@Column
(
nullable
=
false
)
String
value
;
@Comment
(
"字典选项描述"
)
String
description
;
@Comment
(
"字典选项排序序号"
)
Integer
serial
;
@Comment
(
"字典选项是否启用"
)
@Column
(
nullable
=
false
)
Boolean
enable
;
}
basic-dict/src/main/java/com/yiring/dict/domain/DictItemRepository.java
0 → 100644
浏览文件 @
075e438f
/* (C) 2023 YiRing, Inc. */
package
com
.
yiring
.
dict
.
domain
;
import
java.io.Serializable
;
import
org.springframework.data.jpa.repository.JpaRepository
;
import
org.springframework.data.jpa.repository.JpaSpecificationExecutor
;
import
org.springframework.stereotype.Repository
;
/**
* @author Jim
* @version 0.1
* 2023/1/20 14:10
*/
@Repository
public
interface
DictItemRepository
extends
JpaRepository
<
DictItem
,
Serializable
>,
JpaSpecificationExecutor
<
DictItem
>
{}
basic-dict/src/main/java/com/yiring/dict/domain/DictRepository.java
0 → 100644
浏览文件 @
075e438f
/* (C) 2023 YiRing, Inc. */
package
com
.
yiring
.
dict
.
domain
;
import
java.io.Serializable
;
import
org.springframework.data.jpa.repository.JpaRepository
;
import
org.springframework.data.jpa.repository.JpaSpecificationExecutor
;
import
org.springframework.stereotype.Repository
;
/**
* @author Jim
* @version 0.1
* 2023/1/20 14:10
*/
@Repository
public
interface
DictRepository
extends
JpaRepository
<
Dict
,
Serializable
>,
JpaSpecificationExecutor
<
Dict
>
{}
basic-dict/src/main/java/com/yiring/dict/param/DictItemParam.java
0 → 100644
浏览文件 @
075e438f
/* (C) 2022 YiRing, Inc. */
package
com
.
yiring
.
dict
.
param
;
import
com.yiring.common.validation.group.Group
;
import
io.swagger.v3.oas.annotations.Parameter
;
import
io.swagger.v3.oas.annotations.media.Schema
;
import
jakarta.validation.constraints.NotBlank
;
import
jakarta.validation.constraints.NotNull
;
import
java.io.Serial
;
import
java.io.Serializable
;
import
lombok.*
;
import
lombok.experimental.FieldDefaults
;
/**
* 数据字典选项参数
*
* @author Jim
* @version 0.1
* 2022/3/25 17:09
*/
@Schema
(
name
=
"DictItemParam"
)
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@FieldDefaults
(
level
=
AccessLevel
.
PRIVATE
)
public
class
DictItemParam
implements
Serializable
{
@Serial
private
static
final
long
serialVersionUID
=
-
4045504997838271449L
;
@Parameter
(
description
=
"id"
,
example
=
"1"
)
@NotBlank
(
groups
=
{
Group
.
Edit
.
class
})
String
id
;
@Parameter
(
description
=
"字典 ID"
,
example
=
"1"
)
@NotBlank
String
dictId
;
@Parameter
(
description
=
"字典选项名称"
,
example
=
"男"
)
@NotBlank
String
name
;
@Parameter
(
description
=
"字典选项值"
,
example
=
"1"
)
@NotBlank
String
value
;
@Parameter
(
description
=
"字典选项描述"
)
String
description
;
@Parameter
(
description
=
"字典选项排序序号"
)
Integer
serial
;
@Parameter
(
description
=
"字典选项是否启用"
,
example
=
"true"
)
@NotNull
Boolean
enable
;
}
basic-dict/src/main/java/com/yiring/dict/param/DictParam.java
0 → 100644
浏览文件 @
075e438f
/* (C) 2022 YiRing, Inc. */
package
com
.
yiring
.
dict
.
param
;
import
com.yiring.common.validation.group.Group
;
import
io.swagger.v3.oas.annotations.Parameter
;
import
io.swagger.v3.oas.annotations.media.Schema
;
import
jakarta.validation.constraints.NotBlank
;
import
java.io.Serial
;
import
java.io.Serializable
;
import
lombok.*
;
import
lombok.experimental.FieldDefaults
;
/**
* 数据字典参数
*
* @author Jim
* @version 0.1
* 2022/3/25 17:09
*/
@Schema
(
name
=
"DictParam"
)
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@FieldDefaults
(
level
=
AccessLevel
.
PRIVATE
)
public
class
DictParam
implements
Serializable
{
@Serial
private
static
final
long
serialVersionUID
=
-
5755754262686183535L
;
@Parameter
(
description
=
"id"
,
example
=
"1"
)
@NotBlank
(
groups
=
{
Group
.
Edit
.
class
})
String
id
;
@Parameter
(
description
=
"字典名称"
,
example
=
"性别"
)
@NotBlank
String
name
;
@Parameter
(
description
=
"字典编号"
,
example
=
"GENDER"
)
@NotBlank
String
code
;
@Parameter
(
description
=
"字典描述"
)
String
description
;
}
basic-dict/src/main/java/com/yiring/dict/param/SelectorDictItemParam.java
0 → 100644
浏览文件 @
075e438f
/* (C) 2022 YiRing, Inc. */
package
com
.
yiring
.
dict
.
param
;
import
io.swagger.v3.oas.annotations.Parameter
;
import
io.swagger.v3.oas.annotations.media.Schema
;
import
java.io.Serial
;
import
java.io.Serializable
;
import
lombok.*
;
import
lombok.experimental.FieldDefaults
;
/**
* 数据字典选项下拉查询参数
*
* @author Jim
* @version 0.1
* 2022/3/25 17:09
*/
@Schema
(
name
=
"SelectorDictItemParam"
)
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@FieldDefaults
(
level
=
AccessLevel
.
PRIVATE
)
public
class
SelectorDictItemParam
implements
Serializable
{
@Serial
private
static
final
long
serialVersionUID
=
-
8165884102089982475L
;
@Parameter
(
description
=
"字典 ID"
,
example
=
"1"
)
String
dictId
;
@Parameter
(
description
=
"字典 ID"
,
example
=
"1"
)
String
dictCode
;
@Parameter
(
description
=
"字典选项是否启用"
,
example
=
"true"
)
Boolean
enable
;
}
basic-dict/src/main/java/com/yiring/dict/vo/DictItemVo.java
0 → 100644
浏览文件 @
075e438f
/* (C) 2022 YiRing, Inc. */
package
com
.
yiring
.
dict
.
vo
;
import
io.swagger.v3.oas.annotations.media.Schema
;
import
java.io.Serial
;
import
java.io.Serializable
;
import
lombok.*
;
import
lombok.experimental.FieldDefaults
;
/**
* 数据字典选项输出
*
* @author Jim
* @version 0.1
* 2022/3/25 17:09
*/
@Schema
(
name
=
"DictItemVo"
)
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@FieldDefaults
(
level
=
AccessLevel
.
PRIVATE
)
public
class
DictItemVo
implements
Serializable
{
@Serial
private
static
final
long
serialVersionUID
=
-
5041312962732993815L
;
@Schema
(
description
=
"id"
,
example
=
"1"
)
String
id
;
@Schema
(
description
=
"字典选项名称"
,
example
=
"男"
)
String
name
;
@Schema
(
description
=
"字典选项值"
,
example
=
"1"
)
String
value
;
@Schema
(
description
=
"字典选项描述"
)
String
description
;
@Schema
(
description
=
"字典选项排序序号"
,
example
=
"1"
)
Integer
serial
;
@Schema
(
description
=
"字典选项是否启用"
,
example
=
"true"
)
Boolean
enable
;
}
basic-dict/src/main/java/com/yiring/dict/vo/DictVo.java
0 → 100644
浏览文件 @
075e438f
/* (C) 2022 YiRing, Inc. */
package
com
.
yiring
.
dict
.
vo
;
import
io.swagger.v3.oas.annotations.media.Schema
;
import
java.io.Serial
;
import
java.io.Serializable
;
import
lombok.*
;
import
lombok.experimental.FieldDefaults
;
/**
* 数据字典输出
*
* @author Jim
* @version 0.1
* 2022/3/25 17:09
*/
@Schema
(
name
=
"DictVo"
)
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@FieldDefaults
(
level
=
AccessLevel
.
PRIVATE
)
public
class
DictVo
implements
Serializable
{
@Serial
private
static
final
long
serialVersionUID
=
-
5041312962732993815L
;
@Schema
(
description
=
"id"
,
example
=
"1"
)
String
id
;
@Schema
(
description
=
"字典名称"
,
example
=
"性别"
)
String
name
;
@Schema
(
description
=
"字典编号"
,
example
=
"GENDER"
)
String
code
;
@Schema
(
description
=
"字典描述"
)
String
description
;
}
basic-dict/src/main/java/com/yiring/dict/web/DictController.java
0 → 100644
浏览文件 @
075e438f
/* (C) 2023 YiRing, Inc. */
package
com
.
yiring
.
dict
.
web
;
import
com.yiring.common.core.Result
;
import
com.yiring.common.core.Status
;
import
com.yiring.common.exception.BusinessException
;
import
com.yiring.common.param.IdParam
;
import
com.yiring.common.param.IdsParam
;
import
com.yiring.common.param.PageParam
;
import
com.yiring.common.util.Commons
;
import
com.yiring.common.validation.group.Group
;
import
com.yiring.common.vo.KeyValueVo
;
import
com.yiring.common.vo.PageVo
;
import
com.yiring.dict.domain.Dict
;
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
;
import
java.util.Optional
;
import
java.util.stream.Collectors
;
import
lombok.RequiredArgsConstructor
;
import
lombok.extern.slf4j.Slf4j
;
import
org.springdoc.core.annotations.ParameterObject
;
import
org.springframework.beans.BeanUtils
;
import
org.springframework.data.domain.Example
;
import
org.springframework.data.domain.Page
;
import
org.springframework.validation.annotation.Validated
;
import
org.springframework.web.bind.annotation.GetMapping
;
import
org.springframework.web.bind.annotation.PostMapping
;
import
org.springframework.web.bind.annotation.RequestMapping
;
import
org.springframework.web.bind.annotation.RestController
;
/**
* 数据字典管理
*
* @author Jim
* @version 0.1
* 2023/1/20 15:29
*/
@Slf4j
@Validated
@Tag
(
name
=
"字典管理"
,
description
=
"Dict"
,
extensions
=
{
@Extension
(
properties
=
{
@ExtensionProperty
(
name
=
"x-order"
,
value
=
"-96"
)
})
}
)
@RestController
@RequestMapping
(
"/sys/dict/"
)
@RequiredArgsConstructor
public
class
DictController
{
final
DictRepository
dictRepository
;
@Operation
(
summary
=
"新增"
)
@PostMapping
(
"add"
)
public
Result
<
String
>
add
(
@ParameterObject
@Validated
({
Group
.
Add
.
class
})
DictParam
param
)
{
if
(
has
(
param
.
getCode
()))
{
throw
BusinessException
.
i18n
(
"Code.101000"
);
}
Dict
entity
=
new
Dict
();
BeanUtils
.
copyProperties
(
param
,
entity
);
dictRepository
.
saveAndFlush
(
entity
);
return
Result
.
ok
();
}
@Operation
(
summary
=
"修改"
)
@PostMapping
(
"modify"
)
public
Result
<
String
>
modify
(
@ParameterObject
@Validated
({
Group
.
Edit
.
class
})
DictParam
param
)
{
Optional
<
Dict
>
optional
=
dictRepository
.
findById
(
param
.
getId
());
if
(
optional
.
isEmpty
())
{
throw
Status
.
NOT_FOUND
.
exception
();
}
Dict
entity
=
optional
.
get
();
if
(!
entity
.
getCode
().
equals
(
param
.
getCode
()))
{
throw
BusinessException
.
i18n
(
"Code.101002"
);
}
BeanUtils
.
copyProperties
(
param
,
entity
);
dictRepository
.
saveAndFlush
(
entity
);
return
Result
.
ok
();
}
@Operation
(
summary
=
"删除"
)
@PostMapping
(
"remove"
)
public
Result
<
String
>
remove
(
@ParameterObject
@Validated
IdsParam
param
)
{
List
<
Dict
>
dictList
=
dictRepository
.
findAllById
(
param
.
toIds
());
dictRepository
.
deleteAll
(
dictList
);
return
Result
.
ok
();
}
@Operation
(
summary
=
"查询"
)
@GetMapping
(
"find"
)
public
Result
<
DictVo
>
find
(
@ParameterObject
@Validated
IdParam
param
)
{
Optional
<
Dict
>
optional
=
dictRepository
.
findById
(
param
.
getId
());
if
(
optional
.
isEmpty
())
{
throw
Status
.
NOT_FOUND
.
exception
();
}
Dict
entity
=
optional
.
get
();
DictVo
vo
=
Commons
.
transform
(
entity
,
DictVo
.
class
);
return
Result
.
ok
(
vo
);
}
@Operation
(
summary
=
"分页查询"
)
@GetMapping
(
"page"
)
public
Result
<
PageVo
<
DictVo
>>
page
(
@ParameterObject
@Validated
PageParam
param
)
{
Page
<
Dict
>
page
=
dictRepository
.
findAll
(
PageParam
.
toPageable
(
param
));
List
<
DictVo
>
data
=
Commons
.
transform
(
page
.
getContent
(),
DictVo
.
class
);
PageVo
<
DictVo
>
vo
=
PageVo
.
build
(
data
,
page
.
getTotalElements
());
return
Result
.
ok
(
vo
);
}
@Operation
(
summary
=
"选项查询"
)
@GetMapping
(
"selector"
)
public
Result
<
ArrayList
<
KeyValueVo
>>
selector
()
{
List
<
Dict
>
dictList
=
dictRepository
.
findAll
();
ArrayList
<
KeyValueVo
>
vos
=
dictList
.
stream
()
.
map
(
dict
->
KeyValueVo
.
builder
().
key
(
dict
.
getId
()).
value
(
dict
.
getCode
()).
label
(
dict
.
getName
()).
build
())
.
collect
(
Collectors
.
toCollection
(
ArrayList:
:
new
));
return
Result
.
ok
(
vos
);
}
/**
* 检查是否存在已有相同编码的字典
*
* @param code 字典编码
* @return 是否存在
*/
private
boolean
has
(
String
code
)
{
Dict
entity
=
Dict
.
builder
().
code
(
code
).
build
();
return
dictRepository
.
count
(
Example
.
of
(
entity
))
>
0
;
}
}
basic-dict/src/main/java/com/yiring/dict/web/DictItemController.java
0 → 100644
浏览文件 @
075e438f
/* (C) 2023 YiRing, Inc. */
package
com
.
yiring
.
dict
.
web
;
import
cn.hutool.core.util.StrUtil
;
import
com.yiring.common.core.Result
;
import
com.yiring.common.core.Status
;
import
com.yiring.common.domain.BasicEntity
;
import
com.yiring.common.exception.BusinessException
;
import
com.yiring.common.param.IdParam
;
import
com.yiring.common.param.IdsParam
;
import
com.yiring.common.param.PageParam
;
import
com.yiring.common.util.Commons
;
import
com.yiring.common.validation.group.Group
;
import
com.yiring.common.vo.KeyValueVo
;
import
com.yiring.common.vo.PageVo
;
import
com.yiring.dict.domain.Dict
;
import
com.yiring.dict.domain.DictItem
;
import
com.yiring.dict.domain.DictItemRepository
;
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
jakarta.persistence.criteria.Predicate
;
import
java.util.ArrayList
;
import
java.util.List
;
import
java.util.Objects
;
import
java.util.Optional
;
import
java.util.stream.Collectors
;
import
lombok.RequiredArgsConstructor
;
import
lombok.extern.slf4j.Slf4j
;
import
org.springdoc.core.annotations.ParameterObject
;
import
org.springframework.beans.BeanUtils
;
import
org.springframework.data.domain.Page
;
import
org.springframework.data.jpa.domain.Specification
;
import
org.springframework.validation.annotation.Validated
;
import
org.springframework.web.bind.annotation.GetMapping
;
import
org.springframework.web.bind.annotation.PostMapping
;
import
org.springframework.web.bind.annotation.RequestMapping
;
import
org.springframework.web.bind.annotation.RestController
;
/**
* 数据字典选项管理
*
* @author Jim
* @version 0.1
* 2023/1/20 15:29
*/
@Slf4j
@Validated
@Tag
(
name
=
"字典选项管理"
,
description
=
"DictItem"
,
extensions
=
{
@Extension
(
properties
=
{
@ExtensionProperty
(
name
=
"x-order"
,
value
=
"-95"
)
})
}
)
@RestController
@RequestMapping
(
"/sys/dict/item/"
)
@RequiredArgsConstructor
public
class
DictItemController
{
final
DictItemRepository
dictItemRepository
;
@Operation
(
summary
=
"新增"
)
@PostMapping
(
"add"
)
public
Result
<
String
>
add
(
@ParameterObject
@Validated
({
Group
.
Add
.
class
})
DictItemParam
param
)
{
DictItem
entity
=
new
DictItem
();
BeanUtils
.
copyProperties
(
param
,
entity
);
entity
.
setDict
(
Dict
.
builder
().
id
(
param
.
getDictId
()).
build
());
dictItemRepository
.
saveAndFlush
(
entity
);
return
Result
.
ok
();
}
@Operation
(
summary
=
"修改"
)
@PostMapping
(
"modify"
)
public
Result
<
String
>
modify
(
@ParameterObject
@Validated
({
Group
.
Edit
.
class
})
DictItemParam
param
)
{
Optional
<
DictItem
>
optional
=
dictItemRepository
.
findById
(
param
.
getId
());
if
(
optional
.
isEmpty
())
{
throw
Status
.
NOT_FOUND
.
exception
();
}
DictItem
entity
=
optional
.
get
();
BeanUtils
.
copyProperties
(
param
,
entity
);
dictItemRepository
.
saveAndFlush
(
entity
);
return
Result
.
ok
();
}
@Operation
(
summary
=
"删除"
)
@PostMapping
(
"remove"
)
public
Result
<
String
>
remove
(
@ParameterObject
@Validated
IdsParam
param
)
{
List
<
DictItem
>
items
=
dictItemRepository
.
findAllById
(
param
.
toIds
());
dictItemRepository
.
deleteAll
(
items
);
return
Result
.
ok
();
}
@Operation
(
summary
=
"查询"
)
@GetMapping
(
"find"
)
public
Result
<
DictItemVo
>
find
(
@ParameterObject
@Validated
IdParam
param
)
{
Optional
<
DictItem
>
optional
=
dictItemRepository
.
findById
(
param
.
getId
());
if
(
optional
.
isEmpty
())
{
throw
Status
.
NOT_FOUND
.
exception
();
}
DictItem
entity
=
optional
.
get
();
DictItemVo
vo
=
Commons
.
transform
(
entity
,
DictItemVo
.
class
);
return
Result
.
ok
(
vo
);
}
@Operation
(
summary
=
"分页查询"
)
@GetMapping
(
"page"
)
public
Result
<
PageVo
<
DictItemVo
>>
page
(
@ParameterObject
@Validated
PageParam
param
)
{
Page
<
DictItem
>
page
=
dictItemRepository
.
findAll
(
PageParam
.
toPageable
(
param
));
List
<
DictItemVo
>
data
=
Commons
.
transform
(
page
.
toList
(),
DictItemVo
.
class
);
PageVo
<
DictItemVo
>
vo
=
PageVo
.
build
(
data
,
page
.
getTotalElements
());
return
Result
.
ok
(
vo
);
}
@Operation
(
summary
=
"选项查询"
)
@GetMapping
(
"selector"
)
public
Result
<
ArrayList
<
KeyValueVo
>>
selector
(
@ParameterObject
@Validated
SelectorDictItemParam
param
)
{
if
(
StrUtil
.
isBlank
(
param
.
getDictId
())
&&
StrUtil
.
isBlank
(
param
.
getDictCode
()))
{
throw
BusinessException
.
i18n
(
"Code.101001"
);
}
Specification
<
DictItem
>
specification
=
(
root
,
query
,
cb
)
->
{
List
<
Predicate
>
predicates
=
new
ArrayList
<>();
if
(
Objects
.
nonNull
(
param
.
getEnable
()))
{
predicates
.
add
(
cb
.
equal
(
root
.
get
(
DictItem
.
Fields
.
enable
),
param
.
getEnable
()));
}
if
(
Objects
.
nonNull
(
param
.
getDictId
()))
{
predicates
.
add
(
cb
.
equal
(
root
.
get
(
DictItem
.
Fields
.
dict
).
get
(
BasicEntity
.
Fields
.
id
),
param
.
getDictId
()));
}
if
(
Objects
.
nonNull
(
param
.
getDictCode
()))
{
predicates
.
add
(
cb
.
equal
(
root
.
get
(
DictItem
.
Fields
.
dict
).
get
(
Dict
.
Fields
.
code
),
param
.
getDictCode
()));
}
return
query
.
where
(
predicates
.
toArray
(
new
Predicate
[
0
])).
getRestriction
();
};
List
<
DictItem
>
items
=
dictItemRepository
.
findAll
(
specification
);
ArrayList
<
KeyValueVo
>
vos
=
items
.
stream
()
.
map
(
item
->
KeyValueVo
.
builder
().
key
(
item
.
getId
()).
value
(
item
.
getValue
()).
label
(
item
.
getName
()).
build
())
.
collect
(
Collectors
.
toCollection
(
ArrayList:
:
new
));
return
Result
.
ok
(
vos
);
}
}
basic-dict/src/main/resources/i18n/messages.properties
0 → 100644
浏览文件 @
075e438f
Code.101000
=
\u
5B57
\u5178\u
7F16
\u
53F7
\u
91CD
\u
590D
Code.101002
=
\u
5B57
\u5178\u
7F16
\u
53F7
\u
4E0D
\u5141\u
8BB8
\u
4FEE
\u6539
Code.101001
=
id
\u
548Ccode
\u
53C2
\u6570\u
81F3
\u
5C11
\u
4F20
\u9012\u
4E00
\u
4E2A
basic-dict/src/main/resources/i18n/messages_zh_CN.properties
0 → 100644
浏览文件 @
075e438f
Code.101000
=
\u
5B57
\u5178\u
7F16
\u
53F7
\u
91CD
\u
590D
Code.101002
=
\u
5B57
\u5178\u
7F16
\u
53F7
\u
4E0D
\u5141\u
8BB8
\u
4FEE
\u6539
Code.101001
=
id
\u
548Ccode
\u
53C2
\u6570\u
81F3
\u
5C11
\u
4F20
\u9012\u
4E00
\u
4E2A
build.gradle
浏览文件 @
075e438f
plugins
{
id
'java'
// https://start.spring.io
id
'org.springframework.boot'
version
'3.0.
1
'
id
'org.springframework.boot'
version
'3.0.
2
'
id
'org.graalvm.buildtools.native'
version
'0.9.18'
// https://plugins.gradle.org/plugin/io.spring.dependency-management
id
'io.spring.dependency-management'
version
'1.1.0'
// https://plugins.gradle.org/plugin/com.diffplug.spotless
id
"com.diffplug.spotless"
version
"6.1
2.1
"
id
"com.diffplug.spotless"
version
"6.1
3.0
"
}
ext
{
// Spotless
// https://www.npmjs.com/package/prettier
prettierVersion
=
'2.8.
2
'
prettierVersion
=
'2.8.
3
'
// https://www.npmjs.com/package/prettier-plugin-java
prettierJavaVersion
=
'2.0.0'
// SpringCloud
// https://start.spring.io/
springCloudVersion
=
'2022.0.0'
springCloudVersion
=
'2022.0.1'
// SpringBootAdmin
springBootAdminVersion
=
'3.0.0'
// Dependencies
// // https://mvnrepository.com/artifact/com.github.xiaoymin/knife4j-openapi3-jakarta-spring-boot-starter
...
...
@@ -28,9 +30,9 @@ ext {
// https://mvnrepository.com/artifact/cn.dev33/sa-token-spring-boot3-starter
saTokenVersion
=
'1.34.0'
// https://mvnrepository.com/artifact/cn.hutool/hutool-all
hutoolVersion
=
'5.8.1
1
'
hutoolVersion
=
'5.8.1
2
'
// https://mvnrepository.com/artifact/com.alibaba.fastjson2/fastjson2
fastJsonVersion
=
'2.0.2
2
'
fastJsonVersion
=
'2.0.2
3
'
// https://mvnrepository.com/artifact/com.xuxueli/xxl-job-core
xxlJobVersion
=
'2.3.1'
// https://mvnrepository.com/artifact/com.squareup.okhttp3/okhttp
...
...
@@ -38,9 +40,9 @@ ext {
// https://mvnrepository.com/artifact/io.minio/minio
minioVersion
=
'8.5.1'
// https://mvnrepository.com/artifact/io.hypersistence/hypersistence-utils-hibernate-60
hibernateTypesVersion
=
'3.1.
0
'
hibernateTypesVersion
=
'3.1.
2
'
// https://mvnrepository.com/artifact/org.hibernate/hibernate-spatial
hibernateSpatialVersion
=
'6.1.
6
.Final'
hibernateSpatialVersion
=
'6.1.
7
.Final'
// https://mvnrepository.com/artifact/org.locationtech.jts/jts-core
jtsVersion
=
'1.19.0'
// https://mvnrepository.com/artifact/com.github.liaochong/myexcel
...
...
@@ -49,6 +51,8 @@ ext {
jetbrainsAnnotationsVersion
=
'24.0.0'
// https://mvnrepository.com/artifact/org.apache.pdfbox/pdfbox
pdfboxVersion
=
'2.0.27'
// https://mvnrepository.com/artifact/net.bramp.ffmpeg/ffmpeg
ffmpegWrapperVersion
=
'0.7.0'
}
allprojects
{
...
...
@@ -87,6 +91,7 @@ subprojects {
dependencyManagement
{
imports
{
mavenBom
"org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
mavenBom
"de.codecentric:spring-boot-admin-dependencies:${springBootAdminVersion}"
}
}
...
...
settings.gradle
浏览文件 @
075e438f
...
...
@@ -8,6 +8,7 @@ pluginManagement {
rootProject
.
name
=
'basic-api-boot'
include
'app'
include
'basic-auth'
include
'basic-dict'
include
'basic-websocket'
include
'basic-common:core'
include
'basic-common:util'
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论