Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
S
spring-microservice-exam
Project
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
汪想
spring-microservice-exam
Commits
21f4f0c1
Commit
21f4f0c1
authored
Apr 06, 2020
by
tangyi
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
优化
parent
fd8c285d
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
53 changed files
with
1216 additions
and
476 deletions
+1216
-476
CHANGELOG.md
CHANGELOG.md
+9
-1
README.md
README.md
+2
-2
SysConfigDto.java
...java/com/github/tangyi/common/basic/dto/SysConfigDto.java
+0
-5
SysProperties.java
.../github/tangyi/common/basic/properties/SysProperties.java
+15
-0
CustomGlobalExceptionHandler.java
...i/common/config/handler/CustomGlobalExceptionHandler.java
+58
-10
ApiMsg.java
...n/java/com/github/tangyi/common/core/constant/ApiMsg.java
+6
-0
FileUtil.java
...in/java/com/github/tangyi/common/core/utils/FileUtil.java
+109
-0
FastDfsService.java
...in/java/com/github/tangyi/oss/service/FastDfsService.java
+0
-1
QiNiuUtil.java
...rc/main/java/com/github/tangyi/oss/service/QiNiuUtil.java
+18
-16
user-service.yml
config-service/src/main/resources/config/user-service.yml
+4
-0
dev.env
docs/deploy/dev.env
+39
-0
docker-compose-base.yml
docs/deploy/docker-compose-base.yml
+2
-0
docker-compose-services.yml
docs/deploy/docker-compose-services.yml
+13
-0
docker-compose.env
docs/deploy/docker-compose.env
+5
-2
microservice_auth.sql
docs/deploy/mysql/init/microservice_auth.sql
+2
-1
microservice_exam.sql
docs/deploy/mysql/init/microservice_exam.sql
+0
-0
microservice_user.sql
docs/deploy/mysql/init/microservice_user.sql
+15
-12
update.sql
docs/deploy/mysql/update.sql
+6
-2
readme.md
frontend/spring-microservice-exam-miniprogram/readme.md
+0
-0
package.json
frontend/spring-microservice-exam-ui/package.json
+1
-1
attachment.js
...d/spring-microservice-exam-ui/src/api/admin/attachment.js
+2
-2
index.vue
...service-exam-ui/src/components/Subjects/Choices/index.vue
+22
-78
index.vue
...rvice-exam-ui/src/components/Subjects/Judgement/index.vue
+11
-66
index.vue
...exam-ui/src/components/Subjects/MultipleChoices/index.vue
+31
-99
index.vue
...ice-exam-ui/src/components/Subjects/ShortAnswer/index.vue
+7
-52
index.vue
...ing-microservice-exam-ui/src/components/Tinymce/index.vue
+3
-14
toolbar.js
...ng-microservice-exam-ui/src/components/Tinymce/toolbar.js
+1
-1
zh.js
frontend/spring-microservice-exam-ui/src/lang/zh.js
+1
-0
list.vue
...spring-microservice-exam-ui/src/views/attachment/list.vue
+90
-21
exam.vue
frontend/spring-microservice-exam-ui/src/views/exam/exam.vue
+3
-4
examSubjects.vue
...ring-microservice-exam-ui/src/views/exam/examSubjects.vue
+0
-1
subject.vue
...nd/spring-microservice-exam-ui/src/views/exam/subject.vue
+117
-3
message.vue
...pring-microservice-exam-ui/src/views/personal/message.vue
+1
-4
menu.vue
frontend/spring-microservice-exam-ui/src/views/sys/menu.vue
+3
-0
login_bg.jpeg
...tend/spring-microservice-exam-ui/static/img/login_bg.jpeg
+0
-0
package.json
frontend/spring-microservice-exam-web/package.json
+1
-1
Index.vue
frontend/spring-microservice-exam-web/src/views/Index.vue
+1
-1
header.vue
.../spring-microservice-exam-web/src/views/common/header.vue
+1
-1
account.vue
...ring-microservice-exam-web/src/views/personal/account.vue
+2
-5
CourseService.java
...in/java/com/github/tangyi/exam/service/CourseService.java
+2
-3
ExaminationService.java
...va/com/github/tangyi/exam/service/ExaminationService.java
+2
-1
AttachmentConstant.java
...m/github/tangyi/user/api/constant/AttachmentConstant.java
+5
-0
Attachment.java
...in/java/com/github/tangyi/user/api/module/Attachment.java
+17
-5
AttachmentController.java
...m/github/tangyi/user/controller/AttachmentController.java
+84
-14
AttachUploaderEnum.java
...java/com/github/tangyi/user/enums/AttachUploaderEnum.java
+36
-0
AttachmentService.java
...ava/com/github/tangyi/user/service/AttachmentService.java
+10
-47
AbstractUploader.java
...ava/com/github/tangyi/user/uploader/AbstractUploader.java
+61
-0
FastDfsUploader.java
...java/com/github/tangyi/user/uploader/FastDfsUploader.java
+57
-0
FileUploader.java
...in/java/com/github/tangyi/user/uploader/FileUploader.java
+82
-0
IUploader.java
.../main/java/com/github/tangyi/user/uploader/IUploader.java
+48
-0
QiNiuUploader.java
...n/java/com/github/tangyi/user/uploader/QiNiuUploader.java
+42
-0
UploadInvoker.java
...n/java/com/github/tangyi/user/uploader/UploadInvoker.java
+155
-0
AttachmentMapper.xml
...er-service/src/main/resources/mapper/AttachmentMapper.xml
+14
-0
No files found.
CHANGELOG.md
View file @
21f4f0c1
Version v3.7.0 (2020-03-39)
Version v3.7.0 (2020-04-05)
--------------------------
改进:
*
优化附件上传模块,支持存储方式支持本地、fastDfs、七牛云
*
优化题目编辑页面,统一采用富文本输入,支持数学公式
Version v3.7.0 (2020-03-31)
--------------------------
改进:
...
...
README.md
View file @
21f4f0c1
...
...
@@ -45,7 +45,7 @@
-
构建工具:
`Maven`
-
后台 API 文档:
`Swagger`
-
消息队列:
`RabbitMQ`
-
文件系统:
`七牛云`
、
`FastDfs`
-
文件系统:
`
本地目录`
、
`
七牛云`
、
`FastDfs`
-
缓存:
`Redis`
-
前端:
`vue`
-
小程序:
`wepy`
...
...
@@ -94,7 +94,7 @@
-
知识库:知识库增删改查、上传附件
附件管理:项目的所有附件存储在
`fastDfs`
里,提供统一的管理入口
-
附件列表:管理所有附件,如用户头像、考试附件、知识库附件等
。
-
附件列表:管理所有附件,如用户头像、考试附件、知识库附件等
,存储方式支持服务器本地目录、
`fastDfs`
,七牛云
个人管理:管理个人资料和修改密码
-
个人资料:姓名、头像等基本信息的修改
...
...
common/common-basic/src/main/java/com/github/tangyi/common/basic/dto/SysConfigDto.java
View file @
21f4f0c1
...
...
@@ -14,11 +14,6 @@ public class SysConfigDto implements Serializable {
private
static
final
long
serialVersionUID
=
1L
;
/**
* fastDfs服务器的HTTP地址
*/
private
String
fdfsHttpHost
;
/**
* 上传地址
*/
private
String
uploadUrl
;
...
...
common/common-basic/src/main/java/com/github/tangyi/common/basic/properties/SysProperties.java
View file @
21f4f0c1
...
...
@@ -54,4 +54,19 @@ public class SysProperties {
* 二维码生成链接
*/
private
String
qrCodeUrl
;
/**
* 上传类型,1:本地目录,2:fastDfs,3:七牛云
*/
private
String
attachUploadType
;
/**
* 附件上传目录
*/
private
String
attachPath
;
/**
* 支持预览的附件后缀名,多个用逗号隔开,如:png,jpeg
*/
private
String
canPreview
;
}
common/common-config/src/main/java/com/github/tangyi/common/config/handler/CustomGlobalExceptionHandler.java
View file @
21f4f0c1
...
...
@@ -3,17 +3,22 @@ package com.github.tangyi.common.config.handler;
import
com.github.tangyi.common.core.constant.ApiMsg
;
import
com.github.tangyi.common.core.exceptions.CommonException
;
import
com.github.tangyi.common.core.model.ResponseBean
;
import
com.github.tangyi.common.core.utils.JsonMapper
;
import
org.springframework.context.support.DefaultMessageSourceResolvable
;
import
org.springframework.http.HttpHeaders
;
import
org.springframework.http.HttpStatus
;
import
org.springframework.http.ResponseEntity
;
import
org.springframework.http.converter.HttpMessageConversionException
;
import
org.springframework.validation.BindException
;
import
org.springframework.validation.BindingResult
;
import
org.springframework.validation.FieldError
;
import
org.springframework.web.bind.MethodArgumentNotValidException
;
import
org.springframework.web.bind.annotation.ControllerAdvice
;
import
org.springframework.web.bind.annotation.ExceptionHandler
;
import
org.springframework.web.context.request.WebRequest
;
import
org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler
;
import
org.springframework.web.bind.annotation.RestControllerAdvice
;
import
java.util.HashMap
;
import
java.util.List
;
import
java.util.Map
;
import
java.util.stream.Collectors
;
/**
...
...
@@ -22,8 +27,8 @@ import java.util.stream.Collectors;
* @author tangyi
* @date 2019/05/25 15:36
*/
@ControllerAdvice
public
class
CustomGlobalExceptionHandler
extends
ResponseEntityExceptionHandler
{
@
Rest
ControllerAdvice
public
class
CustomGlobalExceptionHandler
{
/**
* 处理参数校验异常
...
...
@@ -31,13 +36,12 @@ public class CustomGlobalExceptionHandler extends ResponseEntityExceptionHandler
* @param ex ex
* @param headers headers
* @param status status
* @param request request
* @return ResponseEntity
*/
@
Override
p
rotected
ResponseEntity
<
Object
>
handleMethodArgumentNotValid
(
MethodArgumentNotValidException
ex
,
HttpHeaders
headers
,
HttpStatus
status
,
WebRequest
request
)
{
@
ExceptionHandler
(
MethodArgumentNotValidException
.
class
)
p
ublic
ResponseEntity
<
Object
>
validationBodyException
(
MethodArgumentNotValidException
ex
,
HttpHeaders
headers
,
HttpStatus
status
)
{
// 获取所有异常信息
List
<
String
>
errors
=
ex
.
getBindingResult
()
.
getFieldErrors
()
...
...
@@ -49,6 +53,18 @@ public class CustomGlobalExceptionHandler extends ResponseEntityExceptionHandler
}
/**
* 参数类型转换错误
*
* @param exception 错误
* @return 错误信息
*/
@ExceptionHandler
(
HttpMessageConversionException
.
class
)
public
ResponseEntity
<
ResponseBean
<
String
>>
parameterTypeException
(
HttpMessageConversionException
exception
)
{
ResponseBean
<
String
>
responseBean
=
new
ResponseBean
<>(
exception
.
getMessage
(),
ApiMsg
.
KEY_PARAM_VALIDATE
,
ApiMsg
.
ERROR
);
return
new
ResponseEntity
<>(
responseBean
,
HttpStatus
.
OK
);
}
/**
* 处理CommonException
*
* @param e e
...
...
@@ -60,9 +76,40 @@ public class CustomGlobalExceptionHandler extends ResponseEntityExceptionHandler
return
new
ResponseEntity
<>(
responseBean
,
HttpStatus
.
OK
);
}
/**
* 捕获@Validate校验抛出的异常
*
* @param e e
* @return ResponseEntity
*/
@ExceptionHandler
(
BindException
.
class
)
public
ResponseEntity
<
Object
>
validExceptionHandler
(
BindException
e
)
{
Exception
ex
=
parseBindingResult
(
e
.
getBindingResult
());
ResponseBean
<
String
>
responseBean
=
new
ResponseBean
<>(
ex
.
getMessage
(),
ApiMsg
.
KEY_PARAM_VALIDATE
,
ApiMsg
.
ERROR
);
return
new
ResponseEntity
<>(
responseBean
,
HttpStatus
.
OK
);
}
@ExceptionHandler
(
Exception
.
class
)
public
ResponseEntity
<
ResponseBean
<
String
>>
handleException
(
Exception
e
)
{
ResponseBean
<
String
>
responseBean
=
new
ResponseBean
<>(
e
.
getMessage
(),
ApiMsg
.
KEY_ERROR
,
ApiMsg
.
ERROR
);
return
new
ResponseEntity
<>(
responseBean
,
HttpStatus
.
OK
);
}
/**
* 提取Validator产生的异常错误
*
* @param bindingResult bindingResult
* @return Exception
*/
private
Exception
parseBindingResult
(
BindingResult
bindingResult
)
{
Map
<
String
,
String
>
errorMsgs
=
new
HashMap
<>();
for
(
FieldError
error
:
bindingResult
.
getFieldErrors
())
{
errorMsgs
.
put
(
error
.
getField
(),
error
.
getDefaultMessage
());
}
if
(
errorMsgs
.
isEmpty
())
{
return
new
CommonException
(
ApiMsg
.
KEY_PARAM_VALIDATE
+
""
);
}
else
{
return
new
CommonException
(
JsonMapper
.
toJsonString
(
errorMsgs
));
}
}
}
\ No newline at end of file
common/common-core/src/main/java/com/github/tangyi/common/core/constant/ApiMsg.java
View file @
21f4f0c1
...
...
@@ -101,6 +101,11 @@ public class ApiMsg {
public
static
final
int
KEY_AUTHENTICATION
=
405
;
/**
* 参数校验
*/
public
static
final
int
KEY_PARAM_VALIDATE
=
406
;
/**
* code和提示内容的对应关系
*/
private
static
final
Map
<
Integer
,
String
>
CODE_MAP
=
new
HashMap
<>();
...
...
@@ -130,6 +135,7 @@ public class ApiMsg {
KEY_MAP
.
put
(
KEY_VALIDATE_CODE
,
"VALIDATE CODE"
);
KEY_MAP
.
put
(
KEY_TOKEN
,
"TOKEN"
);
KEY_MAP
.
put
(
KEY_ACCESS
,
"ACCESS"
);
KEY_MAP
.
put
(
KEY_PARAM_VALIDATE
,
"PARAM_VALIDATE"
);
}
public
static
String
code2Msg
(
int
codeKey
,
int
msgKey
)
{
...
...
common/common-core/src/main/java/com/github/tangyi/common/core/utils/FileUtil.java
View file @
21f4f0c1
package
com
.
github
.
tangyi
.
common
.
core
.
utils
;
import
lombok.extern.slf4j.Slf4j
;
import
java.io.File
;
/**
* 文件工具类
*
* @author tangyi
* @date 2018/10/30 22:05
*/
@Slf4j
public
class
FileUtil
{
/**
...
...
@@ -25,4 +30,108 @@ public class FileUtil {
}
return
""
;
}
/**
* 创建目录
*
* @param descDirName descDirName
* @return String
*/
public
static
boolean
createDirectory
(
String
descDirName
)
{
String
descDirNames
=
descDirName
;
if
(!
descDirNames
.
endsWith
(
File
.
separator
))
{
descDirNames
=
descDirNames
+
File
.
separator
;
}
File
descDir
=
new
File
(
descDirNames
);
if
(
descDir
.
exists
())
{
log
.
debug
(
"dir "
+
descDirNames
+
" already exits!"
);
return
false
;
}
if
(
descDir
.
mkdirs
())
{
log
.
debug
(
"dir "
+
descDirNames
+
" create success!"
);
return
true
;
}
else
{
log
.
debug
(
"dir "
+
descDirNames
+
" create failure!"
);
return
false
;
}
}
/**
*
* 删除目录及目录下的文件
*
* @param dirName 被删除的目录所在的文件路径
* @return 如果目录删除成功,则返回true,否则返回false
*/
public
static
boolean
deleteDirectory
(
String
dirName
)
{
String
dirNames
=
dirName
;
if
(!
dirNames
.
endsWith
(
File
.
separator
))
{
dirNames
=
dirNames
+
File
.
separator
;
}
File
dirFile
=
new
File
(
dirNames
);
if
(!
dirFile
.
exists
()
||
!
dirFile
.
isDirectory
())
{
log
.
debug
(
dirNames
+
" dir not exists!"
);
return
true
;
}
boolean
flag
=
true
;
// 列出全部文件及子目录
File
[]
files
=
dirFile
.
listFiles
();
if
(
files
!=
null
)
{
for
(
File
file
:
files
)
{
// 删除子文件
if
(
file
.
isFile
())
{
flag
=
FileUtil
.
deleteFile
(
file
.
getAbsolutePath
());
// 如果删除文件失败,则退出循环
if
(!
flag
)
{
break
;
}
}
// 删除子目录
else
if
(
file
.
isDirectory
())
{
flag
=
FileUtil
.
deleteDirectory
(
file
.
getAbsolutePath
());
// 如果删除子目录失败,则退出循环
if
(!
flag
)
{
break
;
}
}
}
}
if
(!
flag
)
{
log
.
debug
(
"delete dir failure!"
);
return
false
;
}
// 删除当前目录
if
(
dirFile
.
delete
())
{
log
.
debug
(
"delete dir "
+
dirName
+
" success!"
);
return
true
;
}
else
{
log
.
debug
(
"delete dir "
+
dirName
+
" failure!"
);
return
false
;
}
}
/**
*
* 删除单个文件
*
* @param fileName 被删除的文件名
* @return 如果删除成功,则返回true,否则返回false
*/
public
static
boolean
deleteFile
(
String
fileName
)
{
File
file
=
new
File
(
fileName
);
if
(
file
.
exists
()
&&
file
.
isFile
())
{
if
(
file
.
delete
())
{
log
.
debug
(
"delete file "
+
fileName
+
" success!"
);
return
true
;
}
else
{
log
.
debug
(
"delete file "
+
fileName
+
" failure!"
);
return
false
;
}
}
else
{
log
.
debug
(
fileName
+
" not exists!"
);
return
true
;
}
}
}
common/common-oss/src/main/java/com/github/tangyi/oss/service/FastDfsService.java
View file @
21f4f0c1
...
...
@@ -27,7 +27,6 @@ import java.util.Set;
* @author tangyi
* @date 2018-01-04 10:34
*/
@Deprecated
@Slf4j
@AllArgsConstructor
@Service
...
...
common/common-oss/src/main/java/com/github/tangyi/oss/service/QiNiu
Service
.java
→
common/common-oss/src/main/java/com/github/tangyi/oss/service/QiNiu
Util
.java
View file @
21f4f0c1
...
...
@@ -2,6 +2,7 @@ package com.github.tangyi.oss.service;
import
com.github.tangyi.common.core.constant.CommonConstant
;
import
com.github.tangyi.common.core.utils.JsonMapper
;
import
com.github.tangyi.common.core.utils.SpringContextHolder
;
import
com.github.tangyi.oss.config.QiNiuConfig
;
import
com.github.tangyi.oss.exceptions.OssException
;
import
com.qiniu.common.QiniuException
;
...
...
@@ -14,10 +15,7 @@ import com.qiniu.storage.model.DefaultPutRet;
import
com.qiniu.util.Auth
;
import
lombok.extern.slf4j.Slf4j
;
import
org.apache.commons.lang3.StringUtils
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.stereotype.Service
;
import
javax.annotation.PostConstruct
;
import
java.io.UnsupportedEncodingException
;
import
java.net.URLEncoder
;
...
...
@@ -27,10 +25,7 @@ import java.net.URLEncoder;
* @date 2019/12/8 20:25
*/
@Slf4j
@Service
public
class
QiNiuService
{
private
final
QiNiuConfig
qiNiuConfig
;
public
class
QiNiuUtil
{
private
Auth
auth
;
...
...
@@ -38,18 +33,25 @@ public class QiNiuService {
private
BucketManager
bucketManager
;
@Autowired
public
QiNiuService
(
QiNiuConfig
qiNiuConfig
)
{
this
.
qiNiuConfig
=
qiNiuConfig
;
private
QiNiuConfig
qiNiuConfig
;
private
static
QiNiuUtil
instance
;
public
synchronized
static
QiNiuUtil
getInstance
()
{
if
(
instance
==
null
)
{
instance
=
new
QiNiuUtil
();
}
return
instance
;
}
@PostConstruct
public
void
init
()
{
public
QiNiuUtil
()
{
qiNiuConfig
=
SpringContextHolder
.
getApplicationContext
().
getBean
(
QiNiuConfig
.
class
);
if
(
StringUtils
.
isNotBlank
(
qiNiuConfig
.
getAccessKey
())
&&
StringUtils
.
isNotBlank
(
qiNiuConfig
.
getSecretKey
()))
{
auth
=
Auth
.
create
(
qiNiuConfig
.
getAccessKey
(),
qiNiuConfig
.
getSecretKey
());
instance
=
new
QiNiuUtil
();
instance
.
auth
=
Auth
.
create
(
qiNiuConfig
.
getAccessKey
(),
qiNiuConfig
.
getSecretKey
());
Configuration
config
=
new
Configuration
(
Region
.
region2
());
uploadManager
=
new
UploadManager
(
config
);
bucketManager
=
new
BucketManager
(
auth
,
config
);
instance
.
uploadManager
=
new
UploadManager
(
config
);
instance
.
bucketManager
=
new
BucketManager
(
instance
.
auth
,
config
);
}
}
...
...
@@ -59,7 +61,7 @@ public class QiNiuService {
* @return String
*/
public
String
getQiNiuToken
()
{
return
auth
.
uploadToken
(
qiNiuConfig
.
getBucket
());
return
auth
.
uploadToken
(
getInstance
().
qiNiuConfig
.
getBucket
());
}
/**
...
...
config-service/src/main/resources/config/user-service.yml
View file @
21f4f0c1
...
...
@@ -112,6 +112,9 @@ sys:
defaultAvatar
:
/static/img/avatar/
key
:
'
1234567887654321'
cacheExpire
:
86400
# 缓存失效时间,单位秒,默认一天
attachtUploadType
:
1
# 上传类型,1:本地目录,2:fastDfs,3:七牛云
attachPath
:
${ATTACH_PATH:C:/attach}
# 附件上传目录
canPreview
:
jpg,png,jpeg,gif
# 支持预览的格式
# feign相关配置
feign
:
...
...
@@ -158,6 +161,7 @@ ignore:
-
/v1/menu/anonymousUser/**
-
/v1/code/**
-
/v1/attachment/download
-
/v1/attachment/preview
-
/v1/log/**
-
/authentication/**
-
/v1/authentication/**
...
...
docs/deploy/dev.env
0 → 100644
View file @
21f4f0c1
DB_AUTH=dev_microservice_auth
DB_USER=dev_microservice_user
DB_EXAM=dev_microservice_exam
DB_GATEWAY=dev_microservice_gateway
# 租户标识,默认gitee
TENANT_CODE=gitee
# 网关token转换
GATEWAY_TOKEN_TRANSFER=false
# 网关secret
GATEWAY_SECRET=15521089185
# 环境配置
SPRING_PROFILES_ACTIVE=native
# consul配置
CONSUL_HOST=localhost
CONSUL_PORT=8500
# rabbitMq配置
RABBIT_HOST=localhost
RABBIT_PORT=5672
RABBITMQ_DEFAULT_USER=guest
RABBITMQ_DEFAULT_PASS=guest
# Redis配置
REDIS_HOST=localhost
# 数据库配置
MYSQL_HOST=118.25.138.130
MYSQL_USERNAME=root
MYSQL_PASSWORD=15521089185
# ID生成配置
CLUSTER_WORK_ID=1
CLUSTER_DATA_CENTER_ID=1
\ No newline at end of file
docs/deploy/docker-compose-base.yml
View file @
21f4f0c1
...
...
@@ -97,6 +97,8 @@ services:
-
redis
ports
:
-
"
9181:9181"
volumes
:
-
./logs/config-service:/logs/config-service
networks
:
-
net
...
...
docs/deploy/docker-compose-services.yml
View file @
21f4f0c1
...
...
@@ -10,6 +10,8 @@ services:
restart
:
always
ports
:
-
"
9180:9180"
volumes
:
-
./logs/gateway-service:/logs/gateway-service
networks
:
-
net
...
...
@@ -23,6 +25,8 @@ services:
restart
:
always
ports
:
-
"
9182:9182"
volumes
:
-
./logs/auth-service:/logs/auth-service
networks
:
-
net
...
...
@@ -36,6 +40,9 @@ services:
restart
:
always
ports
:
-
"
9183:9183"
volumes
:
-
./logs/user-service:/logs/user-service
-
./attach:/attach
networks
:
-
net
...
...
@@ -49,6 +56,8 @@ services:
restart
:
always
ports
:
-
"
9184:9184"
volumes
:
-
./logs/exam-service:/logs/exam-service
networks
:
-
net
...
...
@@ -62,6 +71,8 @@ services:
restart
:
always
ports
:
-
"
9185:9185"
volumes
:
-
./logs/msc-service:/logs/msc-service
networks
:
-
net
...
...
@@ -75,6 +86,8 @@ services:
restart
:
always
ports
:
-
"
9186:9186"
volumes
:
-
./logs/monitor-service:/logs/monitor-service
networks
:
-
net
...
...
docs/deploy/docker-compose.env
View file @
21f4f0c1
...
...
@@ -83,4 +83,7 @@ LOGSTASH_HOST=localhost:5044
# 微信配置
WX_APP_ID=test
WX_APP_SECRET=test
WX_GRANT_TYPE=authorization_code
\ No newline at end of file
WX_GRANT_TYPE=authorization_code
# 附件上传配置
ATTACH_PATH=/attach
\ No newline at end of file
docs/deploy/mysql/init/microservice_auth.sql
View file @
21f4f0c1
SET
NAMES
utf8mb4
;
SET
FOREIGN_KEY_CHECKS
=
0
;
...
...
@@ -32,6 +33,6 @@ CREATE TABLE `oauth_client_details` (
-- ----------------------------
-- Records of oauth_client_details
-- ----------------------------
INSERT
INTO
`oauth_client_details`
VALUES
(
607150228717572096
,
'web_app'
,
NULL
,
'spring-microservice-exam-secret'
,
'$2a$10$IWLD8r7e0rKMW.ZhGsGCUO./MZUNDKudIswSToa9puXJwty.h59.u'
,
'read,write'
,
'password,authorization_code,refresh_token,implicit'
,
NULL
,
NULL
,
'
3600'
,
'86400'
,
NULL
,
NULL
,
'admin'
,
'2019-03-30 23:43:07'
,
'admin'
,
'2019-12-24 21:56:19
'
,
0
,
'EXAM'
,
'gitee'
);
INSERT
INTO
`oauth_client_details`
VALUES
(
607150228717572096
,
'web_app'
,
NULL
,
'spring-microservice-exam-secret'
,
'$2a$10$IWLD8r7e0rKMW.ZhGsGCUO./MZUNDKudIswSToa9puXJwty.h59.u'
,
'read,write'
,
'password,authorization_code,refresh_token,implicit'
,
NULL
,
NULL
,
'
86400'
,
'86400'
,
NULL
,
NULL
,
'admin'
,
'2019-03-30 23:43:07'
,
'admin'
,
'2020-03-28 20:01:31
'
,
0
,
'EXAM'
,
'gitee'
);
SET
FOREIGN_KEY_CHECKS
=
1
;
docs/deploy/mysql/init/microservice_exam.sql
View file @
21f4f0c1
This diff is collapsed.
Click to expand it.
docs/deploy/mysql/init/microservice_user.sql
View file @
21f4f0c1
This diff is collapsed.
Click to expand it.
docs/deploy/mysql/update.sql
View file @
21f4f0c1
ALTER
TABLE
`microservice-user`
.
`sys_user`
ADD
COLUMN
`signature`
varchar
(
255
)
NULL
COMMENT
'个性签名'
AFTER
`wechat`
;
\ No newline at end of file
ADD
COLUMN
`signature`
varchar
(
255
)
NULL
COMMENT
'个性签名'
AFTER
`wechat`
;
ALTER
TABLE
`microservice-user`
.
`sys_attachment`
ADD
COLUMN
`attach_type`
varchar
(
128
)
NULL
COMMENT
'附件类型'
AFTER
`attach_name`
,
ADD
COLUMN
`upload_type`
tinyint
(
4
)
NULL
COMMENT
'上传类型'
AFTER
`preview_url`
;
\ No newline at end of file
frontend/spring-microservice-exam-miniprogram/readme.md
deleted
100644 → 0
View file @
fd8c285d
frontend/spring-microservice-exam-ui/package.json
View file @
21f4f0c1
{
"name"
:
"spring-microservice-exam-ui"
,
"version"
:
"3.
5
.0"
,
"version"
:
"3.
7
.0"
,
"description"
:
"在线考试"
,
"author"
:
"tangyi <1633736729@qq.com>"
,
"private"
:
true
,
...
...
frontend/spring-microservice-exam-ui/src/api/admin/attachment.js
View file @
21f4f0c1
...
...
@@ -24,9 +24,9 @@ export function getObj (id) {
})
}
export
function
p
review
(
id
)
{
export
function
canP
review
(
id
)
{
return
request
({
url
:
baseAttachmentUrl
+
id
+
'/
p
review'
,
url
:
baseAttachmentUrl
+
id
+
'/
canP
review'
,
method
:
'get'
})
}
...
...
frontend/spring-microservice-exam-ui/src/components/Subjects/Choices/index.vue
View file @
21f4f0c1
<
template
>
<el-form
ref=
"dataSubjectForm"
:rules=
"subjectRules"
:model=
"subjectInfo"
:label-position=
"labelPosition"
label-width=
"100px"
>
<el-row>
<el-col
:span=
"
10
"
>
<el-col
:span=
"
20"
:offset=
"2
"
>
<div
class=
"subject-info"
>
<el-row>
<el-col
:span=
"12"
>
...
...
@@ -17,17 +17,17 @@
</el-row>
<el-row>
<el-col
:span=
"24"
>
<el-form-item
:label=
"$t('table.subjectName')"
prop=
"subjectName"
>
<el-input
v-model=
"subjectInfo.subjectName"
@
focus=
"updateTinymceContent(subjectInfo.subjectName, tinymceEdit.subjectName)"
/>
<el-form-item
:label=
"$t('table.subject.answer')"
prop=
"answer"
>
<el-radio-group
v-model=
"subjectInfo.answer.answer"
>
<el-radio
v-for=
"(option) in options"
:label=
"option.optionName"
:key=
"option.optionName"
>
{{
option
.
optionName
}}
</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col
:span=
"24"
>
<el-form-item
:label=
"$t('table.subject.answer')"
prop=
"answer"
>
<el-radio-group
v-model=
"subjectInfo.answer.answer"
>
<el-radio
v-for=
"(option) in options"
:label=
"option.optionName"
:key=
"option.optionName"
>
{{
option
.
optionName
}}
</el-radio>
</el-radio-group>
<el-form-item
:label=
"$t('table.subjectName')"
prop=
"subjectName"
>
<tinymce
ref=
"subjectNameEditor"
:height=
"60"
v-model=
"subjectInfo.subjectName"
/>
</el-form-item>
</el-col>
</el-row>
...
...
@@ -35,15 +35,20 @@
<el-col
:span=
"24"
>
<el-divider>
选项列表
</el-divider>
<el-form-item
v-for=
"(option, index) in options"
:label=
"option.optionName"
:key=
"option.optionName"
:prop=
"'options.' + index + '.optionContent'"
>
:prop=
"'options.' + index + '.optionContent'"
label-width=
"15px"
>
<el-row
:gutter=
"5"
>
<el-col
:span=
"
4
"
>
<el-col
:span=
"
2
"
>
<el-input
v-model=
"option.optionName"
/>
</el-col>
<el-col
:span=
"18"
>
<el-input
v-model=
"option.optionContent"
@
input=
"updateTinymceContent(option.optionContent, index, '1')"
>
<el-button
slot=
"append"
@
click
.
prevent=
"removeOption(option)"
>
删除
</el-button>
</el-input>
<el-col
:span=
"21"
>
<el-row
:gutter=
"5"
>
<el-col
:span=
"23"
>
<tinymce
:height=
"60"
v-model=
"option.optionContent"
/>
</el-col>
<el-col
:span=
"1"
>
<el-button
@
click
.
prevent=
"removeOption(option)"
>
删除
</el-button>
</el-col>
</el-row>
</el-col>
</el-row>
</el-form-item>
...
...
@@ -53,17 +58,12 @@
<el-row>
<el-col
:span=
"24"
>
<el-form-item
:label=
"$t('table.subject.analysis')"
prop=
"analysis"
class=
"analysis-form-item"
>
<
el-input
v-model=
"subjectInfo.analysis"
@
input=
"updateTinymceContent(subjectInfo.analysis, tinymceEdit.analysis)
"
/>
<
tinymce
ref=
"analysisEditor"
:height=
"60"
v-model=
"subjectInfo.analysis
"
/>
</el-form-item>
</el-col>
</el-row>
</div>
</el-col>
<el-col
:span=
"14"
>
<div
class=
"subject-tinymce"
>
<tinymce
ref=
"choicesEditor"
:height=
"350"
v-model=
"choicesContent"
@
hasClick=
"hasClick"
/>
</div>
</el-col>
</el-row>
</el-form>
</
template
>
...
...
@@ -124,36 +124,11 @@ export default {
score
:
[{
required
:
true
,
message
:
'请输入题目分值'
,
trigger
:
'change'
}],
answer
:
[{
required
:
true
,
message
:
'请输入答案'
,
trigger
:
'change'
}]
},
tinymce
:
{
type
:
1
,
// 类型 0:题目名称,1:选项
dialogTinymceVisible
:
false
,
tempValue
:
''
,
currentEdit
:
-
1
},
// 编辑对象
tinymceEdit
:
{
subjectName
:
-
1
,
answer
:
4
,
analysis
:
5
},
options
:
[],
optionCollapseActives
:
[
'1'
],
analysisCollapseActives
:
[
'2'
]
}
},
watch
:
{
// 监听富文本编辑器的输入
choicesContent
:
{
handler
:
function
(
choicesContent
)
{
if
(
isNotEmpty
(
this
.
$refs
.
choicesEditor
))
{
if
(
this
.
editType
===
1
&&
this
.
$refs
.
choicesEditor
.
getHasClick
())
{
this
.
saveTinymceContent
(
choicesContent
)
}
}
},
immediate
:
true
}
},
methods
:
{
initDefaultOptions
()
{
this
.
options
=
[
...
...
@@ -162,6 +137,7 @@ export default {
{
subjectChoicesId
:
''
,
optionName
:
'C'
,
optionContent
:
''
},
{
subjectChoicesId
:
''
,
optionName
:
'D'
,
optionContent
:
''
}
]
this
.
subjectInfo
.
answer
.
answer
=
'A'
},
setSubjectInfo
(
subject
)
{
this
.
subjectInfo
=
subject
...
...
@@ -181,36 +157,6 @@ export default {
getChoicesContent
()
{
return
this
.
choicesContent
},
// 绑定富文本的内容
updateTinymceContent
(
content
,
currentEdit
,
type
)
{
// 重置富文本
this
.
choicesContent
=
''
// 绑定当前编辑的对象
this
.
tinymce
.
currentEdit
=
currentEdit
this
.
tinymce
.
type
=
type
// 选择题
this
.
$refs
.
choicesEditor
.
setContent
(
content
||
''
)
this
.
editType
=
0
this
.
$refs
.
choicesEditor
.
setHashClick
(
false
)
},
// 保存题目时绑定富文本的内容到subjectInfo
saveTinymceContent
(
content
)
{
if
(
this
.
tinymce
.
type
!==
'1'
)
{
switch
(
this
.
tinymce
.
currentEdit
)
{
case
this
.
tinymceEdit
.
subjectName
:
this
.
subjectInfo
.
subjectName
=
content
break
case
this
.
tinymceEdit
.
answer
:
this
.
subjectInfo
.
answer
.
answer
=
content
break
case
this
.
tinymceEdit
.
analysis
:
this
.
subjectInfo
.
analysis
=
content
break
}
}
else
{
this
.
options
[
this
.
tinymce
.
currentEdit
].
optionContent
=
content
}
},
// 表单校验
validate
()
{
let
valid
=
false
...
...
@@ -251,6 +197,8 @@ export default {
this
.
subjectInfo
.
score
=
score
}
this
.
initDefaultOptions
()
this
.
$refs
[
'subjectNameEditor'
].
setContent
(
''
)
this
.
$refs
[
'analysisEditor'
].
setContent
(
''
)
},
addOption
()
{
// 校验
...
...
@@ -270,10 +218,6 @@ export default {
if
(
index
!==
-
1
)
{
this
.
options
.
splice
(
index
,
1
)
}
},
// 点击事件回调
hasClick
(
hasClick
)
{
this
.
editType
=
1
}
}
}
...
...
frontend/spring-microservice-exam-ui/src/components/Subjects/Judgement/index.vue
View file @
21f4f0c1
<
template
>
<el-form
ref=
"dataSubjectForm"
:rules=
"subjectRules"
:model=
"subjectInfo"
:label-position=
"labelPosition"
label-width=
"100px"
>
<el-row>
<el-col
:span=
"
10
"
>
<el-col
:span=
"
20"
:offset=
"2
"
>
<div
class=
"subject-info"
>
<el-row>
<el-col
:span=
"12"
>
...
...
@@ -17,34 +17,29 @@
</el-row>
<el-row>
<el-col
:span=
"24"
>
<el-form-item
:label=
"$t('table.subjectName')"
prop=
"subjectName"
>
<el-input
v-model=
"subjectInfo.subjectName"
@
focus=
"updateTinymceContent(subjectInfo.subjectName, tinymceEdit.subjectName)"
/>
<el-form-item
:label=
"$t('table.subject.answer')"
prop=
"answer"
>
<el-radio-group
v-model=
"subjectInfo.answer.answer"
>
<el-radio
v-for=
"(option) in options"
:label=
"option.optionName"
:key=
"option.optionName"
>
{{
option
.
optionName
}}
</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col
:span=
"24"
>
<el-form-item
:label=
"$t('table.subject.answer')"
prop=
"answer"
>
<el-radio-group
v-model=
"subjectInfo.answer.answer"
>
<el-radio
v-for=
"(option) in options"
:label=
"option.optionName"
:key=
"option.optionName"
>
{{
option
.
optionName
}}
</el-radio>
</el-radio-group>
<el-form-item
:label=
"$t('table.subjectName')"
prop=
"subjectName"
>
<tinymce
ref=
"subjectNameEditor"
:height=
"60"
v-model=
"subjectInfo.subjectName"
/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col
:span=
"24"
>
<el-form-item
:label=
"$t('table.subject.analysis')"
prop=
"analysis"
class=
"analysis-form-item"
>
<
el-input
v-model=
"subjectInfo.analysis"
@
input=
"updateTinymceContent(subjectInfo.analysis, tinymceEdit.analysis)
"
/>
<
tinymce
ref=
"analysisEditor"
:height=
"60"
v-model=
"subjectInfo.analysis
"
/>
</el-form-item>
</el-col>
</el-row>
</div>
</el-col>
<el-col
:span=
"14"
>
<div
class=
"subject-tinymce"
>
<tinymce
ref=
"choicesEditor"
:height=
"350"
v-model=
"choicesContent"
@
hasClick=
"hasClick"
/>
</div>
</el-col>
</el-row>
</el-form>
</
template
>
...
...
@@ -102,40 +97,16 @@ export default {
score
:
[{
required
:
true
,
message
:
'请输入题目分值'
,
trigger
:
'change'
}],
answer
:
[{
required
:
true
,
message
:
'请输入答案'
,
trigger
:
'change'
}]
},
tinymce
:
{
type
:
1
,
// 类型 0:题目名称,1:选项
dialogTinymceVisible
:
false
,
tempValue
:
''
,
currentEdit
:
-
1
},
// 编辑对象
tinymceEdit
:
{
subjectName
:
-
1
,
answer
:
4
,
analysis
:
5
},
options
:
[]
}
},
watch
:
{
// 监听富文本编辑器的输入
choicesContent
:
{
handler
:
function
(
choicesContent
)
{
if
(
isNotEmpty
(
this
.
$refs
.
choicesEditor
))
{
if
(
this
.
editType
===
1
&&
this
.
$refs
.
choicesEditor
.
getHasClick
())
{
this
.
saveTinymceContent
(
choicesContent
)
}
}
},
immediate
:
true
}
},
methods
:
{
initDefaultOptions
()
{
this
.
options
=
[
{
subjectChoicesId
:
''
,
optionName
:
'正确'
,
optionContent
:
'正确'
},
{
subjectChoicesId
:
''
,
optionName
:
'错误'
,
optionContent
:
'错误'
}
]
this
.
subjectInfo
.
answer
.
answer
=
'正确'
},
setSubjectInfo
(
subject
)
{
this
.
subjectInfo
=
subject
...
...
@@ -144,30 +115,6 @@ export default {
getSubjectInfo
()
{
return
this
.
subjectInfo
},
// 绑定富文本的内容
updateTinymceContent
(
content
,
currentEdit
,
type
)
{
// 重置富文本
this
.
choicesContent
=
''
// 绑定当前编辑的对象
this
.
tinymce
.
currentEdit
=
currentEdit
this
.
tinymce
.
type
=
type
this
.
$refs
.
choicesEditor
.
setContent
(
content
||
''
)
this
.
editType
=
0
this
.
$refs
.
choicesEditor
.
setHashClick
(
false
)
},
saveTinymceContent
(
content
)
{
switch
(
this
.
tinymce
.
currentEdit
)
{
case
this
.
tinymceEdit
.
subjectName
:
this
.
subjectInfo
.
subjectName
=
content
break
case
this
.
tinymceEdit
.
answer
:
this
.
subjectInfo
.
answer
.
answer
=
content
break
case
this
.
tinymceEdit
.
analysis
:
this
.
subjectInfo
.
analysis
=
content
break
}
},
// 表单校验
validate
()
{
let
valid
=
false
...
...
@@ -202,10 +149,8 @@ export default {
this
.
subjectInfo
.
score
=
score
}
this
.
initDefaultOptions
()
},
// 点击事件回调
hasClick
(
hasClick
)
{
this
.
editType
=
1
this
.
$refs
[
'subjectNameEditor'
].
setContent
(
''
)
this
.
$refs
[
'analysisEditor'
].
setContent
(
''
)
}
}
}
...
...
frontend/spring-microservice-exam-ui/src/components/Subjects/MultipleChoices/index.vue
View file @
21f4f0c1
<
template
>
<el-form
ref=
"dataSubjectForm"
:rules=
"subjectRules"
:model=
"subjectInfo"
:label-position=
"labelPosition"
label-width=
"100px"
>
<el-row>
<el-col
:span=
"
10
"
>
<el-col
:span=
"
20"
:offset=
"2
"
>
<div
class=
"subject-info"
v-if=
"subjectInfo.type === 3"
>
<el-row>
<el-col
:span=
"12"
>
...
...
@@ -18,64 +17,53 @@
</el-row>
<el-row>
<el-col
:span=
"24"
>
<el-form-item
:label=
"$t('table.subjectName')"
prop=
"subjectName"
>
<el-input
v-model=
"subjectInfo.subjectName"
/>
<el-form-item
:label=
"$t('table.subject.answer')"
prop=
"answer"
>
<el-checkbox-group
v-model=
"multipleAnswers"
>
<el-checkbox
v-for=
"(option) in options"
:label=
"option.optionName"
:key=
"option.optionName"
:name=
"option.optionName"
>
{{
option
.
optionName
}}
</el-checkbox>
</el-checkbox-group>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col
:span=
"24"
>
<el-form-item
:label=
"$t('table.subject.answer')"
prop=
"answer"
>
<el-checkbox-group
v-model=
"multipleAnswers"
>
<el-checkbox
v-for=
"(option) in options"
:label=
"option.optionName"
:key=
"option.optionName"
:name=
"option.optionName"
>
{{
option
.
optionName
}}
</el-checkbox>
</el-checkbox-group>
<el-form-item
:label=
"$t('table.subjectName')"
prop=
"subjectName"
>
<tinymce
ref=
"subjectNameEditor"
:height=
"60"
v-model=
"subjectInfo.subjectName"
/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col
:span=
"24"
>
<el-collapse
v-model=
"optionCollapseActives"
>
<el-collapse-item
title=
"选项列表"
name=
"1"
>
<el-row
class=
"collapse-top"
>
<el-col
:span=
"24"
>
<el-form-item
v-for=
"(option, index) in options"
:label=
"option.optionName"
:key=
"option.optionName"
:prop=
"'options.' + index + '.optionContent'"
>
<el-row
:gutter=
"5"
>
<el-col
:span=
"4"
>
<el-input
v-model=
"option.optionName"
/>
</el-col>
<el-col
:span=
"18"
>
<el-input
v-model=
"option.optionContent"
@
input=
"updateTinymceContent(option.optionContent, index, '1')"
>
<el-button
slot=
"append"
@
click
.
prevent=
"removeOption(option)"
>
删除
</el-button>
</el-input>
</el-col>
</el-row>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col
:span=
"24"
>
<el-button
type=
"success"
@
click
.
prevent=
"addOption()"
style=
"display:block;margin:0 auto"
>
新增选项
</el-button>
</el-col>
</el-row>
</el-collapse-item>
</el-collapse>
<el-divider>
选项列表
</el-divider>
<el-form-item
v-for=
"(option, index) in options"
:label=
"option.optionName"
:key=
"option.optionName"
:prop=
"'options.' + index + '.optionContent'"
label-width=
"15px"
>
<el-row
:gutter=
"5"
>
<el-col
:span=
"2"
>
<el-input
v-model=
"option.optionName"
/>
</el-col>
<el-col
:span=
"21"
>
<el-row
:gutter=
"5"
>
<el-col
:span=
"23"
>
<tinymce
:height=
"60"
v-model=
"option.optionContent"
/>
</el-col>
<el-col
:span=
"1"
>
<el-button
@
click
.
prevent=
"removeOption(option)"
>
删除
</el-button>
</el-col>
</el-row>
</el-col>
</el-row>
</el-form-item>
<el-button
type=
"success"
@
click
.
prevent=
"addOption()"
style=
"display:block;margin:0 auto"
>
新增选项
</el-button>
</el-col>
</el-row>
<el-row>
<el-col
:span=
"24"
>
<el-form-item
:label=
"$t('table.subject.analysis')"
prop=
"analysis"
class=
"analysis-form-item"
>
<
el-input
v-model=
"subjectInfo.analysis"
@
input=
"updateTinymceContent(subjectInfo.analysis, tinymceEdit.analysis)
"
/>
<
tinymce
ref=
"analysisEditor"
:height=
"60"
v-model=
"subjectInfo.analysis
"
/>
</el-form-item>
</el-col>
</el-row>
</div>
</el-col>
<el-col
:span=
"14"
>
<div
class=
"subject-tinymce"
>
<tinymce
ref=
"choicesEditor"
:height=
"350"
v-model=
"choicesContent"
@
hasClick=
"hasClick"
/>
</div>
</el-col>
</el-row>
</el-form>
</
template
>
...
...
@@ -127,37 +115,12 @@ export default {
score
:
[{
required
:
true
,
message
:
'请输入题目分值'
,
trigger
:
'change'
}],
answer
:
[{
required
:
true
,
message
:
'请输入答案'
,
trigger
:
'change'
}]
},
tinymce
:
{
type
:
1
,
// 类型 0:题目名称,1:选项
dialogTinymceVisible
:
false
,
tempValue
:
''
,
currentEdit
:
-
1
},
// 编辑对象
tinymceEdit
:
{
subjectName
:
-
1
,
answer
:
4
,
analysis
:
5
},
options
:
[],
optionCollapseActives
:
[
'1'
],
analysisCollapseActives
:
[
'2'
],
multipleAnswers
:
[]
}
},
watch
:
{
// 监听富文本编辑器的输入
choicesContent
:
{
handler
:
function
(
choicesContent
)
{
if
(
isNotEmpty
(
this
.
$refs
.
choicesEditor
))
{
if
(
this
.
editType
===
1
&&
this
.
$refs
.
choicesEditor
.
getHasClick
())
{
this
.
saveTinymceContent
(
choicesContent
)
}
}
},
immediate
:
true
}
},
methods
:
{
initDefaultOptions
()
{
this
.
options
=
[
...
...
@@ -166,6 +129,7 @@ export default {
{
subjectChoicesId
:
''
,
optionName
:
'C'
,
optionContent
:
''
},
{
subjectChoicesId
:
''
,
optionName
:
'D'
,
optionContent
:
''
}
]
this
.
subjectInfo
.
answer
.
answer
=
'A'
},
setSubjectInfo
(
subject
)
{
this
.
subjectInfo
=
subject
...
...
@@ -187,36 +151,6 @@ export default {
getChoicesContent
()
{
return
this
.
choicesContent
},
// 绑定富文本的内容
updateTinymceContent
(
content
,
currentEdit
,
type
)
{
// 重置富文本
this
.
choicesContent
=
''
// 绑定当前编辑的对象
this
.
tinymce
.
currentEdit
=
currentEdit
this
.
tinymce
.
type
=
type
// 选择题
this
.
$refs
.
choicesEditor
.
setContent
(
content
||
''
)
this
.
editType
=
0
this
.
$refs
.
choicesEditor
.
setHashClick
(
false
)
},
// 保存题目时绑定富文本的内容到subjectInfo
saveTinymceContent
(
content
)
{
if
(
this
.
tinymce
.
type
!==
'1'
)
{
switch
(
this
.
tinymce
.
currentEdit
)
{
case
this
.
tinymceEdit
.
subjectName
:
this
.
subjectInfo
.
subjectName
=
content
break
case
this
.
tinymceEdit
.
answer
:
this
.
subjectInfo
.
answer
.
answer
=
content
break
case
this
.
tinymceEdit
.
analysis
:
this
.
subjectInfo
.
analysis
=
content
break
}
}
else
{
this
.
options
[
this
.
tinymce
.
currentEdit
].
optionContent
=
content
}
},
// 表单校验
validate
()
{
let
valid
=
false
...
...
@@ -257,6 +191,8 @@ export default {
this
.
subjectInfo
.
score
=
score
}
this
.
initDefaultOptions
()
this
.
$refs
[
'subjectNameEditor'
].
setContent
(
''
)
this
.
$refs
[
'analysisEditor'
].
setContent
(
''
)
},
addOption
()
{
// 校验
...
...
@@ -277,10 +213,6 @@ export default {
this
.
options
.
splice
(
index
,
1
)
}
},
// 点击事件回调
hasClick
(
hasClick
)
{
this
.
editType
=
1
},
initMultipleAnswers
()
{
if
(
isNotEmpty
(
this
.
subjectInfo
.
answer
))
{
this
.
multipleAnswers
=
this
.
subjectInfo
.
answer
.
answer
.
split
(
','
)
...
...
frontend/spring-microservice-exam-ui/src/components/Subjects/ShortAnswer/index.vue
View file @
21f4f0c1
<
template
>
<el-form
ref=
"dataSubjectForm"
:rules=
"subjectRules"
:model=
"subjectInfo"
:label-position=
"labelPosition"
label-width=
"100px"
>
<el-row>
<el-col
:span=
"
10
"
>
<el-col
:span=
"
20"
:offset=
"2
"
>
<el-row>
<el-col
:span=
"12"
>
<el-form-item
:label=
"$t('table.subject.score')"
prop=
"score"
>
...
...
@@ -17,30 +17,25 @@
<el-row>
<el-col
:span=
"24"
>
<el-form-item
:label=
"$t('table.subjectName')"
prop=
"subjectName"
>
<
el-input
v-model=
"subjectInfo.subjectName"
@
focus=
"updateTinymceContent(subjectInfo.subjectName, tinymceEdit.subjectName)
"
/>
<
tinymce
ref=
"subjectNameEditor"
:height=
"60"
v-model=
"subjectInfo.subjectName
"
/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col
:span=
"24"
>
<el-form-item
:label=
"$t('table.subject.answer')"
prop=
"answer"
>
<
el-input
v-model=
"subjectInfo.answer.answer"
@
focus=
"updateTinymceContent(subjectInfo.answer.answer, tinymceEdit.answer)
"
/>
<
tinymce
ref=
"answerEditor"
:height=
"60"
v-model=
"subjectInfo.answer.answer
"
/>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col
:span=
"24"
>
<el-form-item
:label=
"$t('table.subject.analysis')"
prop=
"analysis"
>
<
el-input
v-model=
"subjectInfo.analysis"
@
focus=
"updateTinymceContent(subjectInfo.analysis, tinymceEdit.analysis)
"
/>
<
tinymce
ref=
"analysisEditor"
:height=
"60"
v-model=
"subjectInfo.analysis
"
/>
</el-form-item>
</el-col>
</el-row>
</el-col>
<el-col
:span=
"14"
>
<div
class=
"subject-tinymce"
>
<tinymce
ref=
"shortAnswerEditor"
:height=
"350"
v-model=
"shortAnswerEditorContent"
/>
</div>
</el-col>
</el-row>
</el-form>
</
template
>
...
...
@@ -85,37 +80,15 @@ export default {
data
()
{
return
{
subjectInfo
:
this
.
subject
,
shortAnswerEditorContent
:
this
.
content
,
labelPosition
:
'right'
,
// 表单校验规则
subjectRules
:
{
subjectName
:
[{
required
:
true
,
message
:
'请输入题目名称'
,
trigger
:
'change'
}],
score
:
[{
required
:
true
,
message
:
'请输入题目分值'
,
trigger
:
'change'
}],
answer
:
[{
required
:
true
,
message
:
'请输入答案'
,
trigger
:
'change'
}]
},
tinymce
:
{
type
:
1
,
// 类型 0:题目名称,1:选项A,2:选择B,3:选项C,4:选项D
dialogTinymceVisible
:
false
,
tempValue
:
''
,
currentEdit
:
-
1
},
// 编辑对象
tinymceEdit
:
{
subjectName
:
-
1
,
answer
:
4
,
analysis
:
5
}
}
},
watch
:
{
// 监听富文本编辑器的输入
shortAnswerEditorContent
:
{
handler
:
function
(
shortAnswerEditorContent
)
{
this
.
saveTinymceContent
(
shortAnswerEditorContent
)
},
immediate
:
true
}
},
methods
:
{
setSubjectInfo
(
subject
)
{
this
.
subjectInfo
=
subject
...
...
@@ -123,27 +96,6 @@ export default {
getSubjectInfo
()
{
return
this
.
subjectInfo
},
// 绑定富文本的内容
updateTinymceContent
(
content
,
currentEdit
)
{
// 绑定当前编辑的对象
this
.
tinymce
.
currentEdit
=
currentEdit
// 选择题
this
.
$refs
.
shortAnswerEditor
.
setContent
(
content
||
''
)
},
// 保存题目时绑定富文本的内容到subjectInfo
saveTinymceContent
(
content
)
{
switch
(
this
.
tinymce
.
currentEdit
)
{
case
this
.
tinymceEdit
.
subjectName
:
this
.
subjectInfo
.
subjectName
=
content
break
case
this
.
tinymceEdit
.
answer
:
this
.
subjectInfo
.
answer
.
answer
=
content
break
case
this
.
tinymceEdit
.
analysis
:
this
.
subjectInfo
.
analysis
=
content
break
}
},
// 表单校验
validate
()
{
let
valid
=
false
...
...
@@ -177,6 +129,9 @@ export default {
if
(
isNotEmpty
(
score
))
{
this
.
subjectInfo
.
score
=
score
}
this
.
$refs
[
'subjectNameEditor'
].
setContent
(
''
)
this
.
$refs
[
'answerEditor'
].
setContent
(
''
)
this
.
$refs
[
'analysisEditor'
].
setContent
(
''
)
},
initDefaultOptions
()
{
...
...
frontend/spring-microservice-exam-ui/src/components/Tinymce/index.vue
View file @
21f4f0c1
...
...
@@ -32,7 +32,7 @@ export default {
},
menubar
:
{
type
:
String
,
default
:
'
file edit insert view format table
'
default
:
''
},
height
:
{
type
:
Number
,
...
...
@@ -42,8 +42,6 @@ export default {
},
data
()
{
return
{
hasChange
:
false
,
hasClick
:
false
,
hasInit
:
false
,
tinymceId
:
this
.
id
,
fullscreen
:
false
,
...
...
@@ -94,6 +92,7 @@ export default {
toolbar
:
this
.
toolbar
.
length
>
0
?
this
.
toolbar
:
toolbar
,
menubar
:
this
.
menubar
,
plugins
:
plugins
,
external_plugins
:
{
tiny_mce_wiris
:
'https://www.wiris.net/demo/plugins/tiny_mce/plugin.js'
},
end_container_on_empty_block
:
true
,
powerpaste_word_import
:
'clean'
,
code_dialog_height
:
450
,
...
...
@@ -103,6 +102,7 @@ export default {
imagetools_cors_hosts
:
[
'www.tinymce.com'
,
'codepen.io'
],
default_link_target
:
'_blank'
,
link_title
:
false
,
statusbar
:
false
,
nonbreaking_force_tab
:
true
,
// inserting nonbreaking space need Nonbreaking Space Plugin
init_instance_callback
:
editor
=>
{
if
(
_this
.
value
)
{
...
...
@@ -113,11 +113,6 @@ export default {
this
.
hasChange
=
true
this
.
$emit
(
'input'
,
editor
.
getContent
())
})
editor
.
on
(
'Click'
,
()
=>
{
this
.
hasClick
=
true
this
.
$emit
(
'hasClick'
,
this
.
hasClick
)
})
},
setup
(
editor
)
{
editor
.
on
(
'FullscreenStateChanged'
,
(
e
)
=>
{
...
...
@@ -136,12 +131,6 @@ export default {
},
getContent
()
{
return
window
.
tinymce
.
get
(
this
.
tinymceId
).
getContent
()
},
getHasClick
()
{
return
this
.
hasClick
},
setHashClick
(
click
)
{
this
.
hasClick
=
click
}
}
}
...
...
frontend/spring-microservice-exam-ui/src/components/Tinymce/toolbar.js
View file @
21f4f0c1
// Here is a list of the toolbar
// Detail list see https://www.tinymce.com/docs/advanced/editor-control-identifiers/#toolbarcontrols
const
toolbar
=
[
'
bold italic underline strikethrough alignleft aligncenter alignright outdent indent blockquote undo redo removeformat subscript superscript code codesample'
,
'hr bullist numlist link image charmap preview anchor pagebreak insertdatetime media table emoticons forecolor backcolor
fullscreen'
]
const
toolbar
=
[
'
tiny_mce_wiris_formulaEditor tiny_mce_wiris_formulaEditorChemistry bold italic underline strikethrough alignleft aligncenter alignright outdent indent blockquote superscript codesample bullist numlist link image charmap media table forecolor backcolor preview
fullscreen'
]
export
default
toolbar
frontend/spring-microservice-exam-ui/src/lang/zh.js
View file @
21f4f0c1
...
...
@@ -169,6 +169,7 @@ export default {
attachSize
:
'附件大小'
,
upload
:
'上传'
,
download
:
'下载'
,
downloadUrl
:
'复制下载链接'
,
uploader
:
'上传者'
,
uploadDate
:
'上传时间'
,
courseName
:
'课程名称'
,
...
...
frontend/spring-microservice-exam-ui/src/views/attachment/list.vue
View file @
21f4f0c1
...
...
@@ -15,10 +15,10 @@
:data=
"params"
class=
"upload-demo"
multiple
>
<el-button
v-waves
type=
"primary"
class=
"filter-item"
>
上传
<i
class=
"el-icon-upload el-icon--right"
style=
"margin-left: 10px;"
/></el-button>
<el-progress
v-if=
"uploading === true"
:percentage=
"percentage"
:text-inside=
"true"
:stroke-width=
"18"
status=
"success"
/>
<el-button
v-waves
type=
"success"
class=
"filter-item"
>
上传
<i
class=
"el-icon-upload el-icon--right"
style=
"margin-left: 10px;"
/></el-button>
</el-upload>
</div>
<el-progress
v-if=
"uploading === true"
:percentage=
"percentage"
:text-inside=
"true"
:stroke-width=
"18"
status=
"success"
/>
<spinner-loading
v-if=
"listLoading"
/>
<el-table
...
...
@@ -28,7 +28,7 @@
style=
"width: 100%;"
@
sort-change=
"sortChange"
>
<el-table-column
type=
"selection"
width=
"55"
/>
<el-table-column
prop=
"
id"
label=
"流水号
"
min-width=
"100"
>
<el-table-column
prop=
"
流水号"
label=
"id
"
min-width=
"100"
>
<template
slot-scope=
"scope"
>
<span>
{{
scope
.
row
.
id
}}
</span>
</
template
>
...
...
@@ -43,6 +43,11 @@
<span>
{{
scope
.
row
.
busiType
|
attachmentTypeFilter
}}
</span>
</
template
>
</el-table-column>
<el-table-column
label=
"附件大小"
min-width=
"90"
>
<
template
slot-scope=
"scope"
>
<span>
{{
scope
.
row
.
attachSize
|
attachmentSizeFilter
}}
</span>
</
template
>
</el-table-column>
<el-table-column
:label=
"$t('table.uploader')"
min-width=
"50"
>
<
template
slot-scope=
"scope"
>
<span>
{{
scope
.
row
.
creator
}}
</span>
...
...
@@ -50,14 +55,38 @@
</el-table-column>
<el-table-column
:label=
"$t('table.uploadDate')"
min-width=
"70"
>
<
template
slot-scope=
"scope"
>
<span>
{{
scope
.
row
.
createDate
|
timeFilter
}}
</span>
<span>
{{
scope
.
row
.
createDate
|
fmtDate
(
'yyyy-MM-dd hh:mm'
)
}}
</span>
</
template
>
</el-table-column>
<el-table-column
:label=
"$t('table.actions')"
class-name=
"status-col"
width=
"
300px
"
>
<el-table-column
:label=
"$t('table.actions')"
class-name=
"status-col"
width=
"
100
"
>
<
template
slot-scope=
"scope"
>
<el-button
type=
"text"
@
click=
"handleDownload(scope.row)"
>
{{
$t
(
'table.download'
)
}}
</el-button>
<el-button
type=
"text"
@
click=
"handleDelete(scope.row)"
>
{{
$t
(
'table.delete'
)
}}
</el-button>
<el-dropdown>
<span
class=
"el-dropdown-link"
>
操作
<i
class=
"el-icon-caret-bottom el-icon--right"
></i>
</span>
<el-dropdown-menu
slot=
"dropdown"
>
<el-dropdown-item>
<a
@
click=
"handlePreview(scope.row)"
>
<span><i
class=
"el-icon-view"
></i>
{{
$t
(
'table.preview'
)
}}
</span>
</a>
</el-dropdown-item>
<el-dropdown-item>
<a
@
click=
"handleDownloadUrl(scope.row)"
>
<span><i
class=
"el-icon-document-copy"
></i>
{{
$t
(
'table.downloadUrl'
)
}}
</span>
</a>
</el-dropdown-item>
<el-dropdown-item>
<a
@
click=
"handleDownload(scope.row)"
>
<span><i
class=
"el-icon-download"
></i>
{{
$t
(
'table.download'
)
}}
</span>
</a>
</el-dropdown-item>
<el-dropdown-item>
<a
@
click=
"handleDelete(scope.row)"
>
<span><i
class=
"el-icon-delete"
></i>
{{
$t
(
'table.delete'
)
}}
</span>
</a>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</
template
>
</el-table-column>
</el-table>
...
...
@@ -65,14 +94,21 @@
<div
class=
"pagination-container"
>
<el-pagination
v-show=
"total>0"
:current-page=
"listQuery.pageNum"
:page-sizes=
"[10,20,30, 50]"
:page-size=
"listQuery.pageSize"
:total=
"total"
background
layout=
"total, sizes, prev, pager, next, jumper"
@
size-change=
"handleSizeChange"
@
current-change=
"handleCurrentChange"
/>
</div>
<!-- 预览 -->
<el-dialog
:visible
.
sync=
"dialogPreviewVisible"
title=
"预览"
width=
"50%"
top=
"12vh"
>
<div
class=
"preview"
>
<img
:src=
"previewUrl"
alt=
"二维码"
>
</div>
</el-dialog>
</div>
</template>
<
script
>
import
{
fetchList
,
addObj
,
putObj
,
delAttachment
,
getDownloadUrl
}
from
'@/api/admin/attachment'
import
{
fetchList
,
addObj
,
putObj
,
delAttachment
,
canPreview
}
from
'@/api/admin/attachment'
import
waves
from
'@/directive/waves'
import
{
getToken
}
from
'@/utils/auth'
// getToken from cookie
import
{
notifySuccess
,
messageSuccess
,
isNotEmpty
,
formatDate
}
from
'@/utils/util'
import
{
getToken
}
from
'@/utils/auth'
import
{
messageSuccess
,
messageWarn
}
from
'@/utils/util'
import
SpinnerLoading
from
'@/components/SpinnerLoading'
export
default
{
...
...
@@ -105,8 +141,16 @@ export default {
}
return
attachType
},
timeFilter
(
time
)
{
return
formatDate
(
new
Date
(
time
),
'yyyy-MM-dd hh:mm'
)
attachmentSizeFilter
(
attachSize
)
{
let
fileSizeByte
=
attachSize
let
fileSizeMsg
=
''
if
(
fileSizeByte
<
1048576
)
fileSizeMsg
=
(
fileSizeByte
/
1024
).
toFixed
(
2
)
+
'KB'
else
if
(
fileSizeByte
===
1048576
)
fileSizeMsg
=
'1MB'
else
if
(
fileSizeByte
>
1048576
&&
fileSizeByte
<
1073741824
)
fileSizeMsg
=
(
fileSizeByte
/
(
1024
*
1024
)).
toFixed
(
2
)
+
'MB'
else
if
(
fileSizeByte
>
1048576
&&
fileSizeByte
===
1073741824
)
fileSizeMsg
=
'1GB'
else
if
(
fileSizeByte
>
1073741824
&&
fileSizeByte
<
1099511627776
)
fileSizeMsg
=
(
fileSizeByte
/
(
1024
*
1024
*
1024
)).
toFixed
(
2
)
+
'GB'
else
fileSizeMsg
=
'文件超过1TB'
return
fileSizeMsg
}
},
data
()
{
...
...
@@ -151,7 +195,9 @@ export default {
}
],
uploading
:
false
,
percentage
:
0
percentage
:
0
,
dialogPreviewVisible
:
false
,
previewUrl
:
''
}
},
created
()
{
...
...
@@ -204,25 +250,38 @@ export default {
addObj
(
this
.
temp
).
then
(()
=>
{
this
.
list
.
unshift
(
this
.
temp
)
this
.
getList
()
notify
Success
(
this
,
'创建成功'
)
message
Success
(
this
,
'创建成功'
)
})
}
})
},
handleDownload
(
row
)
{
getDownloadUrl
(
row
.
id
).
then
(
response
=>
{
if
(
isNotEmpty
(
response
.
data
))
{
window
.
open
(
'http://'
+
response
.
data
.
data
,
'_blank'
)
window
.
location
.
href
=
'/api/user/v1/attachment/download?id='
+
row
.
id
},
handlePreview
(
row
)
{
this
.
previewUrl
=
''
canPreview
(
row
.
id
).
then
(
response
=>
{
if
(
response
.
data
.
data
)
{
this
.
previewUrl
=
'/api/user/v1/attachment/preview?id='
+
row
.
id
this
.
dialogPreviewVisible
=
true
}
else
{
messageWarn
(
this
,
'暂不支持预览该格式的附件'
)
}
}).
catch
(
error
=>
{
console
.
error
(
error
)
})
},
handleDownloadUrl
(
row
)
{
const
url
=
'http://'
+
window
.
location
.
host
+
'/api/user/v1/attachment/download?id='
+
row
.
id
this
.
$alert
(
url
,
'下载链接'
,
{
confirmButtonText
:
'确定'
})
},
updateData
()
{
this
.
$refs
[
'dataForm'
].
validate
((
valid
)
=>
{
if
(
valid
)
{
const
tempData
=
Object
.
assign
({},
this
.
temp
)
putObj
(
tempData
).
then
(()
=>
{
this
.
getList
()
notify
Success
(
this
,
'更新成功'
)
message
Success
(
this
,
'更新成功'
)
})
}
})
...
...
@@ -236,14 +295,14 @@ export default {
}).
then
(()
=>
{
delAttachment
(
row
.
id
).
then
(()
=>
{
this
.
getList
()
notify
Success
(
this
,
'删除成功'
)
message
Success
(
this
,
'删除成功'
)
})
}).
catch
(()
=>
{})
},
handleUploadSuccess
()
{
this
.
uploading
=
false
this
.
getList
()
notify
Success
(
this
,
'上传成功'
)
message
Success
(
this
,
'上传成功'
)
},
handleUploadProgress
(
event
,
file
,
fileList
)
{
this
.
uploading
=
true
...
...
@@ -252,3 +311,13 @@ export default {
}
}
</
script
>
<
style
lang=
"scss"
scoped
>
.upload-demo
{
display
:
inline-block
;
}
.preview
{
text-align
:
center
;
overflow
:
hidden
;
}
</
style
>
frontend/spring-microservice-exam-ui/src/views/exam/exam.vue
View file @
21f4f0c1
...
...
@@ -234,7 +234,7 @@ import waves from '@/directive/waves'
import
{
mapGetters
,
mapState
}
from
'vuex'
import
{
getToken
}
from
'@/utils/auth'
import
{
checkMultipleSelect
,
isNotEmpty
,
notifySuccess
,
notifyFail
,
messageSuccess
}
from
'@/utils/util'
import
{
delAttachment
,
preview
}
from
'@/api/admin/attachment'
import
{
delAttachment
}
from
'@/api/admin/attachment'
import
Tinymce
from
'@/components/Tinymce'
import
SpinnerLoading
from
'@/components/SpinnerLoading'
import
Choices
from
'@/components/Subjects/Choices'
...
...
@@ -465,6 +465,7 @@ export default {
},
handleUpdate
(
row
)
{
this
.
temp
=
Object
.
assign
({},
row
)
this
.
avatar
=
''
if
(
!
isNotEmpty
(
this
.
temp
.
course
))
{
this
.
temp
.
course
=
{
id
:
''
,
...
...
@@ -473,9 +474,7 @@ export default {
}
// 获取图片的预览地址
if
(
isNotEmpty
(
this
.
temp
.
avatarId
))
{
preview
(
this
.
temp
.
avatarId
).
then
(
response
=>
{
this
.
avatar
=
response
.
data
.
data
})
this
.
avatar
=
'/api/user/v1/attachment/preview?id='
+
this
.
temp
.
avatarId
}
this
.
dialogStatus
=
'update'
this
.
dialogFormVisible
=
true
...
...
frontend/spring-microservice-exam-ui/src/views/exam/examSubjects.vue
View file @
21f4f0c1
...
...
@@ -394,7 +394,6 @@ export default {
</
script
>
<
style
lang=
"scss"
rel=
"stylesheet/scss"
scoped
>
/* 题目 */
.subject-title
{
font-size
:
18px
;
line-height
:
22px
;
...
...
frontend/spring-microservice-exam-ui/src/views/exam/subject.vue
View file @
21f4f0c1
...
...
@@ -86,6 +86,11 @@
<span><i
class=
"el-icon-edit"
></i>
{{
$t
(
'table.edit'
)
}}
</span>
</a>
</el-dropdown-item>
<el-dropdown-item>
<a
@
click=
"handleViewSubject(scope.row)"
>
<span><i
class=
"el-icon-view"
></i>
{{
$t
(
'table.preview'
)
}}
</span>
</a>
</el-dropdown-item>
<el-dropdown-item
v-if=
"subject_bank_btn_del"
>
<a
@
click=
"handleDeleteSubject(scope.row)"
>
<span><i
class=
"el-icon-delete"
></i>
{{
$t
(
'table.delete'
)
}}
</span>
...
...
@@ -183,12 +188,39 @@
</el-col>
</el-row>
</el-dialog>
<!-- 预览题目 -->
<el-dialog
title=
"预览题目"
:visible
.
sync=
"dialogViewVisible"
width=
"60%"
top=
"10vh"
>
<div
class=
"subject-title"
>
<span
class=
"subject-title-content"
v-html=
"tempSubject.subjectName"
/>
<span
class=
"subject-title-content"
>
({{tempSubject.score}})分
</span>
</div>
<ul
v-if=
"tempSubject.type === 0 || tempSubject.type === 3"
class=
"subject-options"
>
<li
class=
"subject-option"
v-for=
"(option) in tempSubject.options"
:key=
"option.id"
>
<input
class=
"toggle"
type=
"checkbox"
>
<label><span
class=
"subject-option-prefix"
>
{{option.optionName}}
</span><span
v-html=
"option.optionContent"
class=
"subject-option-prefix"
></span></label>
</li>
</ul>
<ul
v-if=
"tempSubject.type === 2"
class=
"subject-options"
>
<li
class=
"subject-option"
>
<input
class=
"toggle"
type=
"checkbox"
>
<label><span
class=
"subject-option-prefix"
>
正确
</span></label>
</li>
<li
class=
"subject-option"
>
<input
class=
"toggle"
type=
"checkbox"
>
<label><span
class=
"subject-option-prefix"
>
错误
</span></label>
</li>
</ul>
<div
slot=
"footer"
class=
"dialog-footer"
>
<el-button
type=
"primary"
@
click=
"dialogViewVisible = false"
>
{{ $t('table.confirm') }}
</el-button>
</div>
</el-dialog>
</div>
</template>
<
script
>
import
{
fetchCategoryTree
,
getCategory
,
addCategory
,
delCategory
,
putCategory
}
from
'@/api/exam/subjectCategory'
import
{
fetchSubjectList
,
addSubject
,
putSubject
,
delSubject
,
delAllSubject
,
exportSubject
}
from
'@/api/exam/subject'
import
{
fetchSubjectList
,
addSubject
,
getSubject
,
putSubject
,
delSubject
,
delAllSubject
,
exportSubject
}
from
'@/api/exam/subject'
import
{
mapGetters
}
from
'vuex'
import
{
getToken
}
from
'@/utils/auth'
import
{
checkMultipleSelect
,
exportExcel
,
notifySuccess
,
isNotEmpty
}
from
'@/utils/util'
...
...
@@ -308,6 +340,8 @@ export default {
dialogImportVisible
:
false
,
// 导出窗口状态
dialogExportVisible
:
false
,
// 预览窗口状态
dialogViewVisible
:
false
,
// 选择的菜单
multipleSelection
:
[],
importUrl
:
'/api/exam/v1/subject/import'
,
...
...
@@ -699,6 +733,14 @@ export default {
}).
catch
(()
=>
{})
}
},
// 查看题目
handleViewSubject
(
row
)
{
// 加载题目信息
getSubject
(
row
.
id
,
{
type
:
row
.
type
}).
then
(
response
=>
{
this
.
tempSubject
=
response
.
data
.
data
this
.
dialogViewVisible
=
true
})
},
// 点击排序按钮
sortSubjectChange
(
column
,
prop
,
order
)
{
this
.
listQuery
.
sort
=
column
.
prop
...
...
@@ -813,16 +855,88 @@ export default {
}
</
script
>
<
style
scoped
>
<
style
lang=
"scss"
rel=
"stylesheet/scss"
scoped
>
.category-header
{
margin
:
12px
;
}
.tree-container
{
.tree-container
{
padding-top
:
10px
;
}
.category-btn
{
margin
:
5px
;
padding
:
6px
13px
;
}
.filter-tree
{
overflow
:
hidden
;
}
.subject-title
{
font-size
:
18px
;
line-height
:
22px
;
.subject-title-number
{
display
:
inline-block
;
line-height
:
22px
;
}
.subject-title-content
{
display
:
inline-block
;
}
}
.subject-options
{
margin
:
0
;
padding
:
0
;
list-style
:
none
;
>
li
{
position
:
relative
;
font-size
:
24px
;
.toggle
{
opacity
:
0
;
text-align
:
center
;
width
:
35px
;
/* auto, since non-WebKit browsers doesn't support input styling */
height
:
auto
;
position
:
absolute
;
top
:
0
;
bottom
:
0
;
margin
:
auto
0
;
border
:
none
;
/* Mobile Safari */
-webkit-appearance
:
none
;
appearance
:
none
;
}
.toggle
+
label
{
background-image
:
url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23ededed%22%20stroke-width%3D%223%22/%3E%3C/svg%3E')
;
background-repeat
:
no-repeat
;
background-position
:
center
left
;
background-size
:
30px
;
}
.toggle
:checked
+
label
{
background-size
:
30px
;
background-image
:
url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23bddad5%22%20stroke-width%3D%223%22/%3E%3Cpath%20fill%3D%22%235dc2af%22%20d%3D%22M72%2025L42%2071%2027%2056l-4%204%2020%2020%2034-52z%22/%3E%3C/svg%3E')
;
}
label
{
word-break
:
break-all
;
padding
:
10px
10px
10px
45px
;
display
:
block
;
line-height
:
1
.0
;
transition
:
color
0
.4s
;
}
/* 选项名称 */
.subject-option-prefix
{
font-size
:
16px
;
display
:
inline-block
}
}
}
</
style
>
frontend/spring-microservice-exam-ui/src/views/personal/message.vue
View file @
21f4f0c1
...
...
@@ -95,7 +95,6 @@
import
{
updateObjInfo
,
updateAvatar
}
from
'@/api/admin/user'
import
{
mapState
}
from
'vuex'
import
{
getToken
}
from
'@/utils/auth'
import
{
preview
}
from
'@/api/admin/attachment'
import
{
isNotEmpty
,
notifySuccess
,
notifyFail
}
from
'@/utils/util'
import
store
from
'@/store'
...
...
@@ -152,9 +151,7 @@ export default {
return
}
// 重新获取预览地址
preview
(
res
.
data
.
id
).
then
(
response
=>
{
this
.
userInfo
.
avatarUrl
=
response
.
data
.
data
})
this
.
userInfo
.
avatarUrl
=
'/api/user/v1/attachment/preview?id='
+
res
.
data
.
id
this
.
userInfo
.
avatarId
=
res
.
data
.
id
updateAvatar
(
this
.
userInfo
).
then
(
response
=>
{
notifySuccess
(
this
,
'头像上传成功'
)
...
...
frontend/spring-microservice-exam-ui/src/views/sys/menu.vue
View file @
21f4f0c1
...
...
@@ -428,4 +428,7 @@ export default {
.tab-container
{
margin
:
30px
;
}
.filter-tree
{
overflow
:
hidden
;
}
</
style
>
frontend/spring-microservice-exam-ui/static/img/login_bg.jpeg
0 → 100644
View file @
21f4f0c1
243 KB
frontend/spring-microservice-exam-web/package.json
View file @
21f4f0c1
{
"name"
:
"spring-microservice-exam-web"
,
"version"
:
"3.
5
.0"
,
"version"
:
"3.
7
.0"
,
"description"
:
"spring-microservice-exam-web"
,
"author"
:
"tangyi <1633736729@qq.com>"
,
"private"
:
true
,
...
...
frontend/spring-microservice-exam-web/src/views/Index.vue
View file @
21f4f0c1
...
...
@@ -45,7 +45,7 @@
</el-submenu>
<el-submenu
v-if=
"login"
index=
"/user-info"
>
<
template
slot=
"title"
>
<img
src=
"https://colorlib.com/preview/theme/clever/img/bg-img/t1.png
"
style=
"height: 30px;border-radius: 50%;margin-right: 6px;"
/>
<img
:src=
"userInfo.avatarUrl
"
style=
"height: 30px;border-radius: 50%;margin-right: 6px;"
/>
{{
userInfo
.
identifier
}}
</
template
>
<el-menu-item
index=
"account"
@
click=
"open('/account')"
>
个人中心
</el-menu-item>
...
...
frontend/spring-microservice-exam-web/src/views/common/header.vue
View file @
21f4f0c1
...
...
@@ -44,7 +44,7 @@
</el-submenu>
<el-submenu
v-if=
"login"
index=
"/user-info"
>
<
template
slot=
"title"
>
<img
src=
"https://colorlib.com/preview/theme/clever/img/bg-img/t1.png
"
style=
"height: 30px;border-radius: 50%;margin-right: 6px;"
/>
<img
:src=
"userInfo.avatarUrl
"
style=
"height: 30px;border-radius: 50%;margin-right: 6px;"
/>
{{
userInfo
.
identifier
}}
</
template
>
<el-menu-item
index=
"account"
@
click=
"open('/account')"
>
个人中心
</el-menu-item>
...
...
frontend/spring-microservice-exam-web/src/views/personal/account.vue
View file @
21f4f0c1
...
...
@@ -91,7 +91,6 @@ import { updateObjInfo, updateAvatar } from '@/api/admin/user'
import
OFooter
from
'../common/footer'
import
{
getToken
}
from
'@/utils/auth'
import
{
mapState
}
from
'vuex'
import
{
preview
}
from
'@/api/admin/attachment'
import
{
isNotEmpty
,
notifySuccess
,
notifyFail
}
from
'@/utils/util'
import
store
from
'@/store'
...
...
@@ -144,14 +143,12 @@ export default {
})
},
handleAvatarSuccess
(
res
,
file
)
{
if
(
!
isNotEmpty
(
res
.
data
)
||
!
isNotEmpty
(
res
.
data
.
fastFileId
)
)
{
if
(
!
isNotEmpty
(
res
.
data
))
{
notifyFail
(
this
,
'头像上传失败'
)
return
}
// 重新获取预览地址
preview
(
res
.
data
.
id
).
then
(
response
=>
{
this
.
userInfo
.
avatarUrl
=
response
.
data
.
data
})
this
.
userInfo
.
avatarUrl
=
'/api/user/v1/attachment/preview?id='
+
res
.
data
.
id
this
.
userInfo
.
avatarId
=
res
.
data
.
id
updateAvatar
(
this
.
userInfo
).
then
(
response
=>
{
notifySuccess
(
this
,
'头像上传成功'
)
...
...
modules/exam-service-parent/exam-service/src/main/java/com/github/tangyi/exam/service/CourseService.java
View file @
21f4f0c1
...
...
@@ -6,6 +6,7 @@ import com.github.tangyi.common.core.constant.CommonConstant;
import
com.github.tangyi.common.core.service.CrudService
;
import
com.github.tangyi.exam.api.module.Course
;
import
com.github.tangyi.exam.mapper.CourseMapper
;
import
com.github.tangyi.user.api.constant.AttachmentConstant
;
import
com.github.tangyi.user.api.module.Attachment
;
import
lombok.AllArgsConstructor
;
import
lombok.extern.slf4j.Slf4j
;
...
...
@@ -141,9 +142,7 @@ public class CourseService extends CrudService<CourseMapper, Course> {
courseList
.
forEach
(
course
->
{
// 获取配置默认头像地址
if
(
course
.
getLogoId
()
!=
null
&&
course
.
getLogoId
()
!=
0L
)
{
Attachment
attachment
=
new
Attachment
();
attachment
.
setId
(
course
.
getLogoId
());
course
.
setLogoUrl
(
sysProperties
.
getLogoUrl
()
+
course
.
getLogoId
()
+
sysProperties
.
getLogoSuffix
());
course
.
setLogoUrl
(
AttachmentConstant
.
ATTACHMENT_PREVIEW_URL
+
course
.
getLogoId
());
}
else
{
Long
index
=
new
Random
().
nextInt
(
sysProperties
.
getLogoCount
())
+
1L
;
course
.
setLogoUrl
(
sysProperties
.
getLogoUrl
()
+
index
+
sysProperties
.
getLogoSuffix
());
...
...
modules/exam-service-parent/exam-service/src/main/java/com/github/tangyi/exam/service/ExaminationService.java
View file @
21f4f0c1
...
...
@@ -14,6 +14,7 @@ import com.github.tangyi.exam.api.module.Examination;
import
com.github.tangyi.exam.api.module.ExaminationSubject
;
import
com.github.tangyi.exam.enums.ExaminationTypeEnum
;
import
com.github.tangyi.exam.mapper.ExaminationMapper
;
import
com.github.tangyi.user.api.constant.AttachmentConstant
;
import
com.github.tangyi.user.api.module.Attachment
;
import
lombok.AllArgsConstructor
;
import
lombok.extern.slf4j.Slf4j
;
...
...
@@ -297,7 +298,7 @@ public class ExaminationService extends CrudService<ExaminationMapper, Examinati
if
(
examinationDto
.
getAvatarId
()
!=
null
&&
examinationDto
.
getAvatarId
()
!=
0L
)
{
Attachment
attachment
=
new
Attachment
();
attachment
.
setId
(
examinationDto
.
getAvatarId
());
examinationDto
.
setLogoUrl
(
sysProperties
.
getLogoUrl
()
+
examinationDto
.
getAvatarId
()
+
sysProperties
.
getLogoSuffix
());
examinationDto
.
setLogoUrl
(
AttachmentConstant
.
ATTACHMENT_PREVIEW_URL
+
examinationDto
.
getAvatarId
());
}
else
{
Long
index
=
new
Random
().
nextInt
(
sysProperties
.
getLogoCount
())
+
1L
;
examinationDto
.
setLogoUrl
(
sysProperties
.
getLogoUrl
()
+
index
+
sysProperties
.
getLogoSuffix
());
...
...
modules/user-service-parent/user-service-api/src/main/java/com/github/tangyi/user/api/constant/AttachmentConstant.java
View file @
21f4f0c1
...
...
@@ -20,4 +20,9 @@ public class AttachmentConstant {
* 知识库附件
*/
public
static
final
String
BUSI_TYPE_KNOWLEDGE_ATTACHMENT
=
"2"
;
/**
* 附件预览地址
*/
public
static
final
String
ATTACHMENT_PREVIEW_URL
=
"/api/user/v1/attachment/preview?id="
;
}
modules/user-service-parent/user-service-api/src/main/java/com/github/tangyi/user/api/module/Attachment.java
View file @
21f4f0c1
package
com
.
github
.
tangyi
.
user
.
api
.
module
;
import
com.fasterxml.jackson.annotation.JsonIgnore
;
import
com.github.tangyi.common.core.persistence.BaseEntity
;
import
com.github.tangyi.user.api.constant.AttachmentConstant
;
import
lombok.Data
;
import
javax.validation.constraints.NotBlank
;
/**
* 附件信息
*
...
...
@@ -18,16 +17,19 @@ public class Attachment extends BaseEntity<Attachment> {
/**
* 附件名称
*/
@NotBlank
(
message
=
"附件名称不能为空"
)
private
String
attachName
;
/**
* 附件大小
*/
@NotBlank
(
message
=
"附件大小不能为空"
)
private
String
attachSize
;
/**
* 附件类型
*/
private
String
attachType
;
/**
* 组名称
*/
private
String
groupName
;
...
...
@@ -35,12 +37,12 @@ public class Attachment extends BaseEntity<Attachment> {
/**
* 文件ID
*/
@JsonIgnore
private
String
fastFileId
;
/**
* 业务流水号
*/
@NotBlank
(
message
=
"附件业务流水号不能为空"
)
private
String
busiId
;
/**
...
...
@@ -57,4 +59,14 @@ public class Attachment extends BaseEntity<Attachment> {
* 预览地址
*/
private
String
previewUrl
;
/**
* 上传类型,1:本地目录,2:fastDfs,3:七牛云
*/
private
Integer
uploadType
;
/**
* 上传结果
*/
private
String
uploadResult
;
}
modules/user-service-parent/user-service/src/main/java/com/github/tangyi/user/controller/AttachmentController.java
View file @
21f4f0c1
package
com
.
github
.
tangyi
.
user
.
controller
;
import
com.github.pagehelper.PageInfo
;
import
com.github.tangyi.common.basic.properties.SysProperties
;
import
com.github.tangyi.common.basic.vo.AttachmentVo
;
import
com.github.tangyi.common.core.constant.CommonConstant
;
import
com.github.tangyi.common.core.exceptions.CommonException
;
import
com.github.tangyi.common.core.model.ResponseBean
;
import
com.github.tangyi.common.core.utils.FileUtil
;
import
com.github.tangyi.common.core.utils.PageUtil
;
import
com.github.tangyi.common.core.utils.Servlets
;
import
com.github.tangyi.common.core.web.BaseController
;
import
com.github.tangyi.common.log.annotation.Log
;
import
com.github.tangyi.common.security.utils.SysUtil
;
import
com.github.tangyi.user.api.module.Attachment
;
import
com.github.tangyi.user.service.AttachmentService
;
import
com.github.tangyi.user.uploader.UploadInvoker
;
import
com.google.common.net.HttpHeaders
;
import
io.swagger.annotations.*
;
import
lombok.AllArgsConstructor
;
import
lombok.extern.slf4j.Slf4j
;
import
org.apache.commons.collections4.CollectionUtils
;
import
org.apache.commons.lang.ArrayUtils
;
import
org.apache.commons.lang3.StringUtils
;
import
org.springframework.beans.BeanUtils
;
import
org.springframework.util.FileCopyUtils
;
import
org.springframework.web.bind.annotation.*
;
import
org.springframework.web.multipart.MultipartFile
;
import
javax.servlet.http.HttpServletRequest
;
import
javax.servlet.http.HttpServletResponse
;
import
javax.validation.constraints.NotBlank
;
import
java.io.*
;
import
java.net.URLEncoder
;
import
java.util.List
;
import
java.util.stream.Collectors
;
...
...
@@ -39,6 +50,8 @@ public class AttachmentController extends BaseController {
private
final
AttachmentService
attachmentService
;
private
final
SysProperties
sysProperties
;
/**
* 根据ID获取
*
...
...
@@ -104,9 +117,19 @@ public class AttachmentController extends BaseController {
@Log
(
"上传文件"
)
public
ResponseBean
<
Attachment
>
upload
(
@ApiParam
(
value
=
"要上传的文件"
,
required
=
true
)
@RequestParam
(
"file"
)
MultipartFile
file
,
Attachment
attachment
)
{
if
(
file
.
isEmpty
())
return
new
ResponseBean
<>(
new
Attachment
());
return
new
ResponseBean
<>(
attachmentService
.
upload
(
file
,
attachment
));
if
(!
file
.
isEmpty
())
{
try
{
attachment
.
setCommonValue
(
SysUtil
.
getUser
(),
SysUtil
.
getSysCode
(),
SysUtil
.
getTenantCode
());
attachment
.
setAttachType
(
FileUtil
.
getFileNameEx
(
file
.
getOriginalFilename
()));
attachment
.
setAttachSize
(
String
.
valueOf
(
file
.
getSize
()));
attachment
.
setAttachName
(
file
.
getOriginalFilename
());
attachment
.
setBusiId
(
attachment
.
getId
().
toString
());
attachment
=
UploadInvoker
.
getInstance
().
upload
(
attachment
,
file
.
getBytes
());
}
catch
(
Exception
e
)
{
log
.
error
(
"upload attachment error: {}"
,
e
.
getMessage
(),
e
);
}
}
return
new
ResponseBean
<>(
attachment
);
}
/**
...
...
@@ -119,19 +142,36 @@ public class AttachmentController extends BaseController {
@GetMapping
(
"download"
)
@ApiOperation
(
value
=
"下载附件"
,
notes
=
"根据ID下载附件"
)
@ApiImplicitParam
(
name
=
"id"
,
value
=
"附件ID"
,
required
=
true
,
dataType
=
"Long"
)
public
ResponseBean
<
String
>
download
(
@NotBlank
Long
id
)
{
String
downloadUrl
=
""
;
public
void
download
(
HttpServletRequest
request
,
HttpServletResponse
response
,
@NotBlank
Long
id
)
{
try
{
Attachment
attachment
=
new
Attachment
();
attachment
.
setId
(
id
);
attachment
=
attachmentService
.
get
(
attachment
);
if
(
attachment
==
null
)
throw
new
CommonException
(
"Attachment does not exist"
);
downloadUrl
=
attachmentService
.
download
(
attachment
);
InputStream
inputStream
=
UploadInvoker
.
getInstance
().
download
(
attachment
);
if
(
inputStream
==
null
)
{
log
.
info
(
"attachment is not exists"
);
return
;
}
OutputStream
outputStream
=
response
.
getOutputStream
();
response
.
setContentType
(
"application/zip"
);
response
.
setHeader
(
HttpHeaders
.
CACHE_CONTROL
,
"max-age=10"
);
// IE之外的浏览器使用编码输出名称
String
contentDisposition
=
""
;
String
httpUserAgent
=
request
.
getHeader
(
"User-Agent"
);
if
(
StringUtils
.
isNotEmpty
(
httpUserAgent
))
{
httpUserAgent
=
httpUserAgent
.
toLowerCase
();
String
fileName
=
attachment
.
getAttachName
();
contentDisposition
=
httpUserAgent
.
contains
(
"wps"
)
?
"attachment;filename="
+
URLEncoder
.
encode
(
fileName
,
"UTF-8"
)
:
Servlets
.
getDownName
(
request
,
fileName
);
}
response
.
setHeader
(
HttpHeaders
.
CONTENT_DISPOSITION
,
contentDisposition
);
response
.
setContentLength
(
inputStream
.
available
());
FileCopyUtils
.
copy
(
inputStream
,
outputStream
);
log
.
info
(
"download {} success"
,
attachment
.
getAttachName
());
}
catch
(
Exception
e
)
{
log
.
error
(
"Download attachment failed: {}"
,
e
.
getMessage
(),
e
);
}
return
new
ResponseBean
<>(
downloadUrl
);
}
/**
...
...
@@ -152,7 +192,7 @@ public class AttachmentController extends BaseController {
attachment
=
attachmentService
.
get
(
attachment
);
boolean
success
=
false
;
if
(
attachment
!=
null
)
success
=
attachmentService
.
delete
(
attachment
)
>
0
;
success
=
UploadInvoker
.
getInstance
().
delete
(
attachment
)
;
return
new
ResponseBean
<>(
success
);
}
...
...
@@ -172,7 +212,7 @@ public class AttachmentController extends BaseController {
boolean
success
=
false
;
try
{
if
(
ArrayUtils
.
isNotEmpty
(
ids
))
success
=
attachmentService
.
deleteAll
(
ids
)
>
0
;
success
=
UploadInvoker
.
getInstance
().
deleteAll
(
ids
)
;
}
catch
(
Exception
e
)
{
log
.
error
(
"Delete attachment failed"
,
e
);
}
...
...
@@ -205,19 +245,49 @@ public class AttachmentController extends BaseController {
}
/**
*
获取预览地址
*
是否支持预览
*
* @param id id
* @return ResponseBean
* @author tangyi
* @date 2019/06/19 15:47
*/
@GetMapping
(
"/{id}/
p
review"
)
@ApiOperation
(
value
=
"
获取预览地址"
,
notes
=
"根据附件ID获取预览地址
"
)
@GetMapping
(
"/{id}/
canP
review"
)
@ApiOperation
(
value
=
"
判断附件是否支持预览"
,
notes
=
"根据附件ID判断附件是否支持预览
"
)
@ApiImplicitParam
(
name
=
"id"
,
value
=
"附件id"
,
required
=
true
,
dataType
=
"Long"
,
paramType
=
"path"
)
public
ResponseBean
<
String
>
getPreviewUrl
(
@PathVariable
Long
id
)
{
public
ResponseBean
<
Boolean
>
canPreview
(
@PathVariable
Long
id
)
{
Attachment
attachment
=
new
Attachment
();
attachment
.
setId
(
id
);
return
new
ResponseBean
<>(
attachmentService
.
getPreviewUrl
(
attachment
));
attachment
=
attachmentService
.
get
(
attachment
);
return
new
ResponseBean
<>(
attachment
!=
null
&&
ArrayUtils
.
contains
(
sysProperties
.
getCanPreview
().
split
(
","
),
attachment
.
getAttachType
()));
}
/**
* 预览附件
*
* @param response response
* @param id id
* @author tangyi
* @date 2019/06/19 15:47
*/
@GetMapping
(
"/preview"
)
@ApiOperation
(
value
=
"预览附件"
,
notes
=
"根据附件ID预览附件"
)
@ApiImplicitParam
(
name
=
"id"
,
value
=
"附件id"
,
required
=
true
,
dataType
=
"Long"
)
public
void
preview
(
HttpServletResponse
response
,
@RequestParam
Long
id
)
throws
Exception
{
Attachment
attachment
=
new
Attachment
();
attachment
.
setId
(
id
);
attachment
=
attachmentService
.
get
(
attachment
);
FileInputStream
stream
=
new
FileInputStream
(
new
File
(
attachment
.
getFastFileId
()
+
File
.
separator
+
attachment
.
getAttachName
()));
ByteArrayOutputStream
out
=
new
ByteArrayOutputStream
(
1000
);
byte
[]
b
=
new
byte
[
1000
];
int
n
;
while
((
n
=
stream
.
read
(
b
))
!=
-
1
)
{
out
.
write
(
b
,
0
,
n
);
}
response
.
setHeader
(
"Content-Type"
,
"image/png"
);
response
.
getOutputStream
().
write
(
out
.
toByteArray
());
response
.
getOutputStream
().
flush
();
out
.
close
();
stream
.
close
();
}
}
modules/user-service-parent/user-service/src/main/java/com/github/tangyi/user/enums/AttachUploaderEnum.java
0 → 100644
View file @
21f4f0c1
package
com
.
github
.
tangyi
.
user
.
enums
;
import
lombok.Getter
;
/**
* 附件存储类型
* @author tangyi
* @date 2020/04/05 14:01
*/
@Getter
public
enum
AttachUploaderEnum
{
FILE
(
1
,
"文件"
,
"com.github.tangyi.user.uploader.FileUploader"
),
FAST_DFS
(
2
,
"FastDfs"
,
"com.github.tangyi.user.uploader.FastDfsUploader"
),
QI_NIU
(
3
,
"七牛云"
,
"com.github.tangyi.user.uploader.QiNiuUploader"
);
private
Integer
value
;
private
String
desc
;
private
String
implClass
;
AttachUploaderEnum
(
int
value
,
String
desc
,
String
implClass
)
{
this
.
value
=
value
;
this
.
desc
=
desc
;
this
.
implClass
=
implClass
;
}
public
static
AttachUploaderEnum
matchByValue
(
Integer
value
)
{
for
(
AttachUploaderEnum
item
:
AttachUploaderEnum
.
values
())
{
if
(
item
.
value
.
equals
(
value
))
{
return
item
;
}
}
return
FILE
;
}
}
modules/user-service-parent/user-service/src/main/java/com/github/tangyi/user/service/AttachmentService.java
View file @
21f4f0c1
package
com
.
github
.
tangyi
.
user
.
service
;
import
com.github.tangyi.common.core.constant.CommonConstant
;
import
com.github.tangyi.common.core.exceptions.CommonException
;
import
com.github.tangyi.common.core.service.CrudService
;
import
com.github.tangyi.common.security.utils.SysUtil
;
import
com.github.tangyi.oss.service.QiNiuService
;
import
com.github.tangyi.user.api.constant.AttachmentConstant
;
import
com.github.tangyi.user.api.module.Attachment
;
import
com.github.tangyi.user.mapper.AttachmentMapper
;
import
com.github.tangyi.user.uploader.UploadInvoker
;
import
lombok.AllArgsConstructor
;
import
lombok.extern.slf4j.Slf4j
;
import
org.apache.commons.lang
3
.StringUtils
;
import
org.apache.commons.lang.StringUtils
;
import
org.springframework.cache.annotation.CacheEvict
;
import
org.springframework.cache.annotation.Cacheable
;
import
org.springframework.stereotype.Service
;
import
org.springframework.transaction.annotation.Transactional
;
import
org.springframework.web.multipart.MultipartFile
;
import
java.
nio.charset.StandardCharsets
;
import
java.
io.InputStream
;
/**
* @author tangyi
...
...
@@ -27,8 +25,6 @@ import java.nio.charset.StandardCharsets;
@Service
public
class
AttachmentService
extends
CrudService
<
AttachmentMapper
,
Attachment
>
{
private
final
QiNiuService
qiNiuService
;
/**
* 根据id查询
*
...
...
@@ -55,50 +51,14 @@ public class AttachmentService extends CrudService<AttachmentMapper, Attachment>
}
/**
* 上传
*
* @param file file
* @param attachment attachment
* @return int
*/
@Transactional
public
Attachment
upload
(
MultipartFile
file
,
Attachment
attachment
)
{
try
{
long
start
=
System
.
currentTimeMillis
();
long
attachSize
=
file
.
getSize
();
if
(
StringUtils
.
isNotBlank
(
file
.
getOriginalFilename
()))
{
String
fileName
=
new
String
(
file
.
getOriginalFilename
().
getBytes
(),
StandardCharsets
.
UTF_8
);
String
previewUrl
=
qiNiuService
.
upload
(
file
.
getBytes
(),
fileName
);
Attachment
newAttachment
=
new
Attachment
();
newAttachment
.
setCommonValue
(
SysUtil
.
getUser
(),
SysUtil
.
getSysCode
(),
SysUtil
.
getTenantCode
());
newAttachment
.
setPreviewUrl
(
previewUrl
);
newAttachment
.
setAttachName
(
fileName
);
newAttachment
.
setGroupName
(
qiNiuService
.
getDomainOfBucket
());
newAttachment
.
setFastFileId
(
fileName
);
newAttachment
.
setAttachSize
(
Long
.
toString
(
attachSize
));
newAttachment
.
setBusiId
(
attachment
.
getBusiId
());
newAttachment
.
setBusiModule
(
attachment
.
getBusiModule
());
newAttachment
.
setBusiType
(
attachment
.
getBusiType
());
super
.
insert
(
newAttachment
);
log
.
info
(
"Upload attachment success, fileName: {}, time: {}ms"
,
file
.
getName
(),
System
.
currentTimeMillis
()
-
start
);
return
newAttachment
;
}
return
null
;
}
catch
(
Exception
e
)
{
log
.
error
(
e
.
getMessage
(),
e
);
throw
new
CommonException
(
e
);
}
}
/**
* 下载
*
* @param attachment attachment
* @return InputStream
*/
public
String
download
(
Attachment
attachment
)
throws
Exception
{
public
InputStream
download
(
Attachment
attachment
)
throws
Exception
{
// 下载附件
return
qiNiuService
.
getDownloadUrl
(
attachment
.
getAttachName
()
);
return
UploadInvoker
.
getInstance
().
download
(
attachment
);
}
/**
...
...
@@ -140,8 +100,11 @@ public class AttachmentService extends CrudService<AttachmentMapper, Attachment>
attachment
=
this
.
get
(
attachment
);
if
(
attachment
!=
null
)
{
String
preview
=
attachment
.
getPreviewUrl
();
if
(
!
preview
.
startsWith
(
"http"
))
if
(
StringUtils
.
isNotBlank
(
preview
)
&&
!
preview
.
startsWith
(
"http"
))
{
preview
=
"http://"
+
preview
;
}
else
{
preview
=
AttachmentConstant
.
ATTACHMENT_PREVIEW_URL
+
attachment
.
getId
();
}
log
.
debug
(
"GetPreviewUrl id: {}, preview url: {}"
,
attachment
.
getId
(),
preview
);
return
preview
;
}
...
...
modules/user-service-parent/user-service/src/main/java/com/github/tangyi/user/uploader/AbstractUploader.java
0 → 100644
View file @
21f4f0c1
package
com
.
github
.
tangyi
.
user
.
uploader
;
import
com.github.tangyi.common.basic.properties.SysProperties
;
import
com.github.tangyi.common.core.utils.SpringContextHolder
;
import
com.github.tangyi.common.security.utils.SysUtil
;
import
com.github.tangyi.user.api.module.Attachment
;
import
com.github.tangyi.user.service.AttachmentService
;
import
org.apache.commons.lang3.StringUtils
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
java.io.File
;
import
java.io.InputStream
;
/**
* @author tangyi
* @date 2020/04/05 13:37
*/
public
abstract
class
AbstractUploader
implements
IUploader
{
@Override
public
int
save
(
Attachment
attachment
)
{
return
SpringContextHolder
.
getApplicationContext
().
getBean
(
AttachmentService
.
class
).
insert
(
attachment
);
}
@Override
public
boolean
delete
(
Attachment
attachment
)
{
return
SpringContextHolder
.
getApplicationContext
().
getBean
(
AttachmentService
.
class
).
delete
(
attachment
)
>
0
;
}
@Override
public
abstract
Attachment
upload
(
Attachment
attachment
,
byte
[]
bytes
);
@Override
public
abstract
InputStream
download
(
Attachment
attachment
);
/**
* 获取附件存储目录
*
* @param attachment attachment
* @param id id
* @return String
*/
public
String
getFileRealDirectory
(
Attachment
attachment
,
String
id
)
{
String
applicationCode
=
attachment
.
getApplicationCode
();
String
busiId
=
attachment
.
getBusiId
();
String
fileName
=
attachment
.
getAttachName
();
String
fileRealDirectory
=
SpringContextHolder
.
getApplicationContext
().
getBean
(
SysProperties
.
class
).
getAttachPath
()
+
File
.
separator
+
applicationCode
+
File
.
separator
;
// 有分类就加上
if
(
StringUtils
.
isNotBlank
(
attachment
.
getBusiModule
()))
{
String
busiModule
=
attachment
.
getBusiModule
();
fileRealDirectory
=
fileRealDirectory
+
busiModule
+
File
.
separator
;
}
if
(
StringUtils
.
isNotBlank
(
attachment
.
getBusiType
()))
{
String
busiType
=
attachment
.
getBusiType
();
fileRealDirectory
=
fileRealDirectory
+
busiType
+
File
.
separator
;
}
fileRealDirectory
=
fileRealDirectory
+
busiId
;
return
fileRealDirectory
;
}
}
modules/user-service-parent/user-service/src/main/java/com/github/tangyi/user/uploader/FastDfsUploader.java
0 → 100644
View file @
21f4f0c1
package
com
.
github
.
tangyi
.
user
.
uploader
;
import
com.github.tangyi.oss.service.FastDfsService
;
import
com.github.tangyi.user.api.module.Attachment
;
import
lombok.extern.slf4j.Slf4j
;
import
org.apache.commons.lang3.StringUtils
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.stereotype.Service
;
import
java.io.ByteArrayInputStream
;
import
java.io.InputStream
;
/**
* 上传到FastDfs
* @author tangyi
* @date 2020/04/05 13:36
*/
@Slf4j
@Service
public
class
FastDfsUploader
extends
AbstractUploader
{
@Autowired
private
FastDfsService
fastDfsService
;
@Override
public
Attachment
upload
(
Attachment
attachment
,
byte
[]
bytes
)
{
try
{
attachment
.
setAttachSize
(
String
.
valueOf
(
bytes
.
length
));
String
fastFileId
=
fastDfsService
.
uploadFile
(
new
ByteArrayInputStream
(
bytes
),
bytes
.
length
,
attachment
.
getAttachType
());
String
groupName
=
fastFileId
.
substring
(
0
,
fastFileId
.
indexOf
(
"/"
));
attachment
.
setFastFileId
(
fastFileId
);
attachment
.
setGroupName
(
groupName
);
return
attachment
;
}
catch
(
Exception
e
)
{
log
.
error
(
"上传附件至网盘失败:"
+
attachment
.
getAttachName
()
+
e
.
getMessage
());
return
null
;
}
}
@Override
public
InputStream
download
(
Attachment
attachment
)
{
return
fastDfsService
.
downloadStream
(
attachment
.
getGroupName
(),
attachment
.
getFastFileId
());
}
@Override
public
boolean
delete
(
Attachment
attachment
)
{
if
(
StringUtils
.
isNotEmpty
(
attachment
.
getGroupName
())
&&
StringUtils
.
isNotEmpty
(
attachment
.
getFastFileId
()))
{
fastDfsService
.
deleteFile
(
attachment
.
getGroupName
(),
attachment
.
getFastFileId
());
}
return
Boolean
.
TRUE
;
}
@Override
public
boolean
deleteAll
(
Attachment
attachment
)
{
return
false
;
}
}
modules/user-service-parent/user-service/src/main/java/com/github/tangyi/user/uploader/FileUploader.java
0 → 100644
View file @
21f4f0c1
package
com
.
github
.
tangyi
.
user
.
uploader
;
import
com.github.tangyi.common.core.exceptions.CommonException
;
import
com.github.tangyi.common.core.utils.FileUtil
;
import
com.github.tangyi.user.api.module.Attachment
;
import
lombok.extern.slf4j.Slf4j
;
import
org.apache.commons.lang3.StringUtils
;
import
org.springframework.stereotype.Service
;
import
java.io.*
;
/**
* 上传到本地目录
*
* @author tangyi
* @date 2020/04/05 13:36
*/
@Slf4j
@Service
public
class
FileUploader
extends
AbstractUploader
{
@Override
public
Attachment
upload
(
Attachment
attachment
,
byte
[]
bytes
)
{
try
{
String
fileRealDirectory
=
getFileRealDirectory
(
attachment
,
attachment
.
getId
().
toString
());
fileRealDirectory
=
fileRealDirectory
.
replaceAll
(
"\\\\"
,
"/"
);
String
fileName
=
attachment
.
getAttachName
();
attachment
.
setAttachSize
(
String
.
valueOf
(
bytes
.
length
));
log
.
info
(
"file read directory: {}"
,
fileRealDirectory
);
FileUtil
.
createDirectory
(
fileRealDirectory
);
log
.
info
(
"start write file: {}"
,
fileName
);
saveFileFormByteArray
(
bytes
,
fileRealDirectory
,
fileName
);
log
.
info
(
"write file finished: {}"
,
fileName
);
attachment
.
setFastFileId
(
fileRealDirectory
);
return
attachment
;
}
catch
(
Exception
e
)
{
log
.
error
(
"FileUploader error:{}, {}"
,
attachment
.
getAttachName
(),
e
.
getMessage
(),
e
);
return
null
;
}
}
@Override
public
InputStream
download
(
Attachment
attachment
)
{
String
path
=
attachment
.
getFastFileId
()
+
File
.
separator
+
attachment
.
getAttachName
();
InputStream
input
=
null
;
try
{
String
fileRealDirectory
=
getFileRealDirectory
(
attachment
,
attachment
.
getId
().
toString
());
fileRealDirectory
=
fileRealDirectory
.
replaceAll
(
"\\\\"
,
"/"
);
if
(
StringUtils
.
isNotBlank
(
fileRealDirectory
)
&&
!
fileRealDirectory
.
equals
(
attachment
.
getFastFileId
()))
throw
new
CommonException
(
"attach path validate failure!attachPath:"
+
attachment
.
getFastFileId
()
+
", fileRealDirectory:"
+
fileRealDirectory
);
input
=
new
FileInputStream
(
new
File
(
path
));
}
catch
(
Exception
e
)
{
log
.
error
(
"download attachment failure: {}"
,
e
.
getMessage
(),
e
);
}
return
input
;
}
@Override
public
boolean
delete
(
Attachment
attachment
)
{
String
path
=
attachment
.
getFastFileId
()
+
File
.
separator
+
attachment
.
getAttachName
();
File
file
=
new
File
(
path
);
if
(
file
.
delete
())
{
FileUtil
.
deleteDirectory
(
attachment
.
getFastFileId
());
return
super
.
delete
(
attachment
);
}
return
Boolean
.
FALSE
;
}
@Override
public
boolean
deleteAll
(
Attachment
attachment
)
{
return
false
;
}
private
void
saveFileFormByteArray
(
byte
[]
b
,
String
path
,
String
fileName
)
throws
IOException
{
BufferedOutputStream
fs
=
new
BufferedOutputStream
(
new
FileOutputStream
(
path
+
"/"
+
fileName
,
true
));
fs
.
write
(
b
);
fs
.
flush
();
fs
.
close
();
}
}
modules/user-service-parent/user-service/src/main/java/com/github/tangyi/user/uploader/IUploader.java
0 → 100644
View file @
21f4f0c1
package
com
.
github
.
tangyi
.
user
.
uploader
;
import
com.github.tangyi.user.api.module.Attachment
;
import
java.io.InputStream
;
/**
* @author tangyi
* @date 2020/04/05 13:36
*/
public
interface
IUploader
{
/**
* 上传附件
* @param attachment attachment
* @param bytes bytes
* @return Attachment
*/
Attachment
upload
(
Attachment
attachment
,
byte
[]
bytes
);
/**
* 保存附件信息
* @param attachment attachment
* @return int
*/
int
save
(
Attachment
attachment
);
/**
* 下载附件
* @param attachment attachment
* @return InputStream
*/
InputStream
download
(
Attachment
attachment
);
/**
* 删除附件
* @param attachment attachment
* @return boolean
*/
boolean
delete
(
Attachment
attachment
);
/**
* 批量删除
* @param attachment attachment
* @return boolean
*/
boolean
deleteAll
(
Attachment
attachment
);
}
modules/user-service-parent/user-service/src/main/java/com/github/tangyi/user/uploader/QiNiuUploader.java
0 → 100644
View file @
21f4f0c1
package
com
.
github
.
tangyi
.
user
.
uploader
;
import
com.github.tangyi.oss.service.QiNiuUtil
;
import
com.github.tangyi.user.api.module.Attachment
;
import
lombok.extern.slf4j.Slf4j
;
import
org.springframework.stereotype.Service
;
import
java.io.InputStream
;
/**
* 上传到七牛云
*
* @author tangyi
* @date 2020/04/05 13:36
*/
@Slf4j
@Service
public
class
QiNiuUploader
extends
AbstractUploader
{
@Override
public
Attachment
upload
(
Attachment
attachment
,
byte
[]
bytes
)
{
String
result
=
QiNiuUtil
.
getInstance
().
upload
(
bytes
,
attachment
.
getAttachName
());
attachment
.
setUploadResult
(
result
);
attachment
.
setPreviewUrl
(
attachment
.
getUploadResult
());
return
attachment
;
}
@Override
public
InputStream
download
(
Attachment
attachment
)
{
return
null
;
}
@Override
public
boolean
delete
(
Attachment
attachment
)
{
return
QiNiuUtil
.
getInstance
().
delete
(
attachment
.
getAttachName
());
}
@Override
public
boolean
deleteAll
(
Attachment
attachment
)
{
return
false
;
}
}
modules/user-service-parent/user-service/src/main/java/com/github/tangyi/user/uploader/UploadInvoker.java
0 → 100644
View file @
21f4f0c1
package
com
.
github
.
tangyi
.
user
.
uploader
;
import
com.github.tangyi.common.basic.properties.SysProperties
;
import
com.github.tangyi.common.core.exceptions.CommonException
;
import
com.github.tangyi.common.core.utils.SpringContextHolder
;
import
com.github.tangyi.user.api.module.Attachment
;
import
com.github.tangyi.user.enums.AttachUploaderEnum
;
import
com.github.tangyi.user.service.AttachmentService
;
import
lombok.extern.slf4j.Slf4j
;
import
org.apache.commons.lang.StringUtils
;
import
java.io.InputStream
;
import
java.util.HashMap
;
import
java.util.Map
;
/**
* @author tangyi
* @date 2020/04/05 14:16
*/
@Slf4j
public
class
UploadInvoker
{
private
Map
<
Integer
,
IUploader
>
uploaderMap
=
null
;
private
static
UploadInvoker
instance
;
private
AttachmentService
attachmentService
;
public
UploadInvoker
(
AttachmentService
attachmentService
)
{
this
.
attachmentService
=
attachmentService
;
}
public
synchronized
static
UploadInvoker
getInstance
()
{
if
(
instance
==
null
)
{
instance
=
new
UploadInvoker
(
SpringContextHolder
.
getApplicationContext
().
getBean
(
AttachmentService
.
class
));
}
return
instance
;
}
/**
* 上传附件
*
* @param attachment attachment
* @param bytes bytes
* @return Attachment
* @author tangyi
* @date 2020/04/05 14:27
*/
public
Attachment
upload
(
Attachment
attachment
,
byte
[]
bytes
)
{
if
(
attachment
==
null
||
bytes
==
null
)
return
null
;
if
(
attachment
.
getUploadType
()
==
null
)
{
String
uploadType
=
SpringContextHolder
.
getApplicationContext
().
getBean
(
SysProperties
.
class
).
getAttachUploadType
();
if
(
StringUtils
.
isNotBlank
(
uploadType
))
{
attachment
.
setUploadType
(
Integer
.
parseInt
(
uploadType
));
}
}
IUploader
uploader
=
this
.
getUploader
(
attachment
.
getUploadType
());
if
(
uploader
==
null
)
throw
new
CommonException
(
"uploader is null"
);
attachment
=
uploader
.
upload
(
attachment
,
bytes
);
if
(
attachment
!=
null
)
{
uploader
.
save
(
attachment
);
}
return
attachment
;
}
/**
* 下载附件
*
* @param attachment attachment
* @return Attachment
* @author tangyi
* @date 2020/04/05 14:29
*/
public
InputStream
download
(
Attachment
attachment
)
{
if
(
attachment
==
null
)
return
null
;
IUploader
uploader
=
this
.
getUploader
(
attachment
.
getUploadType
());
if
(
uploader
==
null
)
throw
new
CommonException
(
"uploader is null"
);
return
uploader
.
download
(
attachment
);
}
/**
* 删除附件
*
* @param attachment attachment
* @return Attachment
* @author tangyi
* @date 2020/04/05 14:29
*/
public
boolean
delete
(
Attachment
attachment
)
{
if
(
attachment
==
null
)
return
Boolean
.
FALSE
;
IUploader
uploader
=
this
.
getUploader
(
attachment
.
getUploadType
());
if
(
uploader
==
null
)
throw
new
CommonException
(
"uploader is null"
);
return
uploader
.
delete
(
attachment
);
}
/**
* 批量删除附件
*
* @param ids ids
* @return Attachment
* @author tangyi
* @date 2020/04/05 15:03
*/
public
boolean
deleteAll
(
Long
[]
ids
)
{
boolean
result
=
false
;
for
(
Long
id
:
ids
)
{
// 查询出实体
Attachment
attachmentSearch
=
new
Attachment
();
attachmentSearch
.
setId
(
id
);
attachmentSearch
=
attachmentService
.
get
(
attachmentSearch
);
IUploader
uploader
=
getUploader
(
attachmentSearch
.
getUploadType
());
// 删除对应存储方式中的附件
result
=
uploader
.
delete
(
attachmentSearch
);
if
(
result
)
{
uploader
.
delete
(
attachmentSearch
);
}
}
return
result
;
}
/**
* 获取附件实现类
*
* @param uploadType uploadType
* @return IUploader
* @author tangyi
* @date 2020/04/05 14:17
*/
private
IUploader
getUploader
(
Integer
uploadType
)
{
IUploader
uploader
;
if
(
uploaderMap
==
null
)
{
uploaderMap
=
new
HashMap
<>();
}
uploader
=
uploaderMap
.
get
(
uploadType
);
try
{
if
(
uploader
==
null
)
{
// 如果没有初始化则创建
String
implClass
=
AttachUploaderEnum
.
matchByValue
(
uploadType
).
getImplClass
();
Class
<?>
clazz
=
Class
.
forName
(
implClass
);
uploader
=
(
IUploader
)
clazz
.
newInstance
();
uploaderMap
.
put
(
uploadType
,
uploader
);
}
}
catch
(
Exception
e
)
{
log
.
error
(
"getUploader error:{}"
,
e
.
getMessage
(),
e
);
return
null
;
}
return
uploader
;
}
}
modules/user-service-parent/user-service/src/main/resources/mapper/AttachmentMapper.xml
View file @
21f4f0c1
...
...
@@ -4,6 +4,7 @@
<resultMap
id=
"attachmentResultMap"
type=
"com.github.tangyi.user.api.module.Attachment"
>
<id
column=
"id"
property=
"id"
/>
<result
column=
"attach_name"
property=
"attachName"
/>
<result
column=
"attach_type"
property=
"attachType"
/>
<result
column=
"attach_size"
property=
"attachSize"
/>
<result
column=
"group_name"
property=
"groupName"
/>
<result
column=
"fast_file_id"
property=
"fastFileId"
/>
...
...
@@ -11,6 +12,7 @@
<result
column=
"busi_module"
property=
"busiModule"
/>
<result
column=
"busi_type"
property=
"busiType"
/>
<result
column=
"preview_url"
property=
"previewUrl"
/>
<result
column=
"upload_type"
property=
"uploadType"
/>
<result
column=
"creator"
property=
"creator"
/>
<result
column=
"create_date"
property=
"createDate"
javaType=
"java.util.Date"
jdbcType=
"TIMESTAMP"
/>
<result
column=
"modifier"
property=
"modifier"
/>
...
...
@@ -23,6 +25,7 @@
<sql
id=
"attachmentColumns"
>
a.id,
a.attach_name,
a.attach_type,
a.attach_size,
a.group_name,
a.fast_file_id,
...
...
@@ -30,6 +33,7 @@
a.busi_module,
a.busi_type,
a.preview_url,
a.upload_type,
a.creator,
a.create_date,
a.modifier,
...
...
@@ -92,6 +96,7 @@
INSERT INTO sys_attachment (
id,
attach_name,
attach_type,
attach_size,
group_name,
fast_file_id,
...
...
@@ -99,6 +104,7 @@
busi_module,
busi_type,
preview_url,
upload_type,
creator,
create_date,
modifier,
...
...
@@ -109,6 +115,7 @@
) VALUES (
#{id},
#{attachName},
#{attachType},
#{attachSize},
#{groupName},
#{fastFileId},
...
...
@@ -116,6 +123,7 @@
#{busiModule},
#{busiType},
#{previewUrl},
#{uploadType},
#{creator},
#{createDate, jdbcType=TIMESTAMP, javaType=java.util.Date},
#{modifier},
...
...
@@ -131,6 +139,9 @@
<if
test=
"attachName != null"
>
attach_name = #{attachName},
</if>
<if
test=
"attachType != null"
>
attach_type = #{attachType},
</if>
<if
test=
"attachSize != null"
>
attach_size = #{attachSize},
</if>
...
...
@@ -152,6 +163,9 @@
<if
test=
"previewUrl != null"
>
preview_url = #{previewUrl},
</if>
<if
test=
"uploadType != null"
>
upload_type = #{uploadType},
</if>
<if
test=
"delFlag != null"
>
del_flag = #{delFlag},
</if>
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment