소스 검색

merge from release_v4.1.1

deason 3 년 전
부모
커밋
19edb12712

+ 88 - 4
examcloud-core-basic-api-provider/src/main/java/cn/com/qmth/examcloud/core/basic/api/controller/OrgController.java

@@ -63,13 +63,18 @@ import org.springframework.transaction.annotation.Transactional;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.multipart.commons.CommonsMultipartFile;
 
+import javax.imageio.ImageIO;
 import javax.persistence.criteria.Predicate;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
+
+import java.awt.image.BufferedImage;
 import java.io.File;
 import java.io.FileInputStream;
+import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
+import java.math.BigDecimal;
 import java.util.*;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
@@ -86,6 +91,8 @@ import java.util.stream.Stream;
 @Api(tags = "机构相关接口")
 @RequestMapping("${$rmp.ctr.basic}/org")
 public class OrgController extends ControllerSupport {
+	
+	private final static long LOG_FILE_CACHE_AGE=2592000L;
 
     @Autowired
     OrgCache orgCache;
@@ -720,9 +727,10 @@ public class OrgController extends ControllerSupport {
             env.setRootOrgId(orgEntity.getId().toString());
             env.setRootOrgDomain(orgEntity.getDomainName());
             env.setExt1(propertyGroupId);
-            pi = FileStorageUtil.saveFile("orgPropertiesByOrgId", env, file, null);
+            pi = FileStorageUtil.saveFile("orgPropertiesByOrgId", env, file, null,0L);
+            
             urlList.add(pi.getUrl());
-            pi = FileStorageUtil.saveFile("orgPropertiesByOrgDomain", env, file, null);
+            pi = FileStorageUtil.saveFile("orgPropertiesByOrgDomain", env, file, null,0L);
             urlList.add(pi.getUrl());
 
             return urlList;
@@ -1138,6 +1146,9 @@ public class OrgController extends ControllerSupport {
         if (!StringUtils.equals(prop.get("OE_STUDENT_SYS_NAME"), old.get("OE_STUDENT_SYS_NAME"))) {
             sb.append("系统名称,");
         }
+        if (!StringUtils.equals(prop.get("STUDENT_CLIENT_BG_PICTURE_URL"), old.get("STUDENT_CLIENT_BG_PICTURE_URL"))) {
+            sb.append("考生端登录页图片,");
+        }
         if (!StringUtils.equals(prop.get("LOGO_FILE_URL"), old.get("LOGO_FILE_URL"))) {
             sb.append("学校logo,");
         }
@@ -1345,7 +1356,7 @@ public class OrgController extends ControllerSupport {
         FileStoragePathEnvInfo env = new FileStoragePathEnvInfo();
         env.setFileSuffix(fileSuffix);
         env.setRootOrgId(orgEntity.getRootId().toString());
-        YunPathInfo pi = FileStorageUtil.saveFile("orgLogo", env, storeLocation, null);
+        YunPathInfo pi = FileStorageUtil.saveFile("orgLogo", env, storeLocation, null,LOG_FILE_CACHE_AGE);
         String url = pi.getUrl();
 
         OrgPropertyEntity logoFileUrlEntity = orgPropertyRepo.findByOrgIdAndKeyId(orgId,
@@ -1361,6 +1372,79 @@ public class OrgController extends ControllerSupport {
         ReportsUtil.report(new AdminOperateReport(accessUser.getRootOrgId(), accessUser.getUserId(), "考生端配置-上传学校logo", null));
         return url;
     }
+    
+    private void checkPicSize(File image) throws FileNotFoundException, IOException {
+    	BufferedImage sourceImg =ImageIO.read(new FileInputStream(image));
+    	int w=sourceImg.getWidth();
+    	int h=sourceImg.getHeight();
+    	BigDecimal bw = new BigDecimal(w);
+    	BigDecimal bh = new BigDecimal(h);
+    	double ret = bw.divide(bh,1, BigDecimal.ROUND_HALF_UP).doubleValue();
+    	if(ret!=1.5) {
+    		throw new StatusException("图片宽高比例必须是3:2");
+    	}
+    	if(w<1200||w>2000) {
+    		throw new StatusException("图片宽度必须在1200px-2000px");
+    	}
+    }
+    
+    @ApiOperation(value = "导入考生端登录界面图片")
+    @PostMapping("importClientBgPicture/{orgId}")
+    @Transactional
+    public String importClientBgPicture(@PathVariable Long orgId, HttpServletRequest request,
+                             @RequestParam CommonsMultipartFile file) throws IOException {
+
+        OrgEntity orgEntity = GlobalHelper.getEntity(orgRepo, orgId, OrgEntity.class);
+        if (null == orgEntity) {
+            throw new StatusException("140002", "orgEntity is null");
+        }
+
+        validateRootOrgIsolation(orgEntity.getRootId());
+
+        DiskFileItem fileItem = (DiskFileItem) file.getFileItem();
+        File storeLocation = fileItem.getStoreLocation();
+        String name = file.getOriginalFilename();
+
+        if (1*1024*1024 < storeLocation.length()) {
+            throw new StatusException("140082", "文件过大");
+        }
+
+        String fileSuffix = null;
+        if (name.endsWith(".jpg")) {
+            fileSuffix = ".jpg";
+        } else if (name.endsWith(".jpeg")) {
+            fileSuffix = ".jpeg";
+        } else if (name.endsWith(".png")) {
+            fileSuffix = ".png";
+        } else {
+            throw new StatusException("101001", "文件格式错误");
+        }
+        checkPicSize(storeLocation);
+
+        DynamicEnumManager manager = OrgProperty.getDynamicEnumManager();
+        DynamicEnum logoFileUrl = manager.getByName("STUDENT_CLIENT_BG_PICTURE_URL");
+
+
+        //通用存储
+        FileStoragePathEnvInfo env = new FileStoragePathEnvInfo();
+        env.setFileSuffix(fileSuffix);
+        env.setRootOrgId(orgEntity.getRootId().toString());
+        YunPathInfo pi = FileStorageUtil.saveFile("client_bg_picture", env, storeLocation, null,LOG_FILE_CACHE_AGE);
+        String url = pi.getUrl();
+
+        OrgPropertyEntity logoFileUrlEntity = orgPropertyRepo.findByOrgIdAndKeyId(orgId,
+                logoFileUrl.getId());
+        if (null == logoFileUrlEntity) {
+            logoFileUrlEntity = new OrgPropertyEntity();
+            logoFileUrlEntity.setKeyId(logoFileUrl.getId());
+            logoFileUrlEntity.setOrgId(orgId);
+        }
+        logoFileUrlEntity.setValue(url);
+        orgPropertyRepo.save(logoFileUrlEntity);
+        User accessUser = getAccessUser();
+        ReportsUtil.report(new AdminOperateReport(accessUser.getRootOrgId(), accessUser.getUserId(), "考生端配置-上传考生端登录界面图片", null));
+        return url;
+    }
 
     @ApiOperation(value = "导入离线考试答题纸", notes = "导入离线考试答题纸")
     @PostMapping("importAnswers/{orgId}")
@@ -1526,7 +1610,7 @@ public class OrgController extends ControllerSupport {
         FileStoragePathEnvInfo env = new FileStoragePathEnvInfo();
         env.setFileSuffix(fileSuffix);
         env.setRootOrgId(orgEntity.getRootId().toString());
-        YunPathInfo pi = FileStorageUtil.saveFile("orgLogo", env, storeLocation, null);
+        YunPathInfo pi = FileStorageUtil.saveFile("orgLogo", env, storeLocation, null,LOG_FILE_CACHE_AGE);
         String url = pi.getUrl();
 
         OrgPropertyEntity logoFileUrlEntity = orgPropertyRepo.findByOrgIdAndKeyId(orgId,

+ 23 - 0
examcloud-core-basic-api-provider/src/main/java/cn/com/qmth/examcloud/core/basic/api/controller/VerifyCodeController.java

@@ -5,6 +5,7 @@ import cn.com.qmth.examcloud.commons.exception.StatusException;
 import cn.com.qmth.examcloud.core.basic.dao.enums.LoginRuleType;
 import cn.com.qmth.examcloud.core.basic.service.LoginRuleService;
 import cn.com.qmth.examcloud.core.basic.service.VerifyCodeService;
+import cn.com.qmth.examcloud.core.basic.service.bean.ClientLoginInfo;
 import cn.com.qmth.examcloud.core.basic.service.bean.GeetestLoginInfo;
 import cn.com.qmth.examcloud.starters.greetest.model.RegisterReq;
 import cn.com.qmth.examcloud.starters.greetest.model.RegisterResp;
@@ -47,6 +48,28 @@ public class VerifyCodeController extends ControllerSupport {
         return geetestService.register(req);
     }
 
+    @Naked
+    @WithoutStackTrace
+    @ApiOperation(value = "客户端登录接口")
+    @PostMapping(value = "/api/ecs_core/client/login")
+    public StatusResponseX<User> clientLogin(@RequestBody ClientLoginInfo info, HttpServletRequest request) {
+        setAlwaysOKResponse();
+
+        if (info.getRootOrgId() == null) {
+            throw new StatusException("400", "顶级机构ID不能为空");
+        }
+
+        GeetestLoginInfo loginInfo = new GeetestLoginInfo();
+        loginInfo.setRootOrgId(info.getRootOrgId());
+        loginInfo.setAccountType(info.getAccountType());
+        loginInfo.setAccountValue(info.getAccountValue());
+        loginInfo.setPassword(info.getPassword());
+        loginInfo.setIp_address(super.getIp(request));
+
+        User user = verifyCodeService.geetestLogin(loginInfo);
+        return new StatusResponseX<>(user);
+    }
+
     @Naked
     @WithoutStackTrace
     @ApiOperation(value = "极验-验证码登录接口")

+ 1 - 1
examcloud-core-basic-api-provider/src/main/java/cn/com/qmth/examcloud/core/basic/api/provider/CourseCloudServiceProvider.java

@@ -140,7 +140,7 @@ public class CourseCloudServiceProvider implements CourseCloudService {
         for (Long cur : courseIdList) {
             CourseEntity c = GlobalHelper.getEntity(courseRepo, cur, CourseEntity.class);
             if (null == c) {
-                throw new StatusException("160001", "no course [id=" + cur + "]");
+                throw new StatusException("160001", "课程不存在!" + cur);
             }
             CourseBean courseBean = new CourseBean();
             courseBean.setId(c.getId());

+ 57 - 0
examcloud-core-basic-service/src/main/java/cn/com/qmth/examcloud/core/basic/service/bean/ClientLoginInfo.java

@@ -0,0 +1,57 @@
+package cn.com.qmth.examcloud.core.basic.service.bean;
+
+import cn.com.qmth.examcloud.api.commons.exchange.JsonSerializable;
+import io.swagger.annotations.ApiModelProperty;
+
+/**
+ * 登陆信息
+ */
+public class ClientLoginInfo implements JsonSerializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty("顶级机构ID")
+    private Long rootOrgId;
+
+    @ApiModelProperty("登陆账号类型")
+    private String accountType;
+
+    @ApiModelProperty("登陆账号")
+    private String accountValue;
+
+    @ApiModelProperty("登陆密码")
+    private String password;
+
+    public Long getRootOrgId() {
+        return rootOrgId;
+    }
+
+    public void setRootOrgId(Long rootOrgId) {
+        this.rootOrgId = rootOrgId;
+    }
+
+    public String getAccountType() {
+        return accountType;
+    }
+
+    public void setAccountType(String accountType) {
+        this.accountType = accountType;
+    }
+
+    public String getAccountValue() {
+        return accountValue;
+    }
+
+    public void setAccountValue(String accountValue) {
+        this.accountValue = accountValue;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public void setPassword(String password) {
+        this.password = password;
+    }
+
+}

+ 13 - 1
examcloud-core-basic-service/src/main/java/cn/com/qmth/examcloud/core/basic/service/impl/AdminOperateServiceImpl.java

@@ -58,7 +58,7 @@ public class AdminOperateServiceImpl implements AdminOperateService {
 		e.setOperateTime(r.getReportTime());
 		e.setOperateIp(r.getRemoteHost());
 		e.setOperate(r.getOperate());
-		e.setOperateInfo(r.getOperateInfo());
+		e.setOperateInfo(cut(r.getOperateInfo()));
 		e.setCreationTime(new Date());
 		try {
 			adminOperateRepo.save(e);
@@ -66,6 +66,18 @@ public class AdminOperateServiceImpl implements AdminOperateService {
 			// 忽略;
 		}
 	}
+	
+	private String cut(String operateInfo) {
+		if(StringUtils.isEmpty(operateInfo)) {
+			return operateInfo;
+		}
+		int len=operateInfo.length();
+		if(len>1000) {
+			return operateInfo.substring(0, 992)+" ......}";
+		}else {
+			return operateInfo;
+		}
+	}
 
 	@Override
 	public PageInfo<AdminOperateBean> queryPage(AdminOperateQuery req, Integer pageNo, Integer pageSize,

+ 24 - 0
examcloud-core-basic-service/src/main/java/cn/com/qmth/examcloud/core/basic/service/impl/OrgServiceImpl.java

@@ -20,13 +20,17 @@ import cn.com.qmth.examcloud.core.basic.service.bean.datarule.UserDataRuleForm;
 import cn.com.qmth.examcloud.core.basic.service.cache.OrgCache;
 import cn.com.qmth.examcloud.core.basic.service.cache.OrgPropertyCache;
 import cn.com.qmth.examcloud.core.basic.service.cache.RootOrgCache;
+import cn.com.qmth.examcloud.support.enums.FaceBiopsyScheme;
 import cn.com.qmth.examcloud.task.api.DataSyncCloudService;
 import cn.com.qmth.examcloud.task.api.request.SyncOrgReq;
 import cn.com.qmth.examcloud.web.helpers.GlobalHelper;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.collections.MapUtils;
 import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.data.jpa.domain.Specification;
 import org.springframework.stereotype.Service;
@@ -40,6 +44,8 @@ import java.util.stream.Collectors;
 @Service
 public class OrgServiceImpl implements OrgService {
 
+    private static final Logger log = LoggerFactory.getLogger(OrgServiceImpl.class);
+
     @Autowired
     RoleRepo roleRepo;
 
@@ -266,6 +272,24 @@ public class OrgServiceImpl implements OrgService {
 
     @Override
     public void saveOrgProperties(Long orgId, Map<String, String> properties) {
+        if (MapUtils.isNotEmpty(properties)) {
+            String clientEnabled = properties.get("PC_CLIENT_ENABLED");
+            if (StringUtils.isNotEmpty(clientEnabled)) {
+                // 校验客户端所选的活体方案
+                String faceType = properties.get("IDENTIFICATION_OF_LIVING_BODY_SCHEME");
+                log.info("PC_CLIENT_ENABLED = {}, faceType = {}", clientEnabled, faceType);
+
+                if ("true".equalsIgnoreCase(clientEnabled)) {
+                    if (!FaceBiopsyScheme.FACE_CLIENT.getCode().equals(faceType)) {
+                        throw new StatusException("请选择可用的活体检测方案");
+                    }
+                } else {
+                    if (FaceBiopsyScheme.FACE_CLIENT.getCode().equals(faceType)) {
+                        throw new StatusException("请选择可用的活体检测方案");
+                    }
+                }
+            }
+        }
 
         GlobalHelper.getPresentEntity(orgRepo, orgId, OrgEntity.class);
 

+ 4 - 4
examcloud-core-basic-starter/shell/start.sh

@@ -1,13 +1,13 @@
 #!/bin/bash
 
-PROJECT_JAR="examcloud-core-basic-starter-v4.1.0-RELEASE.jar"
-
-PROJECT_JVM_ARGS=`cat start.vmoptions`
+PROJECT_JAR=`find . -name "examcloud-core-basic-starter*.jar"`
+PROJECT_JAR=${PROJECT_JAR:6}
 
 PROJECT_ARGS=`cat start.args`
-
 PROJECT_ARGS=$PROJECT_ARGS" --sys.config.center.secretKey="$1
 
+PROJECT_JVM_ARGS=`cat start.vmoptions`
+
 PID_LIST=`ps -ef | grep $PROJECT_JAR | grep java | awk '{print $2}'`
 if [ ! -z "$PID_LIST" ]; then
     echo "$PROJECT_JAR is already running..."

+ 2 - 1
examcloud-core-basic-starter/shell/stop.sh

@@ -1,6 +1,7 @@
 #!/bin/bash
 
-PROJECT_JAR="examcloud-core-basic-starter-v4.1.0-RELEASE.jar"
+PROJECT_JAR=`find . -name "examcloud-core-basic-starter*.jar"`
+PROJECT_JAR=${PROJECT_JAR:6}
 
 ps -ef | grep $PROJECT_JAR | grep java | awk '{printf("kill -15 %s\n",$2)}' | sh
 BUILD_ID=DONTKILLME

+ 30 - 14
examcloud-core-basic-starter/src/main/java/cn/com/qmth/examcloud/core/basic/starter/config/Swagger2.java

@@ -2,31 +2,47 @@ package cn.com.qmth.examcloud.core.basic.starter.config;
 
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
-
 import springfox.documentation.builders.ApiInfoBuilder;
+import springfox.documentation.builders.ParameterBuilder;
 import springfox.documentation.builders.PathSelectors;
 import springfox.documentation.builders.RequestHandlerSelectors;
+import springfox.documentation.schema.ModelRef;
 import springfox.documentation.service.ApiInfo;
-import springfox.documentation.service.Contact;
+import springfox.documentation.service.Parameter;
 import springfox.documentation.spi.DocumentationType;
 import springfox.documentation.spring.web.plugins.Docket;
 import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;
 
+import java.util.ArrayList;
+import java.util.List;
+
 @Configuration
 @EnableSwagger2WebMvc
 public class Swagger2 {
 
-	@Bean
-	public Docket createRestApi() {
-		return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).select()
-				.apis(RequestHandlerSelectors.basePackage("cn.com.qmth.examcloud.core.basic.api"))
-				.paths(PathSelectors.any()).build();
-	}
+    @Bean
+    public Docket buildDocket() {
+        List<Parameter> parameters = new ArrayList<>();
+        parameters.add(new ParameterBuilder().name("key").modelRef(new ModelRef("String")).parameterType("header").required(false).build());
+        parameters.add(new ParameterBuilder().name("token").modelRef(new ModelRef("String")).parameterType("header").required(false).build());
+
+        return new Docket(DocumentationType.SWAGGER_2)
+                .groupName("default")
+                .apiInfo(buildApiInfo())
+                .globalOperationParameters(parameters)
+                .useDefaultResponseMessages(false)
+                .select()
+                // .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
+                .apis(RequestHandlerSelectors.basePackage("cn.com.qmth.examcloud.core.basic.api.controller"))
+                .paths(PathSelectors.any())
+                .build();
+    }
 
-	private ApiInfo apiInfo() {
-		return new ApiInfoBuilder().title("API doc")
-				.contact(new Contact("qmth", "http://xxxxx/", "")).version("xxx")
-				.description("API文档").build();
-	}
+    public ApiInfo buildApiInfo() {
+        return new ApiInfoBuilder()
+                .title("考试云平台接口文档")
+                .version("v4.x.x")
+                .build();
+    }
 
-}
+}

+ 7 - 1
examcloud-core-basic-starter/src/main/resources/aliyun.xml

@@ -54,5 +54,11 @@
         <maxSize>10M</maxSize>
         <path>/sys_notice/org_${rootOrgId}${fileSuffix}</path>
     </site>
-
+    <site>
+        <id>client_bg_picture</id>
+        <name>考生端登录界面图片</name>
+        <aliyunId>1</aliyunId>
+        <maxSize>1M</maxSize>
+        <path>/client_bg_picture/${rootOrgId}/${timeMillis}${fileSuffix}</path>
+    </site>
 </sites>

+ 2 - 1
examcloud-core-basic-starter/src/main/resources/log4j2.xml

@@ -67,6 +67,7 @@
         <logger name="com.aliyun" level="WARN"/>
         <logger name="io.lettuce" level="WARN"/>
         <logger name="io.netty" level="WARN"/>
+        <logger name="cn.com.qmth.examcloud.starters.greetest" level="INFO"/>
 
         <!--<Logger name="org.hibernate.SQL" level="DEBUG"/>-->
         <!--<Logger name="org.hibernate.engine.transaction" level="DEBUG"/>-->
@@ -80,7 +81,7 @@
             <AppenderRef ref="FILE_APPENDER"/>
         </Logger>
 
-        <Logger name="STUDENT_CLIENT_LOGGER" level="INFO" additivity="false">
+        <Logger name="STUDENT_CLIENT_LOGGER" level="DEBUG" additivity="false">
             <AppenderRef ref="CONSOLE_APPENDER"/>
             <AppenderRef ref="STUDENT_CLIENT_APPENDER"/>
         </Logger>

+ 43 - 1
examcloud-core-basic-starter/src/main/resources/org-properties.xml

@@ -92,7 +92,7 @@
 	</enum>
 	<enum>
 		<id>15</id>
-		<name>APP_ENABLED </name>
+		<name>APP_ENABLED</name>
 		<desc>开放APP</desc>
 		<valueType>BOOLEAN</valueType>
 	</enum>
@@ -162,4 +162,46 @@
 		<desc>机构答题纸模板</desc>
 		<valueType>STRING</valueType>
 	</enum>
+	<enum>
+		<id>27</id>
+		<name>PC_CLIENT_ENABLED</name>
+		<desc>启用C端考生端</desc>
+		<valueType>BOOLEAN</valueType>
+	</enum>
+	<enum>
+		<id>28</id>
+		<name>ACTION_ALERT</name>
+		<desc>指定动作检测提醒N秒后开始检测</desc>
+		<valueType>LONG</valueType>
+	</enum>
+	<enum>
+		<id>29</id>
+		<name>ACTION_NUM</name>
+		<desc>动作个数</desc>
+		<valueType>LONG</valueType>
+	</enum>
+	<enum>
+		<id>30</id>
+		<name>ACTION_OPTIONS</name>
+		<desc>动作选项</desc>
+		<valueType>STRING</valueType>
+	</enum>
+	<enum>
+		<id>31</id>
+		<name>ACTION_ORDER</name>
+		<desc>动作顺序</desc>
+		<valueType>STRING</valueType>
+	</enum>
+	<enum>
+		<id>32</id>
+		<name>ACTION_DURATION</name>
+		<desc>单个动作最大时长</desc>
+		<valueType>LONG</valueType>
+	</enum>
+	<enum>
+		<id>33</id>
+		<name>STUDENT_CLIENT_BG_PICTURE_URL</name>
+		<desc>考生端登录页图片URL</desc>
+		<valueType>STRING</valueType>
+	</enum>
 </enums>

+ 2 - 1
examcloud-core-basic-starter/src/main/resources/security.properties

@@ -10,4 +10,5 @@
 [s][${$rmp.ctr.basic}/org][property/{orgId}/{key}][GET]=true
 [s][${$rmp.ctr.basic}/org][getAnswersUrl/{orgId}][GET]=true
 [s][${$rmp.ctr.basic}/systemProperty][{key}][GET]=true
-[s][${$rmp.ctr.basic}/rolePrivilege][getStudentClientMenu][GET]=true
+[s][${$rmp.ctr.basic}/rolePrivilege][getStudentClientMenu][GET]=true
+[s][${$rmp.ctr.basic}/auth][logout][POST]=true