Bläddra i källkod

音频和机构中心代码

wangliang 2 år sedan
förälder
incheckning
907fb6830e
37 ändrade filer med 1710 tillägg och 48 borttagningar
  1. 7 0
      pom.xml
  2. 23 0
      themis-admin/src/main/java/com/qmth/themis/admin/api/TBClientDownloadController.java
  3. 90 24
      themis-admin/src/main/java/com/qmth/themis/admin/api/TEExamActivityController.java
  4. 132 0
      themis-admin/src/main/java/com/qmth/themis/admin/api/test.java
  5. 4 0
      themis-admin/src/main/java/com/qmth/themis/admin/start/StartRunning.java
  6. 3 0
      themis-admin/src/main/resources/application-dev.properties
  7. 16 0
      themis-business/src/main/java/com/qmth/themis/business/dao/TBClientDownloadMapper.java
  8. 27 0
      themis-business/src/main/java/com/qmth/themis/business/dao/TEAudioMapper.java
  9. 16 0
      themis-business/src/main/java/com/qmth/themis/business/dao/TSAuthMapper.java
  10. 37 0
      themis-business/src/main/java/com/qmth/themis/business/dto/AuthOrgInfoDto.java
  11. 94 0
      themis-business/src/main/java/com/qmth/themis/business/entity/TBClientDownload.java
  12. 1 1
      themis-business/src/main/java/com/qmth/themis/business/entity/TBUser.java
  13. 162 0
      themis-business/src/main/java/com/qmth/themis/business/entity/TEAudio.java
  14. 132 0
      themis-business/src/main/java/com/qmth/themis/business/entity/TSAuth.java
  15. 42 0
      themis-business/src/main/java/com/qmth/themis/business/enums/AudioDefaultEnum.java
  16. 42 0
      themis-business/src/main/java/com/qmth/themis/business/enums/AudioTypeEnum.java
  17. 42 0
      themis-business/src/main/java/com/qmth/themis/business/enums/AuthEnum.java
  18. 52 0
      themis-business/src/main/java/com/qmth/themis/business/service/AuthInfoService.java
  19. 34 0
      themis-business/src/main/java/com/qmth/themis/business/service/CacheService.java
  20. 16 0
      themis-business/src/main/java/com/qmth/themis/business/service/TBClientDownloadService.java
  21. 29 0
      themis-business/src/main/java/com/qmth/themis/business/service/TEAudioService.java
  22. 16 0
      themis-business/src/main/java/com/qmth/themis/business/service/TSAuthService.java
  23. 242 0
      themis-business/src/main/java/com/qmth/themis/business/service/impl/AuthInfoServiceImpl.java
  24. 59 0
      themis-business/src/main/java/com/qmth/themis/business/service/impl/CacheServiceImpl.java
  25. 20 0
      themis-business/src/main/java/com/qmth/themis/business/service/impl/TBClientDownloadServiceImpl.java
  26. 39 0
      themis-business/src/main/java/com/qmth/themis/business/service/impl/TEAudioServiceImpl.java
  27. 31 1
      themis-business/src/main/java/com/qmth/themis/business/service/impl/TEExamActivityServiceImpl.java
  28. 20 0
      themis-business/src/main/java/com/qmth/themis/business/service/impl/TSAuthServiceImpl.java
  29. 1 1
      themis-business/src/main/java/com/qmth/themis/business/templete/service/impl/TempleteLogicServiceImpl.java
  30. 0 17
      themis-business/src/main/java/com/qmth/themis/business/util/Base64Util.java
  31. 21 0
      themis-business/src/main/java/com/qmth/themis/business/util/OssUtil.java
  32. 2 2
      themis-business/src/main/resources/db/init-table-data.sql
  33. 188 2
      themis-business/src/main/resources/db/init-table.sql
  34. 5 0
      themis-business/src/main/resources/mapper/TBClientDownloadMapper.xml
  35. 56 0
      themis-business/src/main/resources/mapper/TEAudioMapper.xml
  36. 5 0
      themis-business/src/main/resources/mapper/TSAuthMapper.xml
  37. 4 0
      themis-common/pom.xml

+ 7 - 0
pom.xml

@@ -52,6 +52,8 @@
         <version-plugin.version>2.8.1</version-plugin.version>
         <googleBar.version>3.4.0</googleBar.version>
         <ip2region.version>2.6.5</ip2region.version>
+        <qmth.boot.version>1.0.3</qmth.boot.version>
+        <jave-all-deps.version>3.3.1</jave-all-deps.version>
     </properties>
 
     <dependencyManagement>
@@ -262,6 +264,11 @@
                 <artifactId>ip2region</artifactId>
                 <version>${ip2region.version}</version>
             </dependency>
+            <dependency>
+                <groupId>com.qmth.boot</groupId>
+                <artifactId>core-solar</artifactId>
+                <version>${qmth.boot.version}</version>
+            </dependency>
         </dependencies>
     </dependencyManagement>
 

+ 23 - 0
themis-admin/src/main/java/com/qmth/themis/admin/api/TBClientDownloadController.java

@@ -0,0 +1,23 @@
+package com.qmth.themis.admin.api;
+
+
+import io.swagger.annotations.Api;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * <p>
+ * 客户端下载管理 前端控制器
+ * </p>
+ *
+ * @author wangliang
+ * @since 2022-09-05
+ */
+@Api(tags = "客户端下载管理Controller")
+@RestController
+@RequestMapping("/${prefix.url.admin}/download")
+@Validated
+public class TBClientDownloadController {
+
+}

+ 90 - 24
themis-admin/src/main/java/com/qmth/themis/admin/api/TEExamActivityController.java

@@ -1,33 +1,36 @@
 package com.qmth.themis.admin.api;
 
-import java.util.Collections;
-import java.util.List;
-
-import javax.annotation.Resource;
-import javax.validation.constraints.Max;
-import javax.validation.constraints.Min;
-
-import org.springframework.transaction.annotation.Transactional;
-import org.springframework.validation.annotation.Validated;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestMethod;
-import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.bind.annotation.RestController;
-
+import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.qmth.themis.business.constant.SystemConstant;
+import com.qmth.themis.business.entity.TBAttachment;
+import com.qmth.themis.business.entity.TBUser;
+import com.qmth.themis.business.entity.TEAudio;
 import com.qmth.themis.business.entity.TEExamActivity;
+import com.qmth.themis.business.service.TBAttachmentService;
+import com.qmth.themis.business.service.TEAudioService;
 import com.qmth.themis.business.service.TEExamActivityService;
 import com.qmth.themis.business.service.TEExamPaperService;
+import com.qmth.themis.business.util.OssUtil;
+import com.qmth.themis.business.util.ServletUtil;
+import com.qmth.themis.common.exception.BusinessException;
 import com.qmth.themis.common.util.Result;
 import com.qmth.themis.common.util.ResultUtil;
+import io.swagger.annotations.*;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.CollectionUtils;
+import org.springframework.validation.BindingResult;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
 
-import io.swagger.annotations.Api;
-import io.swagger.annotations.ApiOperation;
-import io.swagger.annotations.ApiParam;
-import io.swagger.annotations.ApiResponse;
-import io.swagger.annotations.ApiResponses;
+import javax.annotation.Resource;
+import javax.validation.constraints.Max;
+import javax.validation.constraints.Min;
+import java.io.File;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
 
 /**
  * @Description: 考试场次 前端控制器
@@ -44,9 +47,18 @@ public class TEExamActivityController {
 
     @Resource
     private TEExamActivityService teExamActivityService;
-    
+
     @Resource
-    private  TEExamPaperService examPaperService;
+    private TEExamPaperService examPaperService;
+
+    @Resource
+    TEAudioService teAudioService;
+
+    @Resource
+    OssUtil ossUtil;
+
+    @Resource
+    TBAttachmentService tbAttachmentService;
 
     @ApiOperation(value = "考试场次修改/新增接口")
     @RequestMapping(value = "/save", method = RequestMethod.POST)
@@ -54,8 +66,8 @@ public class TEExamActivityController {
     @ApiResponses({@ApiResponse(code = 200, message = "{\"success\":true}", response = Result.class)})
     public Result save(
             @ApiParam(value = "考试场次信息", required = true) @RequestBody List<TEExamActivity> teExamActivityList) {
-    	teExamActivityService.saveExamActivity(teExamActivityList);
-    	examPaperService.disposeObjectiveAnswer(teExamActivityList.get(0).getExamId());
+        teExamActivityService.saveExamActivity(teExamActivityList);
+        examPaperService.disposeObjectiveAnswer(teExamActivityList.get(0).getExamId());
         return ResultUtil.ok(true);
     }
 
@@ -72,4 +84,58 @@ public class TEExamActivityController {
         return ResultUtil.ok(teExamActivityService
                 .examActivityQuery(new Page<>(pageNumber, pageSize), id, examId, code, startDate, finishDate));
     }
+
+    @ApiOperation(value = "考试场次语音查询接口")
+    @RequestMapping(value = "/audio/query", method = RequestMethod.POST)
+    @ApiResponses({@ApiResponse(code = 200, message = "考试场次语音信息", response = TEAudio.class)})
+    public Result audioQuery(@ApiParam(value = "考试场次id", required = true) @RequestParam Long activityId,
+                             @ApiParam(value = "分页页码", required = true) @RequestParam @Min(SystemConstant.PAGE_NUMBER_MIN) int pageNumber,
+                             @ApiParam(value = "分页数", required = true) @RequestParam @Min(SystemConstant.PAGE_SIZE_MIN) @Max(SystemConstant.PAGE_SIZE_MAX) int pageSize) {
+        IPage<TEAudio> teAudioIPage = teAudioService.query(new Page<>(pageNumber, pageSize), activityId);
+        if (Objects.nonNull(teAudioIPage) && !CollectionUtils.isEmpty(teAudioIPage.getRecords())) {
+            for (TEAudio t : teAudioIPage.getRecords()) {
+                if (Objects.nonNull(t.getAttachmentId())) {
+                    TBAttachment tbAttachment = tbAttachmentService.getById(t.getAttachmentId());
+                    if (Objects.nonNull(tbAttachment.getRemark())) {
+                        JSONObject jsonObject = JSONObject.parseObject(tbAttachment.getRemark());
+                        t.setAttachmentPath(ossUtil.getAliYunOssPublicDomain().getPublicUrl() + File.separator + jsonObject.getString(SystemConstant.PATH));
+                    }
+                }
+            }
+        }
+        return ResultUtil.ok(teAudioIPage);
+    }
+
+    @ApiOperation(value = "考试场次语音保存接口")
+    @RequestMapping(value = "/audio/save", method = RequestMethod.POST)
+    @ApiResponses({@ApiResponse(code = 200, message = "常规信息", response = Result.class)})
+    @Transactional
+    public Result audioSave(@Validated @RequestBody TEAudio teAudio, BindingResult bindingResult) {
+        if (bindingResult.hasErrors()) {
+            return ResultUtil.error(bindingResult.getAllErrors().get(0).getDefaultMessage());
+        }
+        TBUser tbUser = (TBUser) ServletUtil.getRequestAccount();
+        if (Objects.isNull(teAudio.getId())) {
+            teAudio.insertInfo(tbUser.getId());
+        } else {
+            teAudio.updateInfo(tbUser.getId());
+        }
+        teAudioService.saveOrUpdate(teAudio);
+        return ResultUtil.ok(true);
+    }
+
+    @ApiOperation(value = "考试场次语音启用/禁用接口")
+    @RequestMapping(value = "/audio/enable", method = RequestMethod.POST)
+    @ApiResponses({@ApiResponse(code = 200, message = "常规信息", response = Result.class)})
+    @Transactional
+    public Result audioEnable(@ApiParam(value = "考试场次语音id", required = true) @RequestParam Long audioId,
+                              @ApiParam(value = "启用/禁用", required = true) @RequestParam Boolean enable) {
+        TBUser tbUser = (TBUser) ServletUtil.getRequestAccount();
+        TEAudio teAudio = teAudioService.getById(audioId);
+        Optional.ofNullable(teAudio).orElseThrow(() -> new BusinessException("语音信息不存在"));
+        teAudio.setEnable(Objects.nonNull(enable) && enable ? 1 : 0);
+        teAudio.updateInfo(tbUser.getId());
+        teAudioService.updateById(teAudio);
+        return ResultUtil.ok(true);
+    }
 }

+ 132 - 0
themis-admin/src/main/java/com/qmth/themis/admin/api/test.java

@@ -0,0 +1,132 @@
+package com.qmth.themis.admin.api;
+
+import java.io.*;
+import java.util.Map;
+import java.util.Objects;
+import java.util.StringJoiner;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CountDownLatch;
+
+public class test {
+
+    /**
+     * 断点续传
+     *
+     * @param dataStr   目标文件地址
+     * @param targetStr 存放地址
+     */
+    public static void breakpointResume(String dataStr, String targetStr) {
+        File dataFile = new File(dataStr);
+        long length = dataFile.length();
+        int threadNum = 4;//指定线程数
+        //每个线程均分文件大小,且向上取整
+        long part = (long) Math.ceil(length / threadNum);
+        //线程减法计数器
+        CountDownLatch countDownLatch = new CountDownLatch(threadNum);
+        long start = System.currentTimeMillis();
+        //记录传输的日志文件
+        File logFile = new File(targetStr + ".log");
+        String[] splitData = null;//不是null就需要断点续传
+        BufferedReader reader = null;
+        try {
+            if (logFile.exists()) {
+                //存在日志文件,需要进行断点续传
+                reader = new BufferedReader(new FileReader(logFile));
+                String data = reader.readLine();
+                splitData = data.split(",");
+            } else {
+                //不存在日志文件,创建日志文件
+                logFile.createNewFile();
+            }
+            Map<Integer, Long> maps = new ConcurrentHashMap<>();
+            for (int i = 0; i < threadNum; i++) {
+                final int k = i;
+                System.out.println("线程正在执行任务:" + k);
+                String[] finalData = splitData;
+                new Thread(() -> {
+                    RandomAccessFile inFile = null;
+                    RandomAccessFile outFile = null;
+                    RandomAccessFile rafLog = null;
+                    try {
+                        inFile = new RandomAccessFile(dataFile, "r");//读
+                        outFile = new RandomAccessFile(targetStr, "rw");//写
+                        rafLog = new RandomAccessFile(logFile, "rw");//操作日志文件的流
+                        //确定每个线程读取文件的开始和结束的位置,有断点续传就从日志文件取出的位置开始读取
+                        inFile.seek(finalData == null ? k * part : Long.parseLong(finalData[k]));//设置每个线程读取的启始位置
+                        outFile.seek(finalData == null ? k * part : Long.parseLong(finalData[k]));//设置每个线程写入的启始位置
+                        byte[] bytes = new byte[1024 * 10];//每次读取字节大小
+                        int len = -1, allLen = 0;
+                        while (true) {
+                            len = inFile.read(bytes);//从磁盘读取到缓存
+                            if (len == -1) { //数据读完,结束
+                                break;
+                            }
+                            //如果不等于 -1,把每次读取的字节累加
+                            allLen = allLen + len;
+                            //将读取的字节数放入到map中
+                            maps.put(k, allLen + (finalData == null ? k * part : Long.parseLong(finalData[k])));//每个线程的绝对偏移量
+                            outFile.write(bytes, 0, len);//从缓存写入到磁盘
+                            //将map中的字节日志信息数据写入磁盘
+                            StringJoiner stringJoiner = new StringJoiner(",");
+                            maps.forEach((key, value) -> stringJoiner.add(String.valueOf(value)));
+                            //将日志信息写入磁盘
+                            rafLog.seek(0);//覆盖之前的日志信息
+                            rafLog.write(stringJoiner.toString().getBytes("UTF-8"));
+                            /**
+                             * 当前线程读取的内容
+                             *  allLen + (k * part)
+                             *  或
+                             *  allLen + finalData[k] 日志文件里面的偏移量
+                             *  >=
+                             *  下个线程的起始部分((k + 1) * part)
+                             *  当前线程就不再读取写入数据,结束任务
+                             */
+                            if (allLen + (finalData == null ? k * part : Long.parseLong(finalData[k])) >= (k + 1) * part) {
+                                break;
+                            }
+                        }
+                    } catch (IOException e) {
+                        e.printStackTrace();
+                    } finally {
+                        //关流
+                        try {
+                            if (Objects.nonNull(outFile)) {
+                                outFile.close();
+                            }
+                            if (Objects.nonNull(inFile)) {
+                                inFile.close();
+                            }
+                            if (Objects.nonNull(rafLog)) {
+                                rafLog.close();
+                            }
+                        } catch (IOException e) {
+                            e.printStackTrace();
+                        }
+                        countDownLatch.countDown();//减一
+                    }
+                }).start();
+            }
+
+            //主线程要等到线程计数器归零,再继续往下执行
+            countDownLatch.await();
+            long end = System.currentTimeMillis();
+            System.out.println("总耗时:" + (end - start) / 1000 + "秒");
+            //删除日志文件
+            logFile.delete();
+        } catch (Exception e) {
+            e.printStackTrace();
+        } finally {
+            if (null != reader) {
+                try {
+                    reader.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+    }
+
+    public static void main(String[] args) {
+        breakpointResume("/Users/king/Downloads/cn_windows_10_multiple_editions_version_1511_x64_dvd_7223622.iso", "/Users/king/Downloads/new.iso");
+    }
+}

+ 4 - 0
themis-admin/src/main/java/com/qmth/themis/admin/start/StartRunning.java

@@ -23,11 +23,15 @@ public class StartRunning implements CommandLineRunner {
     @Value("${spring.application.name}")
     String applicationName;
 
+//    @Resource
+//    AuthInfoService authInfoService;
+
     @Override
     public void run(String... args) throws Exception {
         log.info("服务器启动时执行 start");
         SystemConstant.initTempFiles();
         SystemConstant.getSearcher(SystemConstant.TEMP_FILES_DIR + File.separator + applicationName);
+//        authInfoService.appInfoInit();
         log.info("服务器启动时执行 end");
     }
 }

+ 3 - 0
themis-admin/src/main/resources/application-dev.properties

@@ -215,6 +215,9 @@ prefix.url.notify=api/notify
 
 monitor.config.prefix=oe_test
 
+#com.qmth.solar.access-key=274f823e5f59410f8b3bb6edcd8e2b6e
+#com.qmth.solar.access-secret=y7AO6W0TOdTF8HpWBwGHbp3wfIHsmUKr
+
 #\u65E0\u9700\u9274\u6743\u7684url
 no.auth.urls=/webjars/**,/druid/**,/swagger-ui.html,/doc.html,/swagger-resources/**,/v2/api-docs,/webjars/springfox-swagger-ui/**,/api/admin/user/login/account,/api/admin/sys/org/queryByOrgCode,/file/**,/upload/**,/client/**,/base_photo/**,/frontend/**,/api/admin/client/save,/api/admin/client/upload,/api/admin/client/query,/api/admin/app/save,/api/admin/app/query,/api/notify/monitor/record/tencent,/api/notify/monitor/status/tencent
 common.system.urls=/api/admin/sys/getMenu,/api/admin/user/logout,/api/admin/sys/env,/api/admin/sys/file/upload,/api/admin/sys/file/download,/api/admin/sys/org/query,/api/admin/sys/role/query,/api/admin/sys/examActivity/query,/api/admin/sys/exam/query,/api/admin/sys/examRoom/query,/api/admin/sys/exam/privilegeQuery,/api/admin/student/photo/upload,/api/admin/sys/getPlayUrls,/api/admin/sys/exam/finish/query,/api/admin/sys/get_tencent_video

+ 16 - 0
themis-business/src/main/java/com/qmth/themis/business/dao/TBClientDownloadMapper.java

@@ -0,0 +1,16 @@
+package com.qmth.themis.business.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.qmth.themis.business.entity.TBClientDownload;
+
+/**
+ * <p>
+ * 客户端下载管理 Mapper 接口
+ * </p>
+ *
+ * @author wangliang
+ * @since 2022-09-05
+ */
+public interface TBClientDownloadMapper extends BaseMapper<TBClientDownload> {
+
+}

+ 27 - 0
themis-business/src/main/java/com/qmth/themis/business/dao/TEAudioMapper.java

@@ -0,0 +1,27 @@
+package com.qmth.themis.business.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.qmth.themis.business.entity.TEAudio;
+
+import java.util.Map;
+
+/**
+ * <p>
+ * 场次音频表 Mapper 接口
+ * </p>
+ *
+ * @author wangliang
+ * @since 2022-08-30
+ */
+public interface TEAudioMapper extends BaseMapper<TEAudio> {
+
+    /**
+     * 根据场次id查询音频信息
+     *
+     * @param iPage
+     * @param activityId
+     * @return
+     */
+    IPage<TEAudio> query(IPage<Map> iPage, Long activityId);
+}

+ 16 - 0
themis-business/src/main/java/com/qmth/themis/business/dao/TSAuthMapper.java

@@ -0,0 +1,16 @@
+package com.qmth.themis.business.dao;
+
+import com.qmth.themis.business.base.CustomBaseMapper;
+import com.qmth.themis.business.entity.TSAuth;
+
+/**
+ * <p>
+ * 激活授权配置表 Mapper 接口
+ * </p>
+ *
+ * @author wangliang
+ * @since 2022-08-30
+ */
+public interface TSAuthMapper extends CustomBaseMapper<TSAuth> {
+
+}

+ 37 - 0
themis-business/src/main/java/com/qmth/themis/business/dto/AuthOrgInfoDto.java

@@ -0,0 +1,37 @@
+package com.qmth.themis.business.dto;
+
+import com.qmth.boot.core.solar.model.AppControl;
+import com.qmth.boot.core.solar.model.OrgInfo;
+
+import java.io.Serializable;
+
+/**
+ * @Description: 鉴权org dto
+ * @Param:
+ * @return:
+ * @Author: wangliang
+ * @Date: 2022/9/2
+ */
+public class AuthOrgInfoDto implements Serializable {
+
+    OrgInfo orgInfo;
+
+    AppControl control;
+
+    public AuthOrgInfoDto() {
+
+    }
+
+    public AuthOrgInfoDto(OrgInfo orgInfo, AppControl control) {
+        this.orgInfo = orgInfo;
+        this.control = control;
+    }
+
+    public AppControl getControl() {
+        return control;
+    }
+
+    public void setControl(AppControl control) {
+        this.control = control;
+    }
+}

+ 94 - 0
themis-business/src/main/java/com/qmth/themis/business/entity/TBClientDownload.java

@@ -0,0 +1,94 @@
+package com.qmth.themis.business.entity;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
+import com.qmth.themis.business.base.BaseEntity;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+
+import java.io.Serializable;
+
+/**
+ * <p>
+ * 客户端下载管理
+ * </p>
+ *
+ * @author wangliang
+ * @since 2022-09-05
+ */
+@ApiModel(value = "TBClientDownload对象", description = "客户端下载管理")
+public class TBClientDownload extends BaseEntity implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @JsonSerialize(using = ToStringSerializer.class)
+    @ApiModelProperty(value = "机构id")
+    private Long orgId;
+
+    @JsonSerialize(using = ToStringSerializer.class)
+    @ApiModelProperty(value = "附件id")
+    private Long attachmentId;
+
+    @ApiModelProperty(value = "文件名称")
+    private String name;
+
+    @ApiModelProperty(value = "是否启用,0:停用,1:启用")
+    private Integer enable;
+
+    @ApiModelProperty(value = "更新人")
+    @TableField(exist = false)
+    private String updateName;
+
+    @ApiModelProperty(value = "附件路径")
+    @TableField(exist = false)
+    private String attachmentPath;
+
+    public Long getOrgId() {
+        return orgId;
+    }
+
+    public void setOrgId(Long orgId) {
+        this.orgId = orgId;
+    }
+
+    public Long getAttachmentId() {
+        return attachmentId;
+    }
+
+    public void setAttachmentId(Long attachmentId) {
+        this.attachmentId = attachmentId;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public Integer getEnable() {
+        return enable;
+    }
+
+    public void setEnable(Integer enable) {
+        this.enable = enable;
+    }
+
+    public String getUpdateName() {
+        return updateName;
+    }
+
+    public void setUpdateName(String updateName) {
+        this.updateName = updateName;
+    }
+
+    public String getAttachmentPath() {
+        return attachmentPath;
+    }
+
+    public void setAttachmentPath(String attachmentPath) {
+        this.attachmentPath = attachmentPath;
+    }
+}

+ 1 - 1
themis-business/src/main/java/com/qmth/themis/business/entity/TBUser.java

@@ -5,8 +5,8 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize;
 import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
 import com.qmth.themis.business.base.BaseEntity;
 import com.qmth.themis.business.constant.SystemConstant;
-import com.qmth.themis.business.util.Base64Util;
 import com.qmth.themis.business.util.UidUtil;
+import com.qmth.themis.common.util.Base64Util;
 import io.swagger.annotations.ApiModel;
 import io.swagger.annotations.ApiModelProperty;
 

+ 162 - 0
themis-business/src/main/java/com/qmth/themis/business/entity/TEAudio.java

@@ -0,0 +1,162 @@
+package com.qmth.themis.business.entity;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
+import com.qmth.themis.business.base.BaseEntity;
+import com.qmth.themis.business.enums.AudioDefaultEnum;
+import com.qmth.themis.business.enums.AudioTypeEnum;
+import com.qmth.themis.business.util.UidUtil;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+
+import javax.validation.constraints.NotNull;
+import java.io.Serializable;
+
+/**
+ * <p>
+ * 场次音频表
+ * </p>
+ *
+ * @author wangliang
+ * @since 2022-08-30
+ */
+@ApiModel(value = "TEAudio对象", description = "场次音频表")
+public class TEAudio extends BaseEntity implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @JsonSerialize(using = ToStringSerializer.class)
+    @ApiModelProperty(value = "机构id")
+    @NotNull(message = "机构id不能为空")
+    private Long orgId;
+
+    @JsonSerialize(using = ToStringSerializer.class)
+    @ApiModelProperty(value = "考试场次id")
+    @NotNull(message = "考试场次id不能为空")
+    private Long activityId;
+
+    @JsonSerialize(using = ToStringSerializer.class)
+    @ApiModelProperty(value = "附件id")
+    @NotNull(message = "附件id不能为空")
+    private Long attachmentId;
+
+    @ApiModelProperty(value = "语音内容")
+    private String content;
+
+    @ApiModelProperty(value = "类别,BEFORE:开考前语音,AFTER:考试结束前语音")
+    @NotNull(message = "类别不能为空")
+    private AudioTypeEnum type;
+
+    @ApiModelProperty(value = "是否启用,0:停用,1:启用")
+    private Integer enable = 1;
+
+    @ApiModelProperty(value = "是否默认,SYS:系统内置,CUSTOM:自定义")
+    private AudioDefaultEnum audioDefault = AudioDefaultEnum.SYS;
+
+    @ApiModelProperty(value = "更新人")
+    @TableField(exist = false)
+    private String updateName;
+
+    @ApiModelProperty(value = "附件路径")
+    @TableField(exist = false)
+    private String attachmentPath;
+
+    public TEAudio() {
+
+    }
+
+    public TEAudio(Long orgId, Long activityId, Long attachmentId, String content, AudioTypeEnum type, AudioDefaultEnum audioDefault, Long userId) {
+        setId(UidUtil.nextId());
+        this.orgId = orgId;
+        this.activityId = activityId;
+        this.attachmentId = attachmentId;
+        this.content = content;
+        this.type = type;
+        this.audioDefault = audioDefault;
+        setCreateId(userId);
+    }
+
+    public void insertInfo(Long userId) {
+        setId(UidUtil.nextId());
+        setCreateId(userId);
+        setCreateTime(System.currentTimeMillis());
+    }
+
+    public void updateInfo(Long userId) {
+        setUpdateId(userId);
+        setUpdateTime(System.currentTimeMillis());
+    }
+
+    public String getUpdateName() {
+        return updateName;
+    }
+
+    public void setUpdateName(String updateName) {
+        this.updateName = updateName;
+    }
+
+    public String getAttachmentPath() {
+        return attachmentPath;
+    }
+
+    public void setAttachmentPath(String attachmentPath) {
+        this.attachmentPath = attachmentPath;
+    }
+
+    public Long getOrgId() {
+        return orgId;
+    }
+
+    public void setOrgId(Long orgId) {
+        this.orgId = orgId;
+    }
+
+    public Long getActivityId() {
+        return activityId;
+    }
+
+    public void setActivityId(Long activityId) {
+        this.activityId = activityId;
+    }
+
+    public Long getAttachmentId() {
+        return attachmentId;
+    }
+
+    public void setAttachmentId(Long attachmentId) {
+        this.attachmentId = attachmentId;
+    }
+
+    public String getContent() {
+        return content;
+    }
+
+    public void setContent(String content) {
+        this.content = content;
+    }
+
+    public AudioTypeEnum getType() {
+        return type;
+    }
+
+    public void setType(AudioTypeEnum type) {
+        this.type = type;
+    }
+
+    public Integer getEnable() {
+        return enable;
+    }
+
+    public void setEnable(Integer enable) {
+        this.enable = enable;
+    }
+
+    public AudioDefaultEnum getAudioDefault() {
+        return audioDefault;
+    }
+
+    public void setAudioDefault(AudioDefaultEnum audioDefault) {
+        this.audioDefault = audioDefault;
+    }
+}

+ 132 - 0
themis-business/src/main/java/com/qmth/themis/business/entity/TSAuth.java

@@ -0,0 +1,132 @@
+package com.qmth.themis.business.entity;
+
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
+import com.qmth.themis.business.base.BaseEntity;
+import com.qmth.themis.business.enums.AuthEnum;
+import com.qmth.themis.business.util.UidUtil;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+
+import java.io.Serializable;
+
+/**
+ * <p>
+ * 激活授权配置表
+ * </p>
+ *
+ * @author wangliang
+ * @since 2022-04-26
+ */
+@ApiModel(value = "TSAuth对象", description = "激活授权配置表")
+public class TSAuth extends BaseEntity implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "学校id")
+    @JsonSerialize(using = ToStringSerializer.class)
+    private Long schoolId;
+
+    @ApiModelProperty(value = "accessKey")
+    private String accessKey;
+
+    @ApiModelProperty(value = "accessSecret")
+    private String accessSecret;
+
+    @ApiModelProperty(value = "离线授权证书")
+    private String description;
+
+    @ApiModelProperty(value = "授权类型")
+    private AuthEnum type;
+
+    @ApiModelProperty(value = "过期时间")
+    private Long expireTime;
+
+    @ApiModelProperty(value = "文件数据")
+    private byte[] file;
+
+    public TSAuth() {
+
+    }
+
+    public TSAuth(Long schoolId, String accessKey, String accessSecret, AuthEnum type, Long expireTime) {
+        setId(UidUtil.nextId());
+        this.schoolId = schoolId;
+        this.accessKey = accessKey;
+        this.accessSecret = accessSecret;
+        this.type = type;
+        this.expireTime = expireTime;
+    }
+
+    public TSAuth(Long schoolId, String description, AuthEnum type, Long expireTime) {
+        setId(UidUtil.nextId());
+        this.schoolId = schoolId;
+        this.description = description;
+        this.type = type;
+        this.expireTime = expireTime;
+    }
+
+    public TSAuth(Long schoolId, byte[] file, AuthEnum type, Long expireTime) {
+        setId(UidUtil.nextId());
+        this.schoolId = schoolId;
+        this.file = file;
+        this.type = type;
+        this.expireTime = expireTime;
+    }
+
+    public byte[] getFile() {
+        return file;
+    }
+
+    public void setFile(byte[] file) {
+        this.file = file;
+    }
+
+    public Long getExpireTime() {
+        return expireTime;
+    }
+
+    public void setExpireTime(Long expireTime) {
+        this.expireTime = expireTime;
+    }
+
+    public Long getSchoolId() {
+        return schoolId;
+    }
+
+    public void setSchoolId(Long schoolId) {
+        this.schoolId = schoolId;
+    }
+
+    public String getAccessKey() {
+        return accessKey;
+    }
+
+    public void setAccessKey(String accessKey) {
+        this.accessKey = accessKey;
+    }
+
+    public String getAccessSecret() {
+        return accessSecret;
+    }
+
+    public void setAccessSecret(String accessSecret) {
+        this.accessSecret = accessSecret;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    public void setDescription(String description) {
+        this.description = description;
+    }
+
+    public AuthEnum getType() {
+        return type;
+    }
+
+    public void setType(AuthEnum type) {
+        this.type = type;
+    }
+}

+ 42 - 0
themis-business/src/main/java/com/qmth/themis/business/enums/AudioDefaultEnum.java

@@ -0,0 +1,42 @@
+package com.qmth.themis.business.enums;
+
+import java.util.Objects;
+
+/**
+ * @Description: 音频默认
+ * @Param:
+ * @return:
+ * @Author: wangliang
+ * @Date: 2022/8/30
+ */
+public enum AudioDefaultEnum {
+
+    SYS("系统内置"),
+
+    CUSTOM("自定义");
+
+    AudioDefaultEnum(String title) {
+        this.title = title;
+    }
+
+    private String title;
+
+    public String getTitle() {
+        return title;
+    }
+
+    /**
+     * 状态转换 toName
+     *
+     * @param value
+     * @return
+     */
+    public static String convertToName(String value) {
+        for (AudioDefaultEnum e : AudioDefaultEnum.values()) {
+            if (Objects.equals(value.trim(), e.getTitle())) {
+                return e.name();
+            }
+        }
+        return null;
+    }
+}

+ 42 - 0
themis-business/src/main/java/com/qmth/themis/business/enums/AudioTypeEnum.java

@@ -0,0 +1,42 @@
+package com.qmth.themis.business.enums;
+
+import java.util.Objects;
+
+/**
+ * @Description: 音频类型
+ * @Param:
+ * @return:
+ * @Author: wangliang
+ * @Date: 2022/8/30
+ */
+public enum AudioTypeEnum {
+
+    BEFORE("开考前语音"),
+
+    AFTER("考试结束前语音");
+
+    AudioTypeEnum(String title) {
+        this.title = title;
+    }
+
+    private String title;
+
+    public String getTitle() {
+        return title;
+    }
+
+    /**
+     * 状态转换 toName
+     *
+     * @param value
+     * @return
+     */
+    public static String convertToName(String value) {
+        for (AudioTypeEnum e : AudioTypeEnum.values()) {
+            if (Objects.equals(value.trim(), e.getTitle())) {
+                return e.name();
+            }
+        }
+        return null;
+    }
+}

+ 42 - 0
themis-business/src/main/java/com/qmth/themis/business/enums/AuthEnum.java

@@ -0,0 +1,42 @@
+package com.qmth.themis.business.enums;
+
+import java.util.Objects;
+
+/**
+ * @Description: 授权配置类型
+ * @Param:
+ * @return:
+ * @Author: wangliang
+ * @Date: 2022/4/26
+ */
+public enum AuthEnum {
+
+    ON_LINE("在线激活"),
+
+    OFF_LINE("离线激活");
+
+    AuthEnum(String title) {
+        this.title = title;
+    }
+
+    private String title;
+
+    public String getTitle() {
+        return title;
+    }
+
+    /**
+     * 状态转换 toName
+     *
+     * @param value
+     * @return
+     */
+    public static String convertToName(String value) {
+        for (AuthEnum e : AuthEnum.values()) {
+            if (Objects.equals(value.trim(), e.getTitle())) {
+                return e.name();
+            }
+        }
+        return null;
+    }
+}

+ 52 - 0
themis-business/src/main/java/com/qmth/themis/business/service/AuthInfoService.java

@@ -0,0 +1,52 @@
+package com.qmth.themis.business.service;
+
+import com.qmth.boot.core.solar.model.AppInfo;
+import com.qmth.themis.business.enums.AuthEnum;
+
+/**
+ * @Description: 授权信息service
+ * @Param:
+ * @return:
+ * @Author: wangliang
+ * @Date: 2022/5/17
+ */
+public interface AuthInfoService {
+
+    /**
+     * 授权信息初始化
+     *
+     * @return
+     */
+    AppInfo appInfoInit();
+
+    /**
+     * app是否过期
+     *
+     * @param code
+     */
+    void appHasExpired(String code);
+
+    /**
+     * 离线激活
+     *
+     * @param licenseData
+     */
+    void updateLicense(byte[] licenseData) throws Exception;
+
+    /**
+     * 查询授权信息
+     *
+     * @return
+     */
+    Long selectAuthInfo();
+
+    /**
+     * 保存授权信息
+     *
+     * @param appInfo
+     * @param authEnum
+     * @param file
+     * @throws Exception
+     */
+    public void saveAuthInfo(AppInfo appInfo, AuthEnum authEnum, byte[] file) throws Exception;
+}

+ 34 - 0
themis-business/src/main/java/com/qmth/themis/business/service/CacheService.java

@@ -1,6 +1,7 @@
 package com.qmth.themis.business.service;
 
 import com.qmth.themis.business.dto.AuthDto;
+import com.qmth.themis.business.dto.AuthOrgInfoDto;
 import com.qmth.themis.business.dto.cache.TEStudentCacheDto;
 import com.qmth.themis.business.entity.SysConfig;
 import com.qmth.themis.business.entity.TBOrg;
@@ -63,6 +64,16 @@ public interface CacheService {
      */
     public void removeOrgCodeCache(String code);
 
+    /**
+     * 删除机构缓存
+     */
+    public void removeOrgIdCache();
+
+    /**
+     * 删除机构缓存
+     */
+    public void removeOrgCodeCache();
+
     /**
      * 添加用户鉴权缓存
      *
@@ -161,4 +172,27 @@ public interface CacheService {
      * @param key
      */
     public void removeSysConfigCache(String key);
+
+    /**
+     * 添加鉴权缓存
+     *
+     * @param code
+     * @return
+     */
+    public AuthOrgInfoDto authInfoCache(String code);
+
+    /**
+     * 修改鉴权缓存
+     *
+     * @param code
+     * @return
+     */
+    public AuthOrgInfoDto updateAuthInfoCache(String code);
+
+    /**
+     * 删除鉴权缓存
+     *
+     * @param code
+     */
+    public void removeAuthInfoCache(String code);
 }

+ 16 - 0
themis-business/src/main/java/com/qmth/themis/business/service/TBClientDownloadService.java

@@ -0,0 +1,16 @@
+package com.qmth.themis.business.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.qmth.themis.business.entity.TBClientDownload;
+
+/**
+ * <p>
+ * 客户端下载管理 服务类
+ * </p>
+ *
+ * @author wangliang
+ * @since 2022-09-05
+ */
+public interface TBClientDownloadService extends IService<TBClientDownload> {
+
+}

+ 29 - 0
themis-business/src/main/java/com/qmth/themis/business/service/TEAudioService.java

@@ -0,0 +1,29 @@
+package com.qmth.themis.business.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.qmth.themis.business.entity.TEAudio;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * <p>
+ * 场次音频表 服务类
+ * </p>
+ *
+ * @author wangliang
+ * @since 2022-08-30
+ */
+public interface TEAudioService extends IService<TEAudio> {
+
+    /**
+     * 根据场次id查询音频信息
+     *
+     * @param iPage
+     * @param activityId
+     * @return
+     */
+    IPage<TEAudio> query(IPage<Map> iPage, @Param("activityId") Long activityId);
+}

+ 16 - 0
themis-business/src/main/java/com/qmth/themis/business/service/TSAuthService.java

@@ -0,0 +1,16 @@
+package com.qmth.themis.business.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.qmth.themis.business.entity.TSAuth;
+
+/**
+ * <p>
+ * 激活授权配置表 服务类
+ * </p>
+ *
+ * @author wangliang
+ * @since 2022-08-30
+ */
+public interface TSAuthService extends IService<TSAuth> {
+
+}

+ 242 - 0
themis-business/src/main/java/com/qmth/themis/business/service/impl/AuthInfoServiceImpl.java

@@ -0,0 +1,242 @@
+package com.qmth.themis.business.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.qmth.boot.core.solar.config.SolarProperties;
+import com.qmth.boot.core.solar.model.AppInfo;
+import com.qmth.boot.core.solar.model.OrgInfo;
+import com.qmth.boot.core.solar.service.SolarService;
+import com.qmth.themis.business.constant.SystemConstant;
+import com.qmth.themis.business.dao.TBOrgMapper;
+import com.qmth.themis.business.dao.TSAuthMapper;
+import com.qmth.themis.business.dto.AuthOrgInfoDto;
+import com.qmth.themis.business.entity.TBOrg;
+import com.qmth.themis.business.entity.TSAuth;
+import com.qmth.themis.business.enums.AuthEnum;
+import com.qmth.themis.business.enums.UploadFileEnum;
+import com.qmth.themis.business.service.AuthInfoService;
+import com.qmth.themis.business.service.CacheService;
+import com.qmth.themis.business.service.TBOrgService;
+import com.qmth.themis.business.service.TSAuthService;
+import com.qmth.themis.business.util.OssUtil;
+import com.qmth.themis.business.util.QrCodeUtil;
+import com.qmth.themis.common.enums.ExceptionResultEnum;
+import com.qmth.themis.common.exception.BusinessException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.CollectionUtils;
+
+import javax.annotation.Resource;
+import java.io.File;
+import java.time.LocalDateTime;
+import java.util.*;
+
+/**
+ * @Description: 授权信息service
+ * @Param:
+ * @return:
+ * @Author: wangliang
+ * @Date: 2022/5/17
+ */
+@Service
+public class AuthInfoServiceImpl implements AuthInfoService {
+    private final static Logger log = LoggerFactory.getLogger(AuthInfoServiceImpl.class);
+
+    @Resource
+    SolarProperties solarProperties;
+
+    @Resource
+    SolarService solarService;
+
+    @Resource
+    TSAuthService tsAuthService;
+
+    @Resource
+    TSAuthMapper tsAuthMapper;
+
+    @Resource
+    CacheService cacheService;
+
+    @Resource
+    TBOrgService tbOrgService;
+
+    @Resource
+    TBOrgMapper tbOrgMapper;
+
+    @Resource
+    QrCodeUtil qrCodeUtil;
+
+    @Resource
+    AuthInfoService authInfoService;
+
+    @Resource
+    OssUtil ossUtil;
+
+    /**
+     * 授权信息初始化
+     *
+     * @return
+     */
+    @Override
+    public AppInfo appInfoInit() {
+        AppInfo appInfo = null;
+        try {
+            appInfo = solarService.getAppInfo();
+            if (Objects.nonNull(appInfo) && Objects.nonNull(solarProperties)) {
+                if (Objects.nonNull(solarProperties.getAccessKey())
+                        && !Objects.equals(solarProperties.getAccessKey().trim(), "")
+                        && Objects.nonNull(solarProperties.getAccessSecret())
+                        && !Objects.equals(solarProperties.getAccessSecret().trim(), "")) {//在线激活
+                    authInfoService.saveAuthInfo(appInfo, AuthEnum.ON_LINE, null);
+                } else if (Objects.nonNull(solarProperties.getLicense())
+                        && !Objects.equals(solarProperties.getLicense().trim(), "")) {//离线激活
+                    authInfoService.saveAuthInfo(appInfo, AuthEnum.OFF_LINE, null);
+                }
+            } else {
+                QueryWrapper<TSAuth> tsAuthQueryWrapper = new QueryWrapper<>();
+                tsAuthQueryWrapper.lambda().isNotNull(TSAuth::getFile);
+                List<TSAuth> tsAuthList = tsAuthService.list(tsAuthQueryWrapper);
+                if (!CollectionUtils.isEmpty(tsAuthList)) {
+                    for (TSAuth t : tsAuthList) {
+                        appInfo = solarService.update(t.getFile());
+                        authInfoService.saveAuthInfo(appInfo, AuthEnum.OFF_LINE, t.getFile());
+                    }
+                }
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return appInfo;
+    }
+
+    /**
+     * app是否过期
+     *
+     * @param code
+     * @return
+     */
+    @Override
+    public void appHasExpired(String code) {
+        if (Objects.nonNull(code)) {
+            AuthOrgInfoDto authOrgInfoDto = cacheService.authInfoCache(code);
+            if (Objects.isNull(authOrgInfoDto) || (Objects.nonNull(authOrgInfoDto) && authOrgInfoDto.getControl().hasExpired())) {
+                //TODO因授权文件失败暂时开发去掉
+                throw new BusinessException(ExceptionResultEnum.AUTH_INFO_ERROR);
+            }
+        }
+    }
+
+    /**
+     * 离线激活
+     *
+     * @param licenseData
+     */
+    @Override
+    public void updateLicense(byte[] licenseData) throws Exception {
+        AppInfo appInfo = solarService.update(licenseData);
+        if (Objects.isNull(appInfo)) {
+            throw new BusinessException("激活失败");
+        }
+        authInfoService.saveAuthInfo(appInfo, AuthEnum.OFF_LINE, licenseData);
+    }
+
+    /**
+     * 查询授权信息
+     *
+     * @return
+     */
+    @Override
+    public Long selectAuthInfo() {
+        Long expireTime = null;
+        AppInfo appInfo = solarService.getAppInfo();
+        if (Objects.nonNull(appInfo) && Objects.nonNull(appInfo.getControl())) {
+            expireTime = Objects.nonNull(appInfo.getControl().getExpireTime()) ? appInfo.getControl().getExpireTime() : -1;
+        }
+        return expireTime;
+    }
+
+    /**
+     * 保存鉴权信息
+     *
+     * @param appInfo
+     * @param authEnum
+     * @param file
+     * @throws Exception
+     */
+    @Override
+    @Transactional
+    public void saveAuthInfo(AppInfo appInfo, AuthEnum authEnum, byte[] file) throws Exception {
+        List<OrgInfo> orgInfoList = solarService.getOrgList();
+        List<TSAuth> tsAuthList = null;
+        Set<Long> orgIdsSet = null;
+        Set<TBOrg> tbOrgSet = null;
+        if (!CollectionUtils.isEmpty(orgInfoList)) {
+            tsAuthList = new ArrayList<>();
+            orgIdsSet = new HashSet<>();
+            tbOrgSet = new HashSet<>();
+        }
+        boolean oss = qrCodeUtil.getSysDomain().isOss();
+        for (OrgInfo o : orgInfoList) {
+            orgIdsSet.add(o.getId());
+            if (authEnum == AuthEnum.OFF_LINE) {
+                if (Objects.isNull(file)) {
+                    tsAuthList.add(new TSAuth(o.getId(), solarProperties.getLicense(), authEnum, appInfo.getControl().getExpireTime()));
+                } else {
+                    tsAuthList.add(new TSAuth(o.getId(), file, authEnum, appInfo.getControl().getExpireTime()));
+                }
+            } else {
+                tsAuthList.add(new TSAuth(o.getId(), solarProperties.getAccessKey(), solarProperties.getAccessSecret(), authEnum, appInfo.getControl().getExpireTime()));
+            }
+            cacheService.updateAuthInfoCache(o.getCode());
+
+            QueryWrapper<TBOrg> tbOrgQueryWrapper = new QueryWrapper<>();
+            tbOrgQueryWrapper.lambda().eq(TBOrg::getCode, o.getCode());
+            TBOrg tbOrg = tbOrgService.getOne(tbOrgQueryWrapper);
+            if (Objects.isNull(tbOrg)) {//不存在则创建学校
+                tbOrg = new TBOrg(o.getCode(), o.getName(), o.getAccessKey(), o.getAccessSecret());
+                if (Objects.nonNull(o.getLogo()) && (!o.getLogo().startsWith("https:") || !o.getLogo().startsWith("http"))) {
+                    String filePath = SystemConstant.TEMP_FILES_DIR + File.separator + SystemConstant.getUuid() + ".jpg";
+                    File logoFile = new File(filePath);
+                    if (!logoFile.getParentFile().exists()) {
+                        // 不存在则创建父目录及子文件
+                        logoFile.getParentFile().mkdirs();
+                        logoFile.createNewFile();
+                    }
+                    SystemConstant.base64ToImage(o.getLogo(), filePath);
+
+                    if (oss) {
+                        LocalDateTime nowTime = LocalDateTime.now();
+                        StringJoiner stringJoiner = new StringJoiner("");
+                        stringJoiner.add(UploadFileEnum.file.name().toLowerCase()).add(File.separator)
+                                .add(String.valueOf(nowTime.getYear())).add(File.separator)
+                                .add(String.format("%02d", nowTime.getMonthValue())).add(File.separator)
+                                .add(String.format("%02d", nowTime.getDayOfMonth())).add(File.separator)
+                                .add(SystemConstant.getUuid()).add(".jpg");
+                        ossUtil.upload(false, stringJoiner.toString(), logoFile);
+                        logoFile.delete();
+                        tbOrg.setLogo(ossUtil.getPrivateUrl(stringJoiner.toString()));
+                    } else {
+                        tbOrg.setLogo(filePath);
+                    }
+                } else {
+                    tbOrg.setLogo(o.getLogo());
+                }
+                tbOrgSet.add(tbOrg);
+            }
+        }
+
+        if (!CollectionUtils.isEmpty(tsAuthList) && !CollectionUtils.isEmpty(orgIdsSet)) {
+            QueryWrapper<TSAuth> tsAuthQueryWrapper = new QueryWrapper<>();
+            tsAuthQueryWrapper.lambda().in(TSAuth::getSchoolId, orgIdsSet);
+            tsAuthService.remove(tsAuthQueryWrapper);
+            tsAuthMapper.insertBatch(tsAuthList);
+
+            if (!CollectionUtils.isEmpty(tbOrgSet)) {
+                cacheService.removeOrgIdCache();
+                cacheService.removeOrgCodeCache();
+                tbOrgMapper.insertBatch(tbOrgSet);
+            }
+        }
+    }
+}

+ 59 - 0
themis-business/src/main/java/com/qmth/themis/business/service/impl/CacheServiceImpl.java

@@ -2,9 +2,13 @@ package com.qmth.themis.business.service.impl;
 
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.google.gson.Gson;
+import com.qmth.boot.core.solar.model.AppInfo;
+import com.qmth.boot.core.solar.model.OrgInfo;
+import com.qmth.boot.core.solar.service.SolarService;
 import com.qmth.themis.business.constant.SpringContextHolder;
 import com.qmth.themis.business.constant.SystemConstant;
 import com.qmth.themis.business.dto.AuthDto;
+import com.qmth.themis.business.dto.AuthOrgInfoDto;
 import com.qmth.themis.business.dto.cache.TEStudentCacheDto;
 import com.qmth.themis.business.entity.*;
 import com.qmth.themis.business.enums.RoleEnum;
@@ -57,6 +61,9 @@ public class CacheServiceImpl implements CacheService {
     @Resource
     SysConfigService sysConfigService;
 
+    @Resource
+    SolarService solarService;
+
     /**
      * 添加机构缓存
      *
@@ -133,6 +140,24 @@ public class CacheServiceImpl implements CacheService {
 
     }
 
+    /**
+     * 删除机构缓存
+     */
+    @Override
+    @CacheEvict(value = SystemConstant.orgCache, allEntries = true)
+    public void removeOrgIdCache() {
+
+    }
+
+    /**
+     * 删除机构缓存
+     */
+    @Override
+    @CacheEvict(value = SystemConstant.orgCodeCache, allEntries = true)
+    public void removeOrgCodeCache() {
+
+    }
+
     /**
      * 添加用户缓存
      *
@@ -338,4 +363,38 @@ public class CacheServiceImpl implements CacheService {
     public void removeSysConfigCache(String key) {
 
     }
+
+    @Override
+    @Cacheable(value = SystemConstant.AUTH_INFO_CACHE, key = "#p0", unless = "#result == null")
+    public AuthOrgInfoDto authInfoCache(String code) {
+        AppInfo appInfo = solarService.getAppInfo();
+        AuthOrgInfoDto authOrgInfoDto = null;
+        if (Objects.nonNull(appInfo)) {
+            List<OrgInfo> orgInfoList = solarService.getOrgList().stream().filter(s -> Objects.equals(s.getCode(), code)).collect(Collectors.toList());
+            if (Objects.nonNull(orgInfoList) && orgInfoList.size() > 0) {
+                authOrgInfoDto = new AuthOrgInfoDto(orgInfoList.get(0), appInfo.getControl());
+            }
+        }
+        return authOrgInfoDto;
+    }
+
+    @Override
+    @CachePut(value = SystemConstant.AUTH_INFO_CACHE, key = "#p0", unless = "#result == null")
+    public AuthOrgInfoDto updateAuthInfoCache(String code) {
+        AppInfo appInfo = solarService.getAppInfo();
+        AuthOrgInfoDto authOrgInfoDto = null;
+        if (Objects.nonNull(appInfo)) {
+            List<OrgInfo> orgInfoList = solarService.getOrgList().stream().filter(s -> Objects.equals(s.getCode(), code)).collect(Collectors.toList());
+            if (Objects.nonNull(orgInfoList) && orgInfoList.size() > 0) {
+                authOrgInfoDto = new AuthOrgInfoDto(orgInfoList.get(0), appInfo.getControl());
+            }
+        }
+        return authOrgInfoDto;
+    }
+
+    @Override
+    @CacheEvict(value = SystemConstant.AUTH_INFO_CACHE, key = "#p0")
+    public void removeAuthInfoCache(String code) {
+
+    }
 }

+ 20 - 0
themis-business/src/main/java/com/qmth/themis/business/service/impl/TBClientDownloadServiceImpl.java

@@ -0,0 +1,20 @@
+package com.qmth.themis.business.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.qmth.themis.business.dao.TBClientDownloadMapper;
+import com.qmth.themis.business.entity.TBClientDownload;
+import com.qmth.themis.business.service.TBClientDownloadService;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * 客户端下载管理 服务实现类
+ * </p>
+ *
+ * @author wangliang
+ * @since 2022-09-05
+ */
+@Service
+public class TBClientDownloadServiceImpl extends ServiceImpl<TBClientDownloadMapper, TBClientDownload> implements TBClientDownloadService {
+
+}

+ 39 - 0
themis-business/src/main/java/com/qmth/themis/business/service/impl/TEAudioServiceImpl.java

@@ -0,0 +1,39 @@
+package com.qmth.themis.business.service.impl;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.qmth.themis.business.dao.TEAudioMapper;
+import com.qmth.themis.business.entity.TEAudio;
+import com.qmth.themis.business.service.TEAudioService;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * <p>
+ * 场次音频表 服务实现类
+ * </p>
+ *
+ * @author wangliang
+ * @since 2022-08-30
+ */
+@Service
+public class TEAudioServiceImpl extends ServiceImpl<TEAudioMapper, TEAudio> implements TEAudioService {
+
+    @Resource
+    TEAudioMapper teAudioMapper;
+
+    /**
+     * 根据场次id查询音频信息
+     *
+     * @param iPage
+     * @param activityId
+     * @return
+     */
+    @Override
+    public IPage<TEAudio> query(IPage<Map> iPage, Long activityId) {
+        return teAudioMapper.query(iPage, activityId);
+    }
+}

+ 31 - 1
themis-business/src/main/java/com/qmth/themis/business/service/impl/TEExamActivityServiceImpl.java

@@ -1,5 +1,6 @@
 package com.qmth.themis.business.service.impl;
 
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.qmth.themis.business.cache.bean.ExamActivityCacheBean;
@@ -15,6 +16,7 @@ import com.qmth.themis.business.dto.response.TEExamActivityDto;
 import com.qmth.themis.business.dto.response.TEExamActivityQueryDto;
 import com.qmth.themis.business.dto.response.TEExamActivityWaitDto;
 import com.qmth.themis.business.entity.TBUser;
+import com.qmth.themis.business.entity.TEAudio;
 import com.qmth.themis.business.entity.TEExam;
 import com.qmth.themis.business.entity.TEExamActivity;
 import com.qmth.themis.business.enums.*;
@@ -28,6 +30,7 @@ import org.springframework.cache.annotation.Cacheable;
 import org.springframework.dao.DuplicateKeyException;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.CollectionUtils;
 
 import javax.annotation.Resource;
 import java.text.ParseException;
@@ -70,6 +73,9 @@ public class TEExamActivityServiceImpl extends ServiceImpl<TEExamActivityMapper,
     @Resource
     private MqUtil mqUtil;
 
+    @Resource
+    TEAudioService teAudioService;
+
     /**
      * 表是否存在
      *
@@ -318,7 +324,7 @@ public class TEExamActivityServiceImpl extends ServiceImpl<TEExamActivityMapper,
     @Transactional
     @Override
     public void saveExamActivity(List<TEExamActivity> teExamActivityList) {
-        if (Objects.isNull(teExamActivityList) || teExamActivityList.size() == 0) {
+        if (CollectionUtils.isEmpty(teExamActivityList)) {
             throw new BusinessException(ExceptionResultEnum.EXAM_INFO_IS_NULL);
         }
         Long examId = null;
@@ -331,16 +337,40 @@ public class TEExamActivityServiceImpl extends ServiceImpl<TEExamActivityMapper,
                     .equals(teExam.getMonitorStatus(), InvigilateMonitorStatusEnum.FINISHED)) {
                 throw new BusinessException("监考结束的考试场次不可以修改");
             }
+            List<TEAudio> teAudioList = new ArrayList<>();
             teExamActivityList.forEach(s -> {
                 if (Objects.nonNull(s.getId())) {
                     s.setUpdateId(tbUser.getId());
+                    QueryWrapper<TEAudio> teAudioQueryWrapper = new QueryWrapper<>();
+                    teAudioQueryWrapper.lambda().eq(TEAudio::getActivityId, s.getId())
+                            .eq(TEAudio::getType, AudioTypeEnum.BEFORE.name())
+                            .eq(TEAudio::getAudioDefault, AudioDefaultEnum.SYS.name());
+                    int count = teAudioService.count(teAudioQueryWrapper);
+                    if (count == 0) {
+                        teAudioList.add(new TEAudio(tbUser.getOrgId(), s.getId(), 1L, AudioTypeEnum.BEFORE.getTitle(), AudioTypeEnum.BEFORE, AudioDefaultEnum.SYS, tbUser.getId()));//开考前语音
+                    }
+
+                    teAudioQueryWrapper = new QueryWrapper<>();
+                    teAudioQueryWrapper.lambda().eq(TEAudio::getActivityId, s.getId())
+                            .eq(TEAudio::getType, AudioTypeEnum.AFTER.name())
+                            .eq(TEAudio::getAudioDefault, AudioDefaultEnum.SYS.name());
+                    count = teAudioService.count(teAudioQueryWrapper);
+                    if (count == 0) {
+                        teAudioList.add(new TEAudio(tbUser.getOrgId(), s.getId(), 2L, AudioTypeEnum.AFTER.getTitle(), AudioTypeEnum.AFTER, AudioDefaultEnum.SYS, tbUser.getId()));//考试结束前语音
+                    }
                 } else {
                     s.setId(uidUtil.getId());
                     s.setCreateId(tbUser.getId());
                     s.setCode(String.valueOf(redisUtil.getRedisActivityCodeSequence(s.getExamId())));
+                    teAudioList.add(new TEAudio(tbUser.getOrgId(), s.getId(), 1L, AudioTypeEnum.BEFORE.getTitle(), AudioTypeEnum.BEFORE, AudioDefaultEnum.SYS, tbUser.getId()));//开考前语音
+                    teAudioList.add(new TEAudio(tbUser.getOrgId(), s.getId(), 2L, AudioTypeEnum.AFTER.getTitle(), AudioTypeEnum.AFTER, AudioDefaultEnum.SYS, tbUser.getId()));//考试结束前语音
                 }
                 this.saveOrUpdate(s);
             });
+            if (!CollectionUtils.isEmpty(teAudioList)) {
+                teAudioService.saveOrUpdateBatch(teAudioList);
+            }
+
             TEExamActivityService teExamActivityService = SpringContextHolder.getBean(TEExamActivityService.class);
             for (TEExamActivity ac : teExamActivityList) {
                 teExamActivityService.updateExamActivityCacheBean(ac.getId());

+ 20 - 0
themis-business/src/main/java/com/qmth/themis/business/service/impl/TSAuthServiceImpl.java

@@ -0,0 +1,20 @@
+package com.qmth.themis.business.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.qmth.themis.business.dao.TSAuthMapper;
+import com.qmth.themis.business.entity.TSAuth;
+import com.qmth.themis.business.service.TSAuthService;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * 激活授权配置表 服务实现类
+ * </p>
+ *
+ * @author wangliang
+ * @since 2022-08-30
+ */
+@Service
+public class TSAuthServiceImpl extends ServiceImpl<TSAuthMapper, TSAuth> implements TSAuthService {
+
+}

+ 1 - 1
themis-business/src/main/java/com/qmth/themis/business/templete/service/impl/TempleteLogicServiceImpl.java

@@ -14,9 +14,9 @@ import com.qmth.themis.business.enums.RoleEnum;
 import com.qmth.themis.business.enums.TaskStatusEnum;
 import com.qmth.themis.business.service.*;
 import com.qmth.themis.business.templete.service.TempleteLogicService;
-import com.qmth.themis.business.util.Base64Util;
 import com.qmth.themis.common.contanst.Constants;
 import com.qmth.themis.common.exception.BusinessException;
+import com.qmth.themis.common.util.Base64Util;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.util.CollectionUtils;

+ 0 - 17
themis-business/src/main/java/com/qmth/themis/business/util/Base64Util.java

@@ -1,17 +0,0 @@
-package com.qmth.themis.business.util;
-
-import com.qmth.themis.business.constant.SystemConstant;
-
-import java.io.UnsupportedEncodingException;
-import java.util.Base64;
-
-public class Base64Util {
-
-    public static String encode(byte[] input) throws UnsupportedEncodingException {
-        return new String(Base64.getEncoder().encode(input), SystemConstant.CHARSET_NAME);
-    }
-
-    public static byte[] decode(String input) throws UnsupportedEncodingException {
-        return Base64.getDecoder().decode(input.getBytes(SystemConstant.CHARSET_NAME));
-    }
-}

+ 21 - 0
themis-business/src/main/java/com/qmth/themis/business/util/OssUtil.java

@@ -135,6 +135,27 @@ public class OssUtil {
         log.info("objectName:{},requestid:{}", objectName, por.getRequestId());
     }
 
+    /**
+     * oss断点续传文件
+     *
+     * @param isPublic
+     * @param objectName
+     * @param file
+     */
+    public void uploadPart(boolean isPublic, String objectName, File file) throws IOException {
+        log.info("oss  upload file is come in");
+        String bucket = isPublic ? aliYunOssPublicDomain.getPublicBucket() : aliYunOssPrivateDomain.getPrivateBucket();
+        OSS client = isPublic ? publicClient : privateClient;
+        ObjectMetadata meta = new ObjectMetadata();
+        // 设置MD5校验。
+        String md5 = BinaryUtil.toBase64String(BinaryUtil.calculateMd5(FileUtils.readFileToByteArray(file)));
+        meta.setContentMD5(md5);
+        // 上传内容到指定的存储空间(bucketName)并保存为指定的文件名称(objectName)。
+        PutObjectRequest putObjectRequest = new PutObjectRequest(bucket, objectName, file, meta);
+        PutObjectResult por = client.putObject(putObjectRequest);
+        log.info("objectName:{},requestid:{}", objectName, por.getRequestId());
+    }
+
     /**
      * oss上传数据内容
      *

+ 2 - 2
themis-business/src/main/resources/db/init-table-data.sql

@@ -7,8 +7,8 @@ INSERT INTO `sys_config` VALUES (3, NULL, 'online.warn.scaleSize', '在线人数
 INSERT INTO `t_b_app_version` VALUES (1, 'v1.1.11', 13, 'http://d.maps9.com/wlv3', 'BUG修复', 'ANDROID', 1, NULL, NULL, NULL, NULL);
 INSERT INTO `t_b_app_version` VALUES (2, '1.1.11', 14, 'https://cdn.online-exam.cn/mobile/wap/index.html', 'V1.1.11', 'IOS', 1, NULL, NULL, NULL, NULL);
 
-INSERT INTO `t_b_attachment` VALUES (1, '开考前语音', NULL, '.mp3', 25, 'e86c65e150ccf2209c6a122241efe43f', '{\"path\":\"after/after.mp3\",\"uploadType\":3,\"type\":\"oss\"}', 1659325817027, 279187026930577408);
-INSERT INTO `t_b_attachment` VALUES (2, '考试结束前语音', NULL, '.mp3', 25, '8109552faf9bc1253730ccdedd429ddf', '{\"path\":\"before/before.mp3\",\"uploadType\":3,\"type\":\"oss\"}', 1659325817027, 279187026930577408);
+INSERT INTO `t_b_attachment` VALUES (1, '开考前语音', NULL, '.mp3', 2.08, 'e86c65e150ccf2209c6a122241efe43f', '{\"path\":\"upload/after/after.mp3\",\"uploadType\":2,\"type\":\"oss\"}', 1659325817027, 279187026930577408);
+INSERT INTO `t_b_attachment` VALUES (2, '考试结束前语音', NULL, '.mp3', 0.79, '8109552faf9bc1253730ccdedd429ddf', '{\"path\":\"upload/before/before.mp3\",\"uploadType\":2,\"type\":\"oss\"}', 1659325817027, 279187026930577408);
 
 INSERT INTO `t_b_client_version` VALUES (1, '1.0.0', 10, 'http://qmth-test.oss-cn-shenzhen.aliyuncs.com/client/v1.0.0/index.json', '1.0版本', 1, NULL, NULL, NULL, NULL);
 INSERT INTO `t_b_client_version` VALUES (2, '1.1.0', 11, 'http://qmth-test.oss-cn-shenzhen.aliyuncs.com/client/v1.1.0/index.json', '1.1版本', 1, NULL, NULL, NULL, NULL);

+ 188 - 2
themis-business/src/main/resources/db/init-table.sql

@@ -1,5 +1,193 @@
 SET NAMES UTF8;
 
+-- ----------------------------
+-- Table structure for qrtz_blob_triggers
+-- ----------------------------
+DROP TABLE IF EXISTS `qrtz_blob_triggers`;
+CREATE TABLE `qrtz_blob_triggers` (
+                                      `SCHED_NAME` varchar(120) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
+                                      `TRIGGER_NAME` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
+                                      `TRIGGER_GROUP` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
+                                      `BLOB_DATA` blob,
+                                      PRIMARY KEY (`SCHED_NAME`,`TRIGGER_NAME`,`TRIGGER_GROUP`) USING BTREE,
+                                      KEY `SCHED_NAME` (`SCHED_NAME`,`TRIGGER_NAME`,`TRIGGER_GROUP`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- ----------------------------
+-- Table structure for qrtz_calendars
+-- ----------------------------
+DROP TABLE IF EXISTS `qrtz_calendars`;
+CREATE TABLE `qrtz_calendars` (
+                                  `SCHED_NAME` varchar(120) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
+                                  `CALENDAR_NAME` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
+                                  `CALENDAR` blob NOT NULL,
+                                  PRIMARY KEY (`SCHED_NAME`,`CALENDAR_NAME`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- ----------------------------
+-- Table structure for qrtz_cron_triggers
+-- ----------------------------
+DROP TABLE IF EXISTS `qrtz_cron_triggers`;
+CREATE TABLE `qrtz_cron_triggers` (
+                                      `SCHED_NAME` varchar(120) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
+                                      `TRIGGER_NAME` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
+                                      `TRIGGER_GROUP` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
+                                      `CRON_EXPRESSION` varchar(120) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
+                                      `TIME_ZONE_ID` varchar(80) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
+                                      PRIMARY KEY (`SCHED_NAME`,`TRIGGER_NAME`,`TRIGGER_GROUP`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- ----------------------------
+-- Table structure for qrtz_fired_triggers
+-- ----------------------------
+DROP TABLE IF EXISTS `qrtz_fired_triggers`;
+CREATE TABLE `qrtz_fired_triggers` (
+                                       `SCHED_NAME` varchar(120) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
+                                       `ENTRY_ID` varchar(95) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
+                                       `TRIGGER_NAME` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
+                                       `TRIGGER_GROUP` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
+                                       `INSTANCE_NAME` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
+                                       `FIRED_TIME` bigint NOT NULL,
+                                       `SCHED_TIME` bigint NOT NULL,
+                                       `PRIORITY` int NOT NULL,
+                                       `STATE` varchar(16) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
+                                       `JOB_NAME` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
+                                       `JOB_GROUP` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
+                                       `IS_NONCONCURRENT` varchar(1) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
+                                       `REQUESTS_RECOVERY` varchar(1) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
+                                       PRIMARY KEY (`SCHED_NAME`,`ENTRY_ID`) USING BTREE,
+                                       KEY `IDX_QRTZ_FT_TRIG_INST_NAME` (`SCHED_NAME`,`INSTANCE_NAME`) USING BTREE,
+                                       KEY `IDX_QRTZ_FT_INST_JOB_REQ_RCVRY` (`SCHED_NAME`,`INSTANCE_NAME`,`REQUESTS_RECOVERY`) USING BTREE,
+                                       KEY `IDX_QRTZ_FT_J_G` (`SCHED_NAME`,`JOB_NAME`,`JOB_GROUP`) USING BTREE,
+                                       KEY `IDX_QRTZ_FT_JG` (`SCHED_NAME`,`JOB_GROUP`) USING BTREE,
+                                       KEY `IDX_QRTZ_FT_T_G` (`SCHED_NAME`,`TRIGGER_NAME`,`TRIGGER_GROUP`) USING BTREE,
+                                       KEY `IDX_QRTZ_FT_TG` (`SCHED_NAME`,`TRIGGER_GROUP`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- ----------------------------
+-- Table structure for qrtz_job_details
+-- ----------------------------
+DROP TABLE IF EXISTS `qrtz_job_details`;
+CREATE TABLE `qrtz_job_details` (
+                                    `SCHED_NAME` varchar(120) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
+                                    `JOB_NAME` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
+                                    `JOB_GROUP` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
+                                    `DESCRIPTION` varchar(250) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
+                                    `JOB_CLASS_NAME` varchar(250) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
+                                    `IS_DURABLE` varchar(1) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
+                                    `IS_NONCONCURRENT` varchar(1) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
+                                    `IS_UPDATE_DATA` varchar(1) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
+                                    `REQUESTS_RECOVERY` varchar(1) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
+                                    `JOB_DATA` blob,
+                                    PRIMARY KEY (`SCHED_NAME`,`JOB_NAME`,`JOB_GROUP`) USING BTREE,
+                                    KEY `IDX_QRTZ_J_REQ_RECOVERY` (`SCHED_NAME`,`REQUESTS_RECOVERY`) USING BTREE,
+                                    KEY `IDX_QRTZ_J_GRP` (`SCHED_NAME`,`JOB_GROUP`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- ----------------------------
+-- Table structure for qrtz_locks
+-- ----------------------------
+DROP TABLE IF EXISTS `qrtz_locks`;
+CREATE TABLE `qrtz_locks` (
+                              `SCHED_NAME` varchar(120) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
+                              `LOCK_NAME` varchar(40) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
+                              PRIMARY KEY (`SCHED_NAME`,`LOCK_NAME`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- ----------------------------
+-- Table structure for qrtz_paused_trigger_grps
+-- ----------------------------
+DROP TABLE IF EXISTS `qrtz_paused_trigger_grps`;
+CREATE TABLE `qrtz_paused_trigger_grps` (
+                                            `SCHED_NAME` varchar(120) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
+                                            `TRIGGER_GROUP` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
+                                            PRIMARY KEY (`SCHED_NAME`,`TRIGGER_GROUP`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- ----------------------------
+-- Table structure for qrtz_scheduler_state
+-- ----------------------------
+DROP TABLE IF EXISTS `qrtz_scheduler_state`;
+CREATE TABLE `qrtz_scheduler_state` (
+                                        `SCHED_NAME` varchar(120) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
+                                        `INSTANCE_NAME` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
+                                        `LAST_CHECKIN_TIME` bigint NOT NULL,
+                                        `CHECKIN_INTERVAL` bigint NOT NULL,
+                                        PRIMARY KEY (`SCHED_NAME`,`INSTANCE_NAME`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- ----------------------------
+-- Table structure for qrtz_simple_triggers
+-- ----------------------------
+DROP TABLE IF EXISTS `qrtz_simple_triggers`;
+CREATE TABLE `qrtz_simple_triggers` (
+                                        `SCHED_NAME` varchar(120) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
+                                        `TRIGGER_NAME` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
+                                        `TRIGGER_GROUP` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
+                                        `REPEAT_COUNT` bigint NOT NULL,
+                                        `REPEAT_INTERVAL` bigint NOT NULL,
+                                        `TIMES_TRIGGERED` bigint NOT NULL,
+                                        PRIMARY KEY (`SCHED_NAME`,`TRIGGER_NAME`,`TRIGGER_GROUP`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- ----------------------------
+-- Table structure for qrtz_simprop_triggers
+-- ----------------------------
+DROP TABLE IF EXISTS `qrtz_simprop_triggers`;
+CREATE TABLE `qrtz_simprop_triggers` (
+                                         `SCHED_NAME` varchar(120) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
+                                         `TRIGGER_NAME` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
+                                         `TRIGGER_GROUP` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
+                                         `STR_PROP_1` varchar(512) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
+                                         `STR_PROP_2` varchar(512) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
+                                         `STR_PROP_3` varchar(512) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
+                                         `INT_PROP_1` int DEFAULT NULL,
+                                         `INT_PROP_2` int DEFAULT NULL,
+                                         `LONG_PROP_1` bigint DEFAULT NULL,
+                                         `LONG_PROP_2` bigint DEFAULT NULL,
+                                         `DEC_PROP_1` decimal(13,4) DEFAULT NULL,
+                                         `DEC_PROP_2` decimal(13,4) DEFAULT NULL,
+                                         `BOOL_PROP_1` varchar(1) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
+                                         `BOOL_PROP_2` varchar(1) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
+                                         PRIMARY KEY (`SCHED_NAME`,`TRIGGER_NAME`,`TRIGGER_GROUP`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- ----------------------------
+-- Table structure for qrtz_triggers
+-- ----------------------------
+DROP TABLE IF EXISTS `qrtz_triggers`;
+CREATE TABLE `qrtz_triggers` (
+                                 `SCHED_NAME` varchar(120) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
+                                 `TRIGGER_NAME` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
+                                 `TRIGGER_GROUP` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
+                                 `JOB_NAME` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
+                                 `JOB_GROUP` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
+                                 `DESCRIPTION` varchar(250) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
+                                 `NEXT_FIRE_TIME` bigint DEFAULT NULL,
+                                 `PREV_FIRE_TIME` bigint DEFAULT NULL,
+                                 `PRIORITY` int DEFAULT NULL,
+                                 `TRIGGER_STATE` varchar(16) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
+                                 `TRIGGER_TYPE` varchar(8) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
+                                 `START_TIME` bigint NOT NULL,
+                                 `END_TIME` bigint DEFAULT NULL,
+                                 `CALENDAR_NAME` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
+                                 `MISFIRE_INSTR` smallint DEFAULT NULL,
+                                 `JOB_DATA` blob,
+                                 PRIMARY KEY (`SCHED_NAME`,`TRIGGER_NAME`,`TRIGGER_GROUP`) USING BTREE,
+                                 KEY `IDX_QRTZ_T_J` (`SCHED_NAME`,`JOB_NAME`,`JOB_GROUP`) USING BTREE,
+                                 KEY `IDX_QRTZ_T_JG` (`SCHED_NAME`,`JOB_GROUP`) USING BTREE,
+                                 KEY `IDX_QRTZ_T_C` (`SCHED_NAME`,`CALENDAR_NAME`) USING BTREE,
+                                 KEY `IDX_QRTZ_T_G` (`SCHED_NAME`,`TRIGGER_GROUP`) USING BTREE,
+                                 KEY `IDX_QRTZ_T_STATE` (`SCHED_NAME`,`TRIGGER_STATE`) USING BTREE,
+                                 KEY `IDX_QRTZ_T_N_STATE` (`SCHED_NAME`,`TRIGGER_NAME`,`TRIGGER_GROUP`,`TRIGGER_STATE`) USING BTREE,
+                                 KEY `IDX_QRTZ_T_N_G_STATE` (`SCHED_NAME`,`TRIGGER_GROUP`,`TRIGGER_STATE`) USING BTREE,
+                                 KEY `IDX_QRTZ_T_NEXT_FIRE_TIME` (`SCHED_NAME`,`NEXT_FIRE_TIME`) USING BTREE,
+                                 KEY `IDX_QRTZ_T_NFT_ST` (`SCHED_NAME`,`TRIGGER_STATE`,`NEXT_FIRE_TIME`) USING BTREE,
+                                 KEY `IDX_QRTZ_T_NFT_MISFIRE` (`SCHED_NAME`,`MISFIRE_INSTR`,`NEXT_FIRE_TIME`) USING BTREE,
+                                 KEY `IDX_QRTZ_T_NFT_ST_MISFIRE` (`SCHED_NAME`,`MISFIRE_INSTR`,`NEXT_FIRE_TIME`,`TRIGGER_STATE`) USING BTREE,
+                                 KEY `IDX_QRTZ_T_NFT_ST_MISFIRE_GRP` (`SCHED_NAME`,`MISFIRE_INSTR`,`NEXT_FIRE_TIME`,`TRIGGER_GROUP`,`TRIGGER_STATE`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
 -- ----------------------------
 -- Table structure for sys_config
 -- ----------------------------
@@ -278,9 +466,7 @@ CREATE TABLE `t_e_audio` (
                              `attachment_id` bigint NOT NULL COMMENT '附件id',
                              `content` varchar(1000) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '语音内容',
                              `type` varchar(30) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL COMMENT '类别,BEFORE:开考前语音,AFTER:考试结束前语音',
-                             `play_time` int DEFAULT NULL COMMENT '播放时长(秒)',
                              `enable` tinyint NOT NULL DEFAULT '1' COMMENT '是否启用,0:停用,1:启用',
-                             `duration` int DEFAULT NULL COMMENT '音频时长',
                              `audio_default` varchar(30) COLLATE utf8_unicode_ci NOT NULL DEFAULT 'SYS' COMMENT '是否默认,SYS:系统内置,CUSTOM:自定义',
                              `create_id` bigint DEFAULT NULL COMMENT '创建人id',
                              `create_time` bigint DEFAULT NULL COMMENT '创建时间',

+ 5 - 0
themis-business/src/main/resources/mapper/TBClientDownloadMapper.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.qmth.themis.business.dao.TBClientDownloadMapper">
+
+</mapper>

+ 56 - 0
themis-business/src/main/resources/mapper/TEAudioMapper.xml

@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.qmth.themis.business.dao.TEAudioMapper">
+
+    <select id="query" resultType="com.qmth.themis.business.entity.TEAudio">
+        select
+        t.id,
+        t.orgId,
+        t.activityId,
+        t.content,
+        t.`type`,
+        t.enable,
+        t.audioDefault,
+        t.attachmentId,
+        if(t.updateName is not null,
+        t.updateName,
+        t.createName) as updateName,
+        if(t.updateTime is not null,
+        t.updateTime,
+        t.createTime) as updateTime
+        from
+        (
+        select
+        tea.id,
+        tea.org_id as orgId,
+        tea.activity_id as activityId,
+        tea.content,
+        tea.`type`,
+        tea.enable,
+        tea.audio_default as audioDefault,
+        tea.attachment_id as attachmentId,
+        (
+        select
+        t.name
+        from
+        t_b_user t
+        where
+        t.id = tea.create_id) as createName,
+        tea.create_time as createTime,
+        (
+        select
+        t.name
+        from
+        t_b_user t
+        where
+        t.id = tea.update_id) as updateName,
+        tea.update_time as updateTime
+        from
+        t_e_audio tea
+        <where> 1 = 1
+            <if test="activityId != null and activityId != ''">
+                and tea.activity_id = #{activityId}
+            </if>
+        </where>) t
+    </select>
+</mapper>

+ 5 - 0
themis-business/src/main/resources/mapper/TSAuthMapper.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.qmth.themis.business.dao.TSAuthMapper">
+
+</mapper>

+ 4 - 0
themis-common/pom.xml

@@ -98,5 +98,9 @@
             <groupId>org.lionsoul</groupId>
             <artifactId>ip2region</artifactId>
         </dependency>
+        <dependency>
+            <groupId>com.qmth.boot</groupId>
+            <artifactId>core-solar</artifactId>
+        </dependency>
     </dependencies>
 </project>