Commit 8504f3a0 by tangyi

重构exam-service

parent eaa21964
Version v3.0.0 (2019-6-19)
--------------------------
* 重构exam-service
* 支持简答题和批改功能
* 一些优化
Version v3.0.0 (2019-6-9)
--------------------------
......
## spring-microservice-exam
- 在线体验-前台:[http://148.70.88.252](http://148.70.88.252)
- 在线体验-后台:[http://148.70.88.252:81](http://148.70.88.252:81)
交流QQ群:
<a target="_blank" href="https://jq.qq.com/?_wv=1027&k=5RKZNF2"><img border="0" src="http://pub.idqqimg.com/wpa/images/group.png" alt="Spring Cloud考试系统学习" title="Spring Cloud考试系统学习"></a>
群号:996208878
如果您觉得有帮助,请点右上角 "Star" 或者项目底部的“捐助”支持一下,谢谢!
项目源码地址
- 前台ui:[spring-microservice-exam-web](https://gitee.com/wells2333/spring-microservice-exam-web.git)
- 后台ui:[spring-microservice-exam-ui](https://gitee.com/wells2333/spring-microservice-exam-ui.git)
- 后端:[spring-microservice-exam](https://gitee.com/wells2333/spring-microservice-exam.git)
## 简介
- 重写[spring-cloud-online-exam](https://gitee.com/wells2333/spring-cloud-online-exam)
......@@ -28,6 +8,12 @@
- 后端基于`spring boot``spring cloud``MySQL`等技术实现权限管理、考试管理等功能。
### 在线体验
- 前台:[http://118.25.138.130](http://118.25.138.130)
- 后台:[http://118.25.138.130:81](http://118.25.138.130:81)
默认账号:
单位ID:gitee
......@@ -36,6 +22,24 @@
2. 学生:student/123456
3. 教师:teacher/123456
交流QQ群:<a target="_blank" href="https://jq.qq.com/?_wv=1027&k=5RKZNF2"><img border="0" src="http://pub.idqqimg.com/wpa/images/group.png" alt="Spring Cloud考试系统学习" title="Spring Cloud考试系统学习"></a>
QQ群号:996208878
如果您觉得有帮助,请点右上角 "Star" 或者项目底部的“捐助”支持一下,谢谢!
### 部署文档
- [在线考试系统部署文档](docs/在线考试系统V2.0_部署文档.md)
- [基于docker-compose部署](docs/在线考试系统V2.0_部署文档(docker版).md)
### 源码地址
- 前台ui:[spring-microservice-exam-web](https://gitee.com/wells2333/spring-microservice-exam-web.git)
- 后台ui:[spring-microservice-exam-ui](https://gitee.com/wells2333/spring-microservice-exam-ui.git)
- 后端:[spring-microservice-exam](https://gitee.com/wells2333/spring-microservice-exam.git)
## 技术选型
- 服务注册与发现:`Consul`
......@@ -65,7 +69,7 @@
## 系统架构
![image](doc/产品设计/系统架构图v3.0.jpg)
![image](docs/产品设计/系统架构图v3.0.jpg)
## 功能概述
......@@ -75,9 +79,9 @@
后台管理分为:系统管理、系统监控、考务管理、附件管理、个人管理
![image](doc/产品设计/功能概述.png)
![image](docs/产品设计/功能概述.png)
- [在线考试系统V2.0功能概述](doc/在线考试系统V2.0_功能概述.md)
- [在线考试系统V2.0功能概述](docs/在线考试系统V2.0_功能概述.md)
## 功能演示
......@@ -85,58 +89,47 @@
1. 登录
![image](doc/images/image_web_login.png)
![image](docs/images/image_web_login.png)
2. 考试
![image](doc/images/image_web_exam.png)
![image](docs/images/image_web_exam.png)
![image](doc/images/image_web_text_subject.png)
![image](docs/images/image_web_text_subject.png)
3. 答题卡
![image](doc/images/image_web_exam_card.png)
![image](docs/images/image_web_exam_card.png)
4. 查看成绩
![image](doc/images/image_web_exam_score.png)
![image](docs/images/image_web_exam_score.png)
5. 查看错题
![image](doc/images/image_web_incorrect_answer.png)
![image](docs/images/image_web_incorrect_answer.png)
### 后台功能
1. 总体功能
![image](doc/images/image_ui_menu.png)
![image](docs/images/image_ui_menu.png)
2. 考试管理
![image](doc/images/image_ui_exam.png)
![image](docs/images/image_ui_exam.png)
3. 题目管理
![image](doc/images/image_ui_exam_subject.png)
![image](docs/images/image_ui_exam_subject.png)
![image](doc/images/image_ui_subjects_rich_edit.png)
![image](docs/images/image_ui_subjects_rich_edit.png)
4. 题库管理
![image](doc/images/image_ui_subject.png)
![image](docs/images/image_ui_subject.png)
5. 个人资料
![image](doc/images/image_ui_msg.png)
## 部署文档
[在线考试系统部署文档](doc/在线考试系统V2.0_部署文档.md)
[基于docker-compose部署](doc/在线考试系统V2.0_部署文档(docker版).md)
![image](docs/images/image_ui_msg.png)
## 后续
- [ ] 多租户
- [ ] 作业、考试、知识点管理、考试成绩排名、图表展示
- [ ] 学生签到、请假
- [ ] 短信验证码,第三方登录
- [ ] swagger修复
- [ ] 站内信
- [x] jwt只存userId这些关键信息,各服务鉴权需要重新换取token
- [x] 重构部门角色,用户直接绑定角色,部门不关联角色
- [x] 简答题,题库完善,智能组卷,在线学习,成绩排名
- [ ] 简答题,题库完善,智能组卷,在线学习,成绩排名
## 问题反馈
......@@ -146,11 +139,13 @@
## 参考资料
- [在线考试系统V2.0部署文档](doc/在线考试系统V2.0_部署文档.md)
- [在线考试系统V2.0设计文档-数据库设计](docs/在线考试系统V2.0_数据库设计.md)
- [在线考试系统V2.0部署文档](docs/在线考试系统V2.0_部署文档.md)
- [在线考试系统V2.0部署文档(docker版)](doc/在线考试系统V2.0_部署文档(docker版).md)
- [在线考试系统V2.0部署文档(docker版)](docs/在线考试系统V2.0_部署文档(docker版).md)
- [在线考试系统V2.0项目结构说明文档](doc/在线考试系统V2.0_项目结构说明.md)
- [在线考试系统V2.0项目结构说明文档](docs/在线考试系统V2.0_项目结构说明.md)
- [微服务架构下的安全设计方案](http://ehedgehog.net/2019/03/23/%E5%BE%AE%E6%9C%8D%E5%8A%A1%E6%9E%B6%E6%9E%84%E4%B8%8B%E7%9A%84%E5%AE%89%E5%85%A8%E8%AE%BE%E8%AE%A1%E6%96%B9%E6%A1%88/)
......
......@@ -87,8 +87,16 @@ public class ExcelToolUtil {
String[] temp = StringUtils.split(key, ".");
Object obj = dataMap.get(temp[0]);
if (obj != null) {
//映射对象,取得对应的字段值
obj = ReflectionUtil.getFieldValue(obj, temp[1]);
// 映射list
if (temp[1].contains("#")) {
String listIndex = StringUtils.split(temp[1], "#")[1];
obj = ((List) obj).get(Integer.parseInt(listIndex));
// 取得对应的字段值
obj = ReflectionUtil.getFieldValue(obj, temp[2]);
} else {
// 映射对象,取得对应的字段值
obj = ReflectionUtil.getFieldValue(obj, temp[1]);
}
if (obj != null) {
cell.setCellValue(obj.toString());
} else {
......
......@@ -8,6 +8,8 @@ import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;
/**
......@@ -97,9 +99,9 @@ public class MapUtil {
*/
public static <T> List<T> map2Java(Class<T> clazz, List<Map<String, Object>> mapList)
throws InstantiationException, IllegalAccessException {
List<T> list = new ArrayList<T>();
List<T> list = new ArrayList<>();
if (mapList != null && mapList.size() > 0) {
T bean = null;
T bean;
for (Map<String, Object> map : mapList) {
bean = clazz.newInstance();
map2Java(bean, map);
......@@ -107,7 +109,6 @@ public class MapUtil {
}
}
return list;
}
/**
......@@ -118,20 +119,43 @@ public class MapUtil {
*/
public static <T> T map2Java(T javaBean, Map<String, Object> map) {
try {
Map<String, Object> collectionObj = new HashMap<>();
for (String key : map.keySet()) {
Field temFiels = null;
Field temFiels;
if (key.contains(".")) {
//对象中属性类型是对象
// 对象中属性类型是对象
String[] temp = StringUtils.split(key, ".");
temFiels = getDeclaredField(javaBean, temp[0]);
Object o = null;
if (temFiels.getType() == Object.class) {
Object o;
if (Objects.requireNonNull(temFiels).getType().equals(Object.class)) {
o = javaBean.getClass().newInstance();
ReflectionUtil.setFieldValue(o, temp[1], map.get(key));
BeanUtils.copyProperties(javaBean, o);
} else if (Objects.requireNonNull(temFiels).getType().equals(List.class)) {
if (!collectionObj.containsKey(temp[0])) {
o = new ArrayList<>();
collectionObj.put(temp[0], o);
}
o = collectionObj.get(temp[0]);
// 获取f字段的通用类型
Type fc = temFiels.getGenericType();
// 如果不为空并且是泛型参数的类型
if (fc instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) fc;
Type[] types = pt.getActualTypeArguments();
// 只处理一个泛型参数
if (types != null && types.length == 1) {
Object tempObj = ((Class<?>) types[0]).newInstance();
ReflectionUtil.setFieldValue(tempObj, temp[2], map.get(key));
((List) o).add(tempObj);
BeanUtils.setProperty(javaBean, temp[0], o);
}
}
} else {
o = temFiels.getType().newInstance();
ReflectionUtil.setFieldValue(o, temp[1], map.get(key));
BeanUtils.setProperty(javaBean, temp[0], o);
}
ReflectionUtil.setFieldValue(o, temp[1], map.get(key));
BeanUtils.copyProperties(javaBean, o);
} else {
// 属性是普通的数据类型
BeanUtils.copyProperty(javaBean, key, map.get(key));
......@@ -178,9 +202,8 @@ public class MapUtil {
if (javaBeanList == null) {
return null;
}
List<Map<String, Object>> mapList = new ArrayList<Map<String, Object>>();
Map<String, Object> map = null;
List<Map<String, Object>> mapList = new ArrayList<>();
Map<String, Object> map;
for (E javaBean : javaBeanList) {
if (javaBean != null) {
map = java2Map(javaBean);
......@@ -202,11 +225,12 @@ public class MapUtil {
try {
// 获取javaBean属性
BeanInfo beanInfo = Introspector.getBeanInfo(javaBean.getClass());
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
if (propertyDescriptors != null && propertyDescriptors.length > 0) {
String propertyName = null; // javaBean属性名
Object propertyValue = null; // javaBean属性值
// javaBean属性名
String propertyName;
// javaBean属性值
Object propertyValue;
for (PropertyDescriptor pd : propertyDescriptors) {
propertyName = pd.getName();
if (!propertyName.equals("class")) {
......
......@@ -2,7 +2,6 @@ FROM anapsix/alpine-java:8_server-jre_unlimited
MAINTAINER tangyi(1633736729@qq.com)
ADD ["target/config-service.jar", "app.jar"]
EXPOSE 8769
ENV JAVA_OPTS="-Xdebug -Dspring.profiles.active=native -jar -Xmn128m -Xms128m"
ENV JAVA_OPTS="-Xmx512m -Xms256m"
RUN sh -c 'touch /app.jar'
HEALTHCHECK CMD curl -f http://localhost:8769/actuator/health || exit 1
ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -jar /app.jar" ]
\ No newline at end of file
......@@ -91,6 +91,7 @@ ignore:
- /v1/attachment/download
- /v1/log/**
- /authentication/**
- /v1/authentication/**
- /**/*.css
- /**/*.js
- /social
......
......@@ -115,6 +115,7 @@ ignore:
- /v1/attachment/download
- /v1/log/**
- /authentication/**
- /v1/authentication/**
- /**/*.css
- /**/*.js
- /social
......
......@@ -65,16 +65,20 @@ swagger:
# 演示环境
preview:
enabled: ${PREVIEW_ENABLED:false}
enabled: ${PREVIEW_ENABLED:true}
ignores:
- token
- start
- saveAndNext
- api/auth # 授权服务
- resetPassword # 重置密码
- register
- submit
- checkExist
- updateInfo
- attachment
- api/exam # 考试服务
# 开启网关token转换
gateway:
token-transfer:
enabled: ${GATEWAY_TOKEN_TRANSFER:true}
# 集群ID生成配置
cluster:
......
......@@ -89,11 +89,11 @@ fdfs:
width: 150
height: 150
tracker-list: #TrackerList参数,支持多个
- ${FDFS_HOST:192.168.0.95}:${FDFS_PORT:22122}
- ${FDFS_HOST:localhost}:${FDFS_PORT:22122}
# 系统配置
sys:
fdfsHttpHost: ${ATTACHMENT_HOST:http://192.168.0.95}:${ATTACHMENT_PORT:8080} # fastDfs的HTTP配置
fdfsHttpHost: ${ATTACHMENT_HOST:http://localhost}:${ATTACHMENT_PORT:8080} # fastDfs的HTTP配置
uploadUrl: api/user/v1/attachment/upload
defaultAvatar: https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif?imageView2/1/w/80/h/80
......@@ -140,6 +140,7 @@ ignore:
- /v1/attachment/download
- /v1/log/**
- /authentication/**
- /v1/authentication/**
- /**/*.css
- /**/*.js
- /social
......
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -70,7 +70,7 @@ services:
# 配置中心
# ---------------------------
config-service:
image: registry.cn-hangzhou.aliyuncs.com/spring-microservice-exam/config-service:3.0.0
image: registry.cn-hangzhou.aliyuncs.com/spring-microservice-exam/config-service:latest
container_name: config-service
env_file: docker-compose.env # 从文件中获取配置
restart: always
......
......@@ -4,7 +4,7 @@ services:
# 前台
# ---------------------------
spring-microservice-exam-web:
image: registry.cn-hangzhou.aliyuncs.com/spring-microservice-exam/spring-microservice-exam-web:3.0.0
image: registry.cn-hangzhou.aliyuncs.com/spring-microservice-exam/spring-microservice-exam-web:latest
volumes:
# 挂载nginx的配置文件
- ./nginx.conf:/etc/nginx/nginx.conf
......@@ -20,7 +20,7 @@ services:
# 后台
# ---------------------------
spring-microservice-exam-ui:
image: registry.cn-hangzhou.aliyuncs.com/spring-microservice-exam/spring-microservice-exam-ui:3.0.0
image: registry.cn-hangzhou.aliyuncs.com/spring-microservice-exam/spring-microservice-exam-ui:latest
volumes:
# 挂载nginx的配置文件
- ./nginx.conf:/etc/nginx/nginx.conf
......
......@@ -4,7 +4,7 @@ services:
# api网关
# ---------------------------
gateway-service:
image: registry.cn-hangzhou.aliyuncs.com/spring-microservice-exam/gateway-service:3.0.0
image: registry.cn-hangzhou.aliyuncs.com/spring-microservice-exam/gateway-service:latest
container_name: gateway-service
env_file: docker-compose.env # 从文件中获取配置
restart: always
......@@ -17,7 +17,7 @@ services:
# 授权服务
# ---------------------------
auth-service:
image: registry.cn-hangzhou.aliyuncs.com/spring-microservice-exam/auth-service:3.0.0
image: registry.cn-hangzhou.aliyuncs.com/spring-microservice-exam/auth-service:latest
container_name: auth-service
env_file: docker-compose.env # 从文件中获取配置
restart: always
......@@ -30,7 +30,7 @@ services:
# 用户服务
# ---------------------------
user-service:
image: registry.cn-hangzhou.aliyuncs.com/spring-microservice-exam/user-service:3.0.0
image: registry.cn-hangzhou.aliyuncs.com/spring-microservice-exam/user-service:latest
container_name: user-service
env_file: docker-compose.env # 从文件中获取配置
restart: always
......@@ -43,7 +43,7 @@ services:
# 考试服务
# ---------------------------
exam-service:
image: registry.cn-hangzhou.aliyuncs.com/spring-microservice-exam/exam-service:3.0.0
image: registry.cn-hangzhou.aliyuncs.com/spring-microservice-exam/exam-service:latest
container_name: exam-service
env_file: docker-compose.env # 从文件中获取配置
restart: always
......@@ -56,7 +56,7 @@ services:
# 监控服务
# ---------------------------
monitor-service:
image: registry.cn-hangzhou.aliyuncs.com/spring-microservice-exam/monitor-service:3.0.0
image: registry.cn-hangzhou.aliyuncs.com/spring-microservice-exam/monitor-service:latest
container_name: monitor-service
env_file: docker-compose.env # 从文件中获取配置
restart: always
......
// 租户标识,默认gitee
# JVM参数
# 可通过docker-compose -f docker-compose-services.yml config命令查看是否生效
JAVA_OPTS=-Xmx512m -Xms256m
# 租户标识,默认gitee
TENANT_CODE=gitee
// 环境配置
# 网关token转换
GATEWAY_TOKEN_TRANSFER=true
# 环境配置
SPRING_PROFILES_ACTIVE=native
// consul配置
# consul配置
CONSUL_HOST=consul
CONSUL_PORT=8500
// rabbitMq配置
# rabbitMq配置
RABBIT_HOST=rabbit
RABBIT_PORT=5672
RABBITMQ_DEFAULT_USER=guest
RABBITMQ_DEFAULT_PASS=guest
// Redis配置
# Redis配置
REDIS_HOST=redis
REDIS_PORT=6379
// 数据库配置
# 数据库配置
MYSQL_HOST=localhost
MYSQL_PORT=3306
MYSQL_USERNAME=platform
MYSQL_PASSWORD=11
// FDFS配置
# FDFS配置
FDFS_HOST=localhost
FDFS_PORT=22122
// 附件服务器配置
ATTACHMENT_HOST=http://localhost
# 附件服务器配置
ATTACHMENT_HOST=http:#localhost
ATTACHMENT_PORT=80
// 各服务host配置
# 各服务host配置
CONFIG_SERVICE_HOST=config-service
AUTH_SERVICE_HOST=auth-service
EXAM_SERVICE_HOST=exam-service
......@@ -40,22 +47,22 @@ GATEWAY_SERVICE_HOST=gateway-service
MONITOR_SERVICE_HOST=monitor-service
USER_SERVICE_HOST=user-service
// 服务监控配置
# 服务监控配置
ADMIN_HOST=monitor-service
ADMIN_PORT=8085
ADMIN_USERNAME=admin
ADMIN_PASSWORD=11
// zipkin配置
# zipkin配置
ZIPKIN_HOST=localhost
ZIPKIN_PORT=9411
// 演示环境
# 演示环境
PREVIEW_ENABLED=false
// ID生成配置
# ID生成配置
CLUSTER_WORK_ID=1
CLUSTER_DATA_CENTER_ID=1
// 时区设置,否则容器里的时间和主机时间差8小时
# 时区设置,否则容器里的时间和主机时间差8小时
TZ=Asia/Shanghai
\ No newline at end of file
......@@ -5,7 +5,7 @@ DOCKERHOME=/spring-microservice-exam
# 镜像名称前缀、标签
BASE_IMAGE_NAME=registry.cn-hangzhou.aliyuncs.com/spring-microservice-exam
BSEE_IMAGE_TAG=3.0.0
BSEE_IMAGE_TAG=latest
# 各服务的镜像名称
CONFIG_SERVICE=$BASE_IMAGE_NAME/config-service:$BSEE_IMAGE_TAG
......
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
#### 考试服务数据库ER图
![image](产品设计/数据库ER关系图.jpg)
#### 考试服务数据库PDM
![image](产品设计/数据库PDM.jpg)
如有问题欢迎指出!
\ No newline at end of file
主要介绍如何基于docker、docker-compose部署后端项目、前端项目,主要的步骤是本地构建镜像,推送的远程仓库,远程服务器从远程仓库拉去镜像运行
主要介绍如何基于docker、docker-compose部署后端项目、前端项目,主要的步骤是本地构建镜像,推送的远程仓库,远程服务器从远程仓库拉去镜像运行
......@@ -10,11 +10,11 @@
MySQL需要单独安装,创建数据库和执行初始化脚本:
`/doc/deploy/mysql/microservice-user.sql`
`/docs/deploy/mysql/microservice-user.sql`
`/doc/deploy/mysql/microservice-exam.sql`
`/docs/deploy/mysql/microservice-exam.sql`
`/doc/deploy/mysql/microservice-auth.sql`
`/docs/deploy/mysql/microservice-auth.sql`
## 安装docker、docker-compose
......@@ -32,7 +32,7 @@ windows系统安装`Docker for Windows`
将源码目录下的`docker-compose.env``docker-compose-base.yml``docker-compose-services.yml``docker-compose-nginx.yml``nginx.conf`上传到服务器的`/spring-microservice-exam/`目录下
将启动脚本`doc/deploy/start.sh`上传到服务器的`/spring-microservice-exam/`目录下
将启动脚本`docs/deploy/start.sh`上传到服务器的`/spring-microservice-exam/`目录下
目录结构:
......
......@@ -41,11 +41,11 @@ fastDfs的IP和端口号:
2. 运行数据库初始化脚本:
`/doc/deploy/mysql/microservice-user.sql`
`/docs/deploy/mysql/microservice-user.sql`
`/doc/deploy/mysql/microservice-exam.sql`
`/docs/deploy/mysql/microservice-exam.sql`
`/doc/deploy/mysql/microservice-auth.sql`
`/docs/deploy/mysql/microservice-auth.sql`
#### 启动后端项目
......
......@@ -2,6 +2,6 @@ FROM anapsix/alpine-java:8_server-jre_unlimited
MAINTAINER tangyi(1633736729@qq.com)
ADD ["target/gateway-service.jar", "app.jar"]
EXPOSE 8000
ENV JAVA_OPTS="-Xdebug -Dspring.profiles.active=native -jar -Xmn128m -Xms128m"
ENV JAVA_OPTS="-Xmx512m -Xms256m"
RUN sh -c 'touch /app.jar'
ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -jar /app.jar" ]
\ No newline at end of file
package com.github.tangyi.gateway;
import com.github.tangyi.gateway.annotation.EnableGatewayTokenTransfer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
......@@ -11,6 +12,7 @@ import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@ComponentScan(basePackages = {"com.github.tangyi"})
@EnableCircuitBreaker
@EnableGatewayTokenTransfer
public class GatewayServiceApplication {
public static void main(String[] args) {
......
package com.github.tangyi.gateway.annotation;
import com.github.tangyi.gateway.config.GatewayTokenTransferConfig;
import org.springframework.context.annotation.Import;
import java.lang.annotation.*;
/**
* 因为token里带有权限信息,避免直接返回给前端,所以在网关做一层Token转换,返回给前端的只是token ID
*
* @author tangyi
* @date 2019/6/19 01:35
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(GatewayTokenTransferConfig.class)
public @interface EnableGatewayTokenTransfer {
}
package com.github.tangyi.gateway.config;
import com.github.tangyi.gateway.filters.TokenRequestGlobalFilter;
import com.github.tangyi.gateway.filters.TokenResponseGlobalFilter;
import com.github.tangyi.gateway.properties.GatewayTokenTransferProperties;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
/**
* 网关Token转换配置
* 开启需要配置gateway.token-transfer.enabled=true
*
* @author tangyi
* @date 2019/6/19 01:36
*/
@Configuration
@EnableConfigurationProperties(GatewayTokenTransferProperties.class)
@ConditionalOnProperty(value = "gateway.token-transfer.enabled", matchIfMissing = true)
public class GatewayTokenTransferConfig {
@Bean
public TokenRequestGlobalFilter tokenRequestGlobalFilter(RedisTemplate redisTemplate) {
return new TokenRequestGlobalFilter(redisTemplate);
}
@Bean
public TokenResponseGlobalFilter tokenResponseGlobalFilter(RedisTemplate redisTemplate) {
return new TokenResponseGlobalFilter(redisTemplate);
}
}
......@@ -35,16 +35,39 @@ public class PreviewFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 当前请求
ServerHttpRequest request = exchange.getRequest();
// enabled为false、GET请求、忽略的URL,直接向下执行
log.trace("preview.enabled:{}", previewConfig.isEnabled());
if (!previewConfig.isEnabled() || StrUtil.equalsIgnoreCase(request.getMethodValue(), HttpMethod.GET.name()) || isIgnore(request.getURI().getPath()))
return chain.filter(exchange);
log.warn("演示环境不能操作,{},{}", request.getMethodValue(), request.getURI().getPath());
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.LOCKED);
return response.setComplete();
if (shouldFilter(request)) {
log.warn("演示环境不能操作,{},{}", request.getMethodValue(), request.getURI().getPath());
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.LOCKED);
return response.setComplete();
}
return chain.filter(exchange);
}
/**
* 是否拦截
*
* @param request request
* @return boolean
* @author tangyi
* @date 2019/06/19 20:06
*/
private boolean shouldFilter(ServerHttpRequest request) {
// enabled为false
if (!previewConfig.isEnabled())
return false;
String method = request.getMethodValue(), uri = request.getURI().getPath();
// GET请求、POST请求
if (StrUtil.equalsIgnoreCase(method, HttpMethod.GET.name()))
return false;
if (StrUtil.equalsIgnoreCase(method, HttpMethod.POST.name()) && !StrUtil.containsIgnoreCase(uri, "delete"))
return false;
// 拦截DELETE请求
if (StrUtil.equalsIgnoreCase(method, HttpMethod.DELETE.name()) && !StrUtil.containsIgnoreCase(uri, "attachment"))
return true;
// URL白名单
return !isIgnore(uri);
}
/**
......
......@@ -2,7 +2,6 @@ package com.github.tangyi.gateway.filters;
import cn.hutool.core.util.StrUtil;
import com.github.tangyi.common.core.constant.CommonConstant;
import com.github.tangyi.common.core.exceptions.CommonException;
import com.github.tangyi.common.core.exceptions.InvalidAccessTokenException;
import com.github.tangyi.common.core.utils.JsonMapper;
import com.github.tangyi.gateway.constants.GatewayConstant;
......@@ -12,7 +11,6 @@ import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.reactivestreams.Publisher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
......@@ -23,7 +21,6 @@ import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
......@@ -40,7 +37,6 @@ import java.util.concurrent.TimeUnit;
* @date 2019/5/19 15:03
*/
@Slf4j
@Component
public class TokenRequestGlobalFilter implements GlobalFilter, Ordered {
private final RedisTemplate<String, String> redisTemplate;
......@@ -55,7 +51,6 @@ public class TokenRequestGlobalFilter implements GlobalFilter, Ordered {
*/
private static long REFRESH_TOKEN_EXPIRE = 60 * 60 * 24 * 30;
@Autowired
public TokenRequestGlobalFilter(RedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
......
......@@ -6,13 +6,11 @@ import com.github.tangyi.common.core.exceptions.InvalidAccessTokenException;
import com.github.tangyi.gateway.constants.GatewayConstant;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Mono;
......@@ -27,12 +25,10 @@ import java.net.URI;
* @date 2019/5/19 15:03
*/
@Slf4j
@Component
public class TokenResponseGlobalFilter implements GlobalFilter, Ordered {
private final RedisTemplate redisTemplate;
@Autowired
public TokenResponseGlobalFilter(RedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
......
......@@ -85,7 +85,7 @@ public class GatewayExceptionHandler implements ErrorWebExceptionHandler {
int code = ResponseBean.FAIL;
if (ex instanceof NotFoundException) {
httpStatus = HttpStatus.NOT_FOUND;
msg = "没有服务提供者.";
msg = "SERVICE_UNAVAILABLE.";
} else if (ex instanceof ResponseStatusException) {
ResponseStatusException responseStatusException = (ResponseStatusException) ex;
httpStatus = responseStatusException.getStatus();
......
package com.github.tangyi.gateway.properties;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* @author tangyi
* @date 2019/6/19 01:55
*/
@ConfigurationProperties("gateway.token-transfer")
public class GatewayTokenTransferProperties {
/**
* 默认开启
*/
private boolean enabled = true;
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}
......@@ -2,6 +2,6 @@ FROM anapsix/alpine-java:8_server-jre_unlimited
MAINTAINER tangyi(1633736729@qq.com)
ADD ["target/monitor-service.jar", "app.jar"]
EXPOSE 8085
ENV JAVA_OPTS="-Xdebug -Dspring.profiles.active=native -jar -Xmn128m -Xms128m"
ENV JAVA_OPTS="-Xmx512m -Xms256m"
RUN sh -c 'touch /app.jar'
ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -jar /app.jar" ]
\ No newline at end of file
......@@ -2,6 +2,6 @@ FROM anapsix/alpine-java:8_server-jre_unlimited
MAINTAINER tangyi(1633736729@qq.com)
ADD ["target/auth-service.jar", "app.jar"]
EXPOSE 8090
ENV JAVA_OPTS="-Xdebug -Dspring.profiles.active=native -jar -Xmn128m -Xms128m"
ENV JAVA_OPTS="-Xmx512m -Xms256m"
RUN sh -c 'touch /app.jar'
ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -jar /app.jar" ]
\ No newline at end of file
......@@ -2,6 +2,6 @@ FROM anapsix/alpine-java:8_server-jre_unlimited
MAINTAINER tangyi(1633736729@qq.com)
ADD ["target/exam-service.jar", "app.jar"]
EXPOSE 8098
ENV JAVA_OPTS="-Xdebug -Dspring.profiles.active=native -jar -Xmn128m -Xms128m"
ENV JAVA_OPTS="-Xmx512m -Xms256m"
RUN sh -c 'touch /app.jar'
ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -jar /app.jar" ]
\ No newline at end of file
......@@ -7,18 +7,25 @@ import com.github.tangyi.common.core.utils.PageUtil;
import com.github.tangyi.common.core.utils.SysUtil;
import com.github.tangyi.common.core.web.BaseController;
import com.github.tangyi.common.log.annotation.Log;
import com.github.tangyi.exam.api.dto.AnswerDto;
import com.github.tangyi.exam.api.dto.SubjectDto;
import com.github.tangyi.exam.api.module.Answer;
import com.github.tangyi.exam.service.AnswerService;
import com.github.tangyi.exam.service.SubjectService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
* 答题controller
......@@ -35,6 +42,8 @@ public class AnswerController extends BaseController {
private final AnswerService answerService;
private final SubjectService subjectService;
/**
* 根据ID获取
*
......@@ -169,7 +178,7 @@ public class AnswerController extends BaseController {
@PostMapping("saveAndNext")
@ApiOperation(value = "保存答题", notes = "保存答题")
@ApiImplicitParam(name = "answer", value = "答题信息", dataType = "Answer")
public ResponseBean<SubjectDto> saveAndNext(@RequestBody @Valid Answer answer) {
public ResponseBean<SubjectDto> saveAndNext(@RequestBody AnswerDto answer) {
return new ResponseBean<>(answerService.saveAndNext(answer));
}
......@@ -188,4 +197,66 @@ public class AnswerController extends BaseController {
public ResponseBean<Boolean> submit(@RequestBody Answer answer) {
return new ResponseBean<>(answerService.submitAsync(answer));
}
/**
* 答题列表,包括题目的详情
* 支持查询正确、错误类型的题目
*
* @param recordId recordId
* @param answer answer
* @return PageInfo
* @author tangyi
* @date 2019/06/18 19:16
*/
@GetMapping("record/{recordId}/answerListInfo")
@ApiOperation(value = "获取答题信息列表", notes = "根据考试记录id获取答题详细信息")
@ApiImplicitParams({
@ApiImplicitParam(name = CommonConstant.PAGE_NUM, value = "分页页码", defaultValue = CommonConstant.PAGE_NUM_DEFAULT, dataType = "String"),
@ApiImplicitParam(name = CommonConstant.PAGE_SIZE, value = "分页大小", defaultValue = CommonConstant.PAGE_SIZE_DEFAULT, dataType = "String"),
@ApiImplicitParam(name = CommonConstant.SORT, value = "排序字段", defaultValue = CommonConstant.PAGE_SORT_DEFAULT, dataType = "String"),
@ApiImplicitParam(name = CommonConstant.ORDER, value = "排序方向", defaultValue = CommonConstant.PAGE_ORDER_DEFAULT, dataType = "String"),
@ApiImplicitParam(name = "recordId", value = "考试记录ID", required = true, dataType = "String", paramType = "path"),
@ApiImplicitParam(name = "answer", value = "答题信息", dataType = "Answer")
})
public PageInfo<AnswerDto> answerListInfo(
@RequestParam(value = CommonConstant.PAGE_NUM, required = false, defaultValue = CommonConstant.PAGE_NUM_DEFAULT) String pageNum,
@RequestParam(value = CommonConstant.PAGE_SIZE, required = false, defaultValue = CommonConstant.PAGE_SIZE_DEFAULT) String pageSize,
@RequestParam(value = CommonConstant.SORT, required = false, defaultValue = CommonConstant.PAGE_SORT_DEFAULT) String sort,
@RequestParam(value = CommonConstant.ORDER, required = false, defaultValue = CommonConstant.PAGE_ORDER_DEFAULT) String order,
@PathVariable String recordId, Answer answer) {
List<AnswerDto> answerDtos = new ArrayList<>();
answer.setExamRecordId(recordId);
PageInfo<Answer> answerPageInfo = answerService.findPage(PageUtil.pageInfo(pageNum, pageSize, sort, order), answer);
if (CollectionUtils.isNotEmpty(answerPageInfo.getList())) {
answerDtos = answerPageInfo.getList().stream().map(tempAnswer -> {
AnswerDto answerDto = new AnswerDto();
BeanUtils.copyProperties(tempAnswer, answerDto);
SubjectDto subjectDto = subjectService.get(tempAnswer.getSubjectId(), tempAnswer.getType());
answerDto.setSubject(subjectDto);
return answerDto;
}).collect(Collectors.toList());
}
PageInfo<AnswerDto> answerDtoPageInfo = new PageInfo<>();
answerDtoPageInfo.setList(answerDtos);
answerDtoPageInfo.setTotal(answerPageInfo.getTotal());
answerDtoPageInfo.setPageNum(answerPageInfo.getPageNum());
answerDtoPageInfo.setPageSize(answerPageInfo.getPageSize());
return answerDtoPageInfo;
}
/**
* 答题详情
*
* @param recordId recordId
* @param answer answer
* @return ResponseBean
* @author tangyi
* @date 2019/06/18 22:50
*/
@GetMapping("record/{recordId}/answerInfo")
@ApiOperation(value = "保存答题", notes = "保存答题")
@ApiImplicitParam(name = "answer", value = "答题信息", dataType = "Answer")
public ResponseBean<AnswerDto> findBySerialNumber(@PathVariable String recordId, AnswerDto answer) {
return new ResponseBean<>(answerService.findBySerialNumber(recordId, answer));
}
}
......@@ -9,6 +9,7 @@ import com.github.tangyi.common.core.web.BaseController;
import com.github.tangyi.common.log.annotation.Log;
import com.github.tangyi.common.security.constant.SecurityConstant;
import com.github.tangyi.exam.api.dto.ExaminationDto;
import com.github.tangyi.exam.api.dto.SubjectDto;
import com.github.tangyi.exam.api.module.Course;
import com.github.tangyi.exam.api.module.Examination;
import com.github.tangyi.exam.service.CourseService;
......@@ -113,6 +114,38 @@ public class ExaminationController extends BaseController {
}
/**
* 根据考试ID获取题目分页数据
*
* @param pageNum pageNum
* @param pageSize pageSize
* @param sort sort
* @param order order
* @param subjectDto subjectDto
* @return PageInfo
* @author tangyi
* @date 2019/6/16 15:45
*/
@RequestMapping("/{examinationId}/subjectList")
@ApiOperation(value = "获取题目列表")
@ApiImplicitParams({
@ApiImplicitParam(name = CommonConstant.PAGE_NUM, value = "分页页码", defaultValue = CommonConstant.PAGE_NUM_DEFAULT, dataType = "String"),
@ApiImplicitParam(name = CommonConstant.PAGE_SIZE, value = "分页大小", defaultValue = CommonConstant.PAGE_SIZE_DEFAULT, dataType = "String"),
@ApiImplicitParam(name = CommonConstant.SORT, value = "排序字段", defaultValue = CommonConstant.PAGE_SORT_DEFAULT, dataType = "String"),
@ApiImplicitParam(name = CommonConstant.ORDER, value = "排序方向", defaultValue = CommonConstant.PAGE_ORDER_DEFAULT, dataType = "String"),
@ApiImplicitParam(name = "examinationId", value = "考试ID", required = true, dataType = "String", paramType = "path"),
@ApiImplicitParam(name = "subjectDto", value = "题目信息", dataType = "SubjectDto")
})
public PageInfo<SubjectDto> subjectList(@RequestParam(value = CommonConstant.PAGE_NUM, required = false, defaultValue = CommonConstant.PAGE_NUM_DEFAULT) String pageNum,
@RequestParam(value = CommonConstant.PAGE_SIZE, required = false, defaultValue = CommonConstant.PAGE_SIZE_DEFAULT) String pageSize,
@RequestParam(value = CommonConstant.SORT, required = false, defaultValue = CommonConstant.PAGE_SORT_DEFAULT) String sort,
@RequestParam(value = CommonConstant.ORDER, required = false, defaultValue = CommonConstant.PAGE_ORDER_DEFAULT) String order,
@PathVariable String examinationId, SubjectDto subjectDto) {
subjectDto.setExaminationId(examinationId);
return examinationService.findSubjectPageById(subjectDto, pageNum, pageSize, sort, order);
}
/**
* 创建
*
* @param examinationDto examinationDto
......@@ -149,7 +182,8 @@ public class ExaminationController extends BaseController {
public ResponseBean<Boolean> updateExamination(@RequestBody @Valid ExaminationDto examinationDto) {
Examination examination = new Examination();
BeanUtils.copyProperties(examinationDto, examination);
examination.setCourseId(examinationDto.getCourse().getId());
if (examinationDto.getCourse() != null)
examination.setCourseId(examinationDto.getCourse().getId());
examination.setCommonValue(SysUtil.getUser(), SysUtil.getSysCode(), SysUtil.getTenantCode());
return new ResponseBean<>(examinationService.update(examination) > 0);
}
......@@ -221,4 +255,20 @@ public class ExaminationController extends BaseController {
examination.setCommonValue(SysUtil.getUser(), SysUtil.getSysCode(), tenantCode);
return new ResponseBean<>(examinationService.findExaminationCount(examination));
}
/**
* 根据考试ID查询题目数量
*
* @param examinationId examinationId
* @return ResponseBean
* @author tangyi
* @date 2019/06/18 14:31
*/
@ApiImplicitParam(name = "examinationId", value = "考试ID", required = true, paramType = "path")
@GetMapping("/{examinationId}/subjectCount")
public ResponseBean<Integer> findExaminationSubjectCount(@PathVariable String examinationId) {
Examination examination = new Examination();
examination.setId(examinationId);
return new ResponseBean<>(examinationService.findSubjectCount(examination));
}
}
package com.github.tangyi.exam.controller;
import com.github.pagehelper.PageInfo;
import com.github.tangyi.common.core.constant.CommonConstant;
import com.github.tangyi.common.core.model.ResponseBean;
import com.github.tangyi.common.core.utils.PageUtil;
import com.github.tangyi.common.core.utils.SysUtil;
import com.github.tangyi.common.core.web.BaseController;
import com.github.tangyi.common.log.annotation.Log;
import com.github.tangyi.exam.api.dto.IncorrectAnswerDto;
import com.github.tangyi.exam.api.module.IncorrectAnswer;
import com.github.tangyi.exam.api.module.Subject;
import com.github.tangyi.exam.service.IncorrectAnswerService;
import com.github.tangyi.exam.service.SubjectService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import java.util.ArrayList;
import java.util.List;
/**
* 错题controller
*
* @author tangyi
* @date 2018/11/8 21:28
*/
@Slf4j
@AllArgsConstructor
@Api("错题信息管理")
@RestController
@RequestMapping("/v1/incorrectAnswer")
public class IncorrectAnswerController extends BaseController {
private final IncorrectAnswerService incorrectAnswerService;
private final SubjectService subjectService;
/**
* 根据ID获取
*
* @param id id
* @return ResponseBean
* @author tangyi
* @date 2018/11/10 21:36
*/
@GetMapping("/{id}")
@ApiOperation(value = "获取错题信息", notes = "根据错题id获取错题详细信息")
@ApiImplicitParam(name = "id", value = "错题ID", required = true, dataType = "String", paramType = "path")
public ResponseBean<IncorrectAnswer> examRecord(@PathVariable String id) {
IncorrectAnswer incorrectAnswer = new IncorrectAnswer();
incorrectAnswer.setId(id);
return new ResponseBean<>(incorrectAnswerService.get(incorrectAnswer));
}
/**
* 获取分页数据
*
* @param pageNum pageNum
* @param pageSize pageSize
* @param sort sort
* @param order order
* @param incorrectAnswer incorrectAnswer
* @return PageInfo
* @author tangyi
* @date 2018/11/10 21:37
*/
@RequestMapping("incorrectAnswerList")
@ApiOperation(value = "获取错题列表")
@ApiImplicitParams({
@ApiImplicitParam(name = CommonConstant.PAGE_NUM, value = "分页页码", defaultValue = CommonConstant.PAGE_NUM_DEFAULT, dataType = "String"),
@ApiImplicitParam(name = CommonConstant.PAGE_SIZE, value = "分页大小", defaultValue = CommonConstant.PAGE_SIZE_DEFAULT, dataType = "String"),
@ApiImplicitParam(name = CommonConstant.SORT, value = "排序字段", defaultValue = CommonConstant.PAGE_SORT_DEFAULT, dataType = "String"),
@ApiImplicitParam(name = CommonConstant.ORDER, value = "排序方向", defaultValue = CommonConstant.PAGE_ORDER_DEFAULT, dataType = "String"),
@ApiImplicitParam(name = "incorrectAnswer", value = "错题信息", dataType = "IncorrectAnswer")
})
public PageInfo<IncorrectAnswerDto> incorrectAnswerList(@RequestParam(value = CommonConstant.PAGE_NUM, required = false, defaultValue = CommonConstant.PAGE_NUM_DEFAULT) String pageNum,
@RequestParam(value = CommonConstant.PAGE_SIZE, required = false, defaultValue = CommonConstant.PAGE_SIZE_DEFAULT) String pageSize,
@RequestParam(value = CommonConstant.SORT, required = false, defaultValue = CommonConstant.PAGE_SORT_DEFAULT) String sort,
@RequestParam(value = CommonConstant.ORDER, required = false, defaultValue = CommonConstant.PAGE_ORDER_DEFAULT) String order,
IncorrectAnswer incorrectAnswer) {
incorrectAnswer.setTenantCode(SysUtil.getTenantCode());
// 查找错题
PageInfo<IncorrectAnswer> incorrectAnswerPageInfo = incorrectAnswerService.findPage(PageUtil.pageInfo(pageNum, pageSize, sort, order), incorrectAnswer);
PageInfo<IncorrectAnswerDto> pageInfo = new PageInfo<>();
List<IncorrectAnswerDto> incorrectAnswerDtoList = new ArrayList<>();
if (CollectionUtils.isNotEmpty(incorrectAnswerPageInfo.getList())) {
Subject subject = new Subject();
// 获取题目ID
subject.setIds(incorrectAnswerPageInfo.getList().stream().map(IncorrectAnswer::getSubjectId).distinct().toArray(String[]::new));
// 查找题目
List<Subject> subjects = subjectService.findListById(subject);
if (CollectionUtils.isNotEmpty(subjects)) {
subjects.forEach(tempSubject -> {
incorrectAnswerPageInfo.getList().stream()
.filter(tempIncorrectAnswer -> tempSubject.getId().equalsIgnoreCase(tempIncorrectAnswer.getSubjectId()))
.forEach(tempIncorrectAnswer -> {
IncorrectAnswerDto incorrectAnswerDto = new IncorrectAnswerDto();
BeanUtils.copyProperties(tempSubject, incorrectAnswerDto);
incorrectAnswerDto.setIncorrectAnswer(tempIncorrectAnswer.getIncorrectAnswer());
incorrectAnswerDtoList.add(incorrectAnswerDto);
});
});
}
}
pageInfo.setPageSize(incorrectAnswerPageInfo.getPageSize());
pageInfo.setPageNum(incorrectAnswerPageInfo.getPageNum());
pageInfo.setTotal(incorrectAnswerPageInfo.getTotal());
pageInfo.setList(incorrectAnswerDtoList);
return pageInfo;
}
/**
* 创建
*
* @param incorrectAnswer incorrectAnswer
* @return ResponseBean
* @author tangyi
* @date 2018/11/10 21:38
*/
@PostMapping
@ApiOperation(value = "创建错题", notes = "创建错题")
@ApiImplicitParam(name = "incorrectAnswer", value = "错题实体incorrectAnswer", required = true, dataType = "IncorrectAnswer")
@Log("新增错题")
public ResponseBean<Boolean> addIncorrectAnswer(@RequestBody @Valid IncorrectAnswer incorrectAnswer) {
incorrectAnswer.setCommonValue(SysUtil.getUser(), SysUtil.getSysCode(), SysUtil.getTenantCode());
return new ResponseBean<>(incorrectAnswerService.insert(incorrectAnswer) > 0);
}
/**
* 更新
*
* @param incorrectAnswer incorrectAnswer
* @return ResponseBean
* @author tangyi
* @date 2018/11/10 21:38
*/
@PutMapping
@ApiOperation(value = "更新错题信息", notes = "根据错题id更新错题的基本信息")
@ApiImplicitParam(name = "incorrectAnswer", value = "错题实体incorrectAnswer", required = true, dataType = "IncorrectAnswer")
@Log("更新错题")
public ResponseBean<Boolean> updateIncorrectAnswer(@RequestBody @Valid IncorrectAnswer incorrectAnswer) {
incorrectAnswer.setCommonValue(SysUtil.getUser(), SysUtil.getSysCode(), SysUtil.getTenantCode());
return new ResponseBean<>(incorrectAnswerService.update(incorrectAnswer) > 0);
}
/**
* 删除
*
* @param id id
* @return ResponseBean
* @author tangyi
* @date 2018/11/10 21:39
*/
@DeleteMapping("{id}")
@ApiOperation(value = "删除错题", notes = "根据ID删除错题")
@ApiImplicitParam(name = "id", value = "错题ID", required = true, paramType = "path")
@Log("删除错题")
public ResponseBean<Boolean> deleteIncorrectAnswer(@PathVariable String id) {
boolean success = false;
try {
IncorrectAnswer incorrectAnswer = incorrectAnswerService.get(id);
if (incorrectAnswer != null) {
incorrectAnswer.setCommonValue(SysUtil.getUser(), SysUtil.getSysCode(), SysUtil.getTenantCode());
success = incorrectAnswerService.delete(incorrectAnswer) > 0;
}
} catch (Exception e) {
log.error("删除错题失败!", e);
}
return new ResponseBean<>(success);
}
}
package com.github.tangyi.exam.enums;
/**
* 题目类型枚举
*
* @author tangyi
* @date 2019/6/16 16:22
*/
public enum SubjectTypeEnum {
/**
* 选择题
*/
CHOICES(0),
/**
* 简答题
*/
SHORT_ANSWER(1);
SubjectTypeEnum(Integer value) {
this.value = value;
}
private Integer value;
/**
* 根据类型返回具体的SubjectType
*
* @param type type
* @return SubjectType
*/
public static SubjectTypeEnum match(Integer type) {
for (SubjectTypeEnum item : SubjectTypeEnum.values()) {
if (item.value.equals(type)) {
return item;
}
}
return null;
}
public Integer getValue() {
return value;
}
public void setValue(Integer value) {
this.value = value;
}
}
package com.github.tangyi.exam.error;
import com.github.tangyi.common.core.exceptions.CommonException;
import com.github.tangyi.common.core.model.ResponseBean;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.http.HttpHeaders;
......@@ -7,6 +8,7 @@ import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
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;
......@@ -45,4 +47,15 @@ public class CustomGlobalExceptionHandler extends ResponseEntityExceptionHandler
responseBean.setStatus(status.value());
return new ResponseEntity<>(responseBean, headers, status);
}
/**
* 处理CommonException
*
* @param e e
* @return ResponseEntity
*/
@ExceptionHandler(CommonException.class)
public ResponseEntity<ResponseBean<String>> handleCommonException(Exception e) {
return new ResponseEntity<>(new ResponseBean<>(e.getMessage()), HttpStatus.INTERNAL_SERVER_ERROR);
}
}
package com.github.tangyi.exam.mapper;
import com.github.tangyi.common.core.persistence.CrudMapper;
import com.github.tangyi.exam.api.module.ExamRecord;
import com.github.tangyi.exam.api.module.ExaminationRecord;
import org.apache.ibatis.annotations.Mapper;
/**
......@@ -11,7 +11,7 @@ import org.apache.ibatis.annotations.Mapper;
* @date 2018/11/8 21:12
*/
@Mapper
public interface ExamRecordMapper extends CrudMapper<ExamRecord> {
public interface ExamRecordMapper extends CrudMapper<ExaminationRecord> {
/**
* 根据用户id、考试id查找
......@@ -21,5 +21,5 @@ public interface ExamRecordMapper extends CrudMapper<ExamRecord> {
* @author tangyi
* @date 2018/12/26 13:56
*/
ExamRecord getByUserIdAndExaminationId(ExamRecord examRecord);
ExaminationRecord getByUserIdAndExaminationId(ExaminationRecord examRecord);
}
package com.github.tangyi.exam.mapper;
import com.github.tangyi.common.core.persistence.CrudMapper;
import com.github.tangyi.exam.api.module.ExaminationSubject;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* 考试题目关联mapper
*
* @author tangyi
* @date 2019/6/16 15:37
*/
@Mapper
public interface ExaminationSubjectMapper extends CrudMapper<ExaminationSubject> {
/**
* 根据题目ID删除
*
* @param examinationSubject examinationSubject
* @return int
* @author tangyi
* @date 2019/06/16 21:58
*/
int deleteBySubjectId(ExaminationSubject examinationSubject);
/**
* 根据题目ID查询
*
* @param examinationSubject examinationSubject
* @return List
* @author tangyi
* @date 2019/06/17 12:18
*/
List<ExaminationSubject> findListBySubjectId(ExaminationSubject examinationSubject);
/**
* 查询考试的题目数量
*
* @param examinationSubject examinationSubject
* @return int
* @author tangyi
* @date 2019/06/18 14:37
*/
int findSubjectCount(ExaminationSubject examinationSubject);
/**
* 根据考试ID和题目序号查询
*
* @param examinationSubject examinationSubject
* @return ExaminationSubject
* @author tangyi
* @date 2019/06/18 23:17
*/
ExaminationSubject findByExaminationIdAndSerialNumber(ExaminationSubject examinationSubject);
}
package com.github.tangyi.exam.mapper;
import com.github.tangyi.common.core.persistence.CrudMapper;
import com.github.tangyi.exam.api.module.SubjectBank;
import com.github.tangyi.exam.api.module.Pictures;
import org.apache.ibatis.annotations.Mapper;
/**
* 题库mapper
* 图片mapper
*
* @author tangyi
* @date 2018/12/9 14:10
* @date 2019/6/16 14:50
*/
@Mapper
public interface SubjectBankMapper extends CrudMapper<SubjectBank> {
public interface PicturesMapper extends CrudMapper<Pictures> {
}
package com.github.tangyi.exam.mapper;
import com.github.tangyi.common.core.persistence.CrudMapper;
import com.github.tangyi.exam.api.module.Subject;
import com.github.tangyi.exam.api.module.SubjectChoices;
import org.apache.ibatis.annotations.Mapper;
/**
* 题目Mapper
* 选择题Mapper
*
* @author tangyi
* @date 2018/11/8 21:15
*/
@Mapper
public interface SubjectMapper extends CrudMapper<Subject> {
public interface SubjectChoicesMapper extends CrudMapper<SubjectChoices> {
/**
* 根据考试ID和序号查找
* 根据序号查找
*
* @param subject subject
* @return Subject
* @param subjectChoices subjectChoices
* @return SubjectChoices
* @author tangyi
* @date 2019/01/20 12:19
*/
Subject getByExaminationIdAndSerialNumber(Subject subject);
SubjectChoices getBySerialNumber(SubjectChoices subjectChoices);
/**
* 根据考试ID查询题目数
* 物理删除
*
* @param subject subject
* @param subjectChoices subjectChoices
* @return int
* @author tangyi
* @date 2019/01/23 20:14
* @date 2019/06/16 22:44
*/
int getExaminationTotalSubject(Subject subject);
int physicalDelete(SubjectChoices subjectChoices);
/**
* 物理批量删除
*
* @param ids ids
* @return int
* @author tangyi
* @date 2019/06/16 22:44
*/
int physicalDeleteAll(String[] ids);
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment