Browse Source

远程下载试卷

jinxia.mo 2 days ago
parent
commit
7204ddf45b

+ 1 - 1
back-ui/src/views/dz/papers/components/list-paper-records.vue

@@ -276,7 +276,7 @@ const columns = [
     { label: '题数/总分', prop: 'questionInfo', width: 100, type: 'slot', slotName: 'questionInfo' },
     { label: '时长(分钟)', prop: 'duration', width: 120, type: 'slot', slotName: 'duration' },
     { label: '创建时间', prop: 'createTime', width: 160, type: 'slot', slotName: 'createTime' },
-    { label: '下载', prop: 'download', width: 80, type: 'slot', slotName: 'download' }
+    { label: '操作', prop: 'download', width: 80, type: 'slot', slotName: 'download' }
 ]
 
 // 操作按钮

+ 30 - 2
ie-admin/src/main/java/com/ruoyi/web/controller/learn/LearnPaperController.java

@@ -1,10 +1,15 @@
 package com.ruoyi.web.controller.learn;
 
+import java.nio.charset.StandardCharsets;
 import java.util.List;
-import javax.annotation.security.PermitAll;
 import javax.servlet.http.HttpServletResponse;
 
+import com.alibaba.fastjson.JSONArray;
 import com.ruoyi.common.annotation.Anonymous;
+import com.ruoyi.common.utils.http.HttpUtils;
+import com.ruoyi.learn.domain.LearnQuestions;
+import com.ruoyi.learn.service.ILearnQuestionsService;
+import com.ruoyi.system.service.ISysConfigService;
 import com.ruoyi.web.service.CommService;
 import com.ruoyi.web.service.PaperService;
 import io.swagger.annotations.Api;
@@ -40,6 +45,8 @@ public class LearnPaperController extends BaseController
     private PaperService paperService;
     @Autowired
     private CommService commService;
+    @Autowired
+    private ISysConfigService configService;
 
     /**
      * 查询试卷列表
@@ -124,6 +131,27 @@ public class LearnPaperController extends BaseController
     @PreAuthorize("@ss.hasPermi('learn:paper:download')")
     @RequestMapping(value = "download", method = {RequestMethod.GET, RequestMethod.POST})
     public void download(HttpServletResponse response, Long paperId) {
-        commService.download(response, paperId);
+        Boolean isRemote = Boolean.parseBoolean(configService.selectConfigByKey("is.remote", "false"));
+        if (isRemote) {
+            downloadRemote(response, paperId);
+        } else {
+            commService.download(response, paperId);
+        }
+    }
+
+    @ApiOperation("下载试卷(远程)")
+    @RequestMapping(value = "downloadRemote", method = {RequestMethod.GET, RequestMethod.POST})
+    @Anonymous
+    public void downloadRemote(HttpServletResponse response, Long paperId) {
+        String url = configService.selectConfigByKey("download.paper.url", "https://www.dz1kt.com/prod-api");
+        commService.downloadRemote(response, paperId,url);
+    }
+
+    @ApiOperation("通用:下载试卷")
+    @RequestMapping(value = "downloadPaper", method = {RequestMethod.GET, RequestMethod.POST})
+    @Anonymous
+    public void downloadPaper(HttpServletResponse response, String paperName, String ques) {
+        List<LearnQuestions> questions = JSONArray.parseArray(ques, LearnQuestions.class);
+        commService.download(response, paperName, questions);
     }
 }

+ 58 - 0
ie-admin/src/main/java/com/ruoyi/web/service/CommService.java

@@ -1,5 +1,6 @@
 package com.ruoyi.web.service;
 
+import com.alibaba.fastjson.JSONArray;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import com.ruoyi.common.core.content.VistorContextHolder;
@@ -10,6 +11,7 @@ import com.ruoyi.common.exception.CustomException;
 import com.ruoyi.common.utils.DateUtils;
 import com.ruoyi.common.utils.SecurityUtils;
 import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.http.HttpUtils;
 import com.ruoyi.dz.domain.DzCards;
 import com.ruoyi.dz.service.IDzCardsService;
 import com.ruoyi.enums.CardTimeStatus;
@@ -28,6 +30,7 @@ import org.springframework.stereotype.Service;
 import org.springframework.util.CollectionUtils;
 
 import javax.servlet.http.HttpServletResponse;
+import java.nio.charset.StandardCharsets;
 import java.time.LocalDate;
 import java.time.ZoneId;
 import java.util.Date;
@@ -198,8 +201,63 @@ public class CommService {
         } catch (Exception e) {
             throw new RuntimeException(e.getMessage(), e);
         }
+    }
+
+    public void downloadRemote(HttpServletResponse response, Long paperId,String url) {
+        try {
+            // 获取试卷信息
+            LearnPaper paper = paperService.selectLearnPaperById(paperId);
+            if (paper == null) {
+                throw new RuntimeException("试卷不存在");
+            }
+
+            // 获取题目列表
+            List<LearnQuestions> questions = learnQuestionsService.selectQuestionByPaperId(paperId);
+
+            // 将题目列表转换为 JSON 字符串
+            String quesJson = JSONArray.toJSONString(questions);
+
+            // 远程服务器地址
+            String remoteUrl = (StringUtils.isEmpty(url)?"https://www.dz1kt.com/prod-api":url)+"/learn/paper/downloadPaper";
+
+            // 构建请求参数
+            String params = "paperName=" + java.net.URLEncoder.encode(paper.getPaperName(), StandardCharsets.UTF_8.toString())
+                    + "&ques=" + java.net.URLEncoder.encode(quesJson, StandardCharsets.UTF_8.toString());
+
+            // 设置响应头
+            response.setHeader("Pragma", "No-cache");
+            response.setHeader("Cache-Control", "No-cache");
+            response.setHeader("Expires", "0");
+            response.setHeader("Content-Disposition", String.format("attachment; filename=\"%s\"",
+                    new String((paper.getPaperName() + ".docx").getBytes("utf-8"), "ISO-8859-1")));
+            response.setHeader("Content-Type", "application/octet-stream");
+
+            // 调用远程服务器并将响应流写入到本地响应
+            HttpUtils.sendGetStream(remoteUrl, params, response.getOutputStream());
+        } catch (Exception e) {
+            throw new RuntimeException("下载失败: " + e.getMessage(), e);
+        }
+    }
 
+    /**
+     * 通用:下载试卷(外部传入数据),不依赖本地数据
+     * @param response
+     * @param paperName
+     * @param questions
+     */
+    public void download(HttpServletResponse response, String paperName,List<LearnQuestions> questions){
+        try {
+            byte[] data = DownloadPaperUtils.download(paperName, questions);
 
+            response.setHeader("Pragma", "No-cache");
+            response.setHeader("Cache-Control", "No-cache");
+            response.setHeader("Expires", "0");
+            response.setHeader("Content-Disposition",  String.format("attachment; filename=\"%s\"", new String((paperName + ".docx").getBytes("utf-8"), "ISO-8859-1")));
+            response.setHeader("Content-Type", "application/octet-stream");
+            IOUtils.write(data, response.getOutputStream());
+        } catch (Exception e) {
+            throw new RuntimeException(e.getMessage(), e);
+        }
     }
 
 

+ 82 - 0
ie-common/src/main/java/com/ruoyi/common/utils/http/HttpUtils.java

@@ -4,12 +4,15 @@ import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
+import java.io.OutputStream;
 import java.io.PrintWriter;
 import java.net.ConnectException;
+import java.net.HttpURLConnection;
 import java.net.SocketTimeoutException;
 import java.net.URL;
 import java.net.URLConnection;
 import java.nio.charset.StandardCharsets;
+import org.apache.commons.io.IOUtils;
 import java.security.cert.X509Certificate;
 import javax.net.ssl.HostnameVerifier;
 import javax.net.ssl.HttpsURLConnection;
@@ -290,4 +293,83 @@ public class HttpUtils
             return true;
         }
     }
+
+    /**
+     * 向指定 URL 发送GET方法的请求,并将响应流写入到指定的输出流
+     *
+     * @param url 发送请求的 URL
+     * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
+     * @param outputStream 输出流,用于写入响应数据
+     * @throws IOException IO异常
+     */
+    public static void sendGetStream(String url, String param, OutputStream outputStream) throws IOException
+    {
+        HttpURLConnection connection = null;
+        InputStream inputStream = null;
+        try
+        {
+            String urlNameString = StringUtils.isNotBlank(param) ? url + "?" + param : url;
+            log.info("sendGetStream - {}", urlNameString);
+            URL realUrl = new URL(urlNameString);
+            connection = (HttpURLConnection) realUrl.openConnection();
+            connection.setRequestMethod("GET");
+            connection.setConnectTimeout(30000);
+            connection.setReadTimeout(30000);
+            connection.setRequestProperty("accept", "*/*");
+            connection.setRequestProperty("connection", "Keep-Alive");
+            connection.setRequestProperty("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)");
+            connection.setDoInput(true);
+            connection.connect();
+            
+            int responseCode = connection.getResponseCode();
+            if (responseCode == HttpURLConnection.HTTP_OK)
+            {
+                inputStream = connection.getInputStream();
+                IOUtils.copy(inputStream, outputStream);
+                outputStream.flush();
+            }
+            else
+            {
+                throw new IOException("远程服务器返回错误: " + responseCode);
+            }
+        }
+        catch (ConnectException e)
+        {
+            log.error("调用HttpUtils.sendGetStream ConnectException, url=" + url + ",param=" + param, e);
+            throw e;
+        }
+        catch (SocketTimeoutException e)
+        {
+            log.error("调用HttpUtils.sendGetStream SocketTimeoutException, url=" + url + ",param=" + param, e);
+            throw e;
+        }
+        catch (IOException e)
+        {
+            log.error("调用HttpUtils.sendGetStream IOException, url=" + url + ",param=" + param, e);
+            throw e;
+        }
+        catch (Exception e)
+        {
+            log.error("调用HttpUtils.sendGetStream Exception, url=" + url + ",param=" + param, e);
+            throw new IOException("调用远程服务器失败", e);
+        }
+        finally
+        {
+            try
+            {
+                if (inputStream != null)
+                {
+                    inputStream.close();
+                }
+                if (connection != null)
+                {
+                    connection.disconnect();
+                }
+            }
+            catch (Exception ex)
+            {
+                log.error("调用sendGetStream关闭资源 Exception, url=" + url + ",param=" + param, ex);
+            }
+        }
+    }
 }

+ 10 - 10
ie-system/src/main/java/com/ruoyi/system/service/ISysConfigService.java

@@ -5,14 +5,14 @@ import com.ruoyi.system.domain.SysConfig;
 
 /**
  * 参数配置 服务层
- * 
+ *
  * @author ruoyi
  */
 public interface ISysConfigService
 {
     /**
      * 查询参数配置信息
-     * 
+     *
      * @param configId 参数配置ID
      * @return 参数配置信息
      */
@@ -20,15 +20,15 @@ public interface ISysConfigService
 
     /**
      * 根据键名查询参数配置信息
-     * 
+     *
      * @param configKey 参数键名
      * @return 参数键值
      */
     public String selectConfigByKey(String configKey);
-
+    public String selectConfigByKey(String configKey, String defaultValue);
     /**
      * 获取验证码开关
-     * 
+     *
      * @return true开启,false关闭
      */
     public boolean selectCaptchaEnabled();
@@ -48,7 +48,7 @@ public interface ISysConfigService
 
     /**
      * 查询参数配置列表
-     * 
+     *
      * @param config 参数配置信息
      * @return 参数配置集合
      */
@@ -56,7 +56,7 @@ public interface ISysConfigService
 
     /**
      * 新增参数配置
-     * 
+     *
      * @param config 参数配置信息
      * @return 结果
      */
@@ -64,7 +64,7 @@ public interface ISysConfigService
 
     /**
      * 修改参数配置
-     * 
+     *
      * @param config 参数配置信息
      * @return 结果
      */
@@ -72,7 +72,7 @@ public interface ISysConfigService
 
     /**
      * 批量删除参数信息
-     * 
+     *
      * @param configIds 需要删除的参数ID
      */
     public void deleteConfigByIds(Long[] configIds);
@@ -94,7 +94,7 @@ public interface ISysConfigService
 
     /**
      * 校验参数键名是否唯一
-     * 
+     *
      * @param config 参数信息
      * @return 结果
      */

+ 21 - 10
ie-system/src/main/java/com/ruoyi/system/service/impl/SysConfigServiceImpl.java

@@ -19,7 +19,7 @@ import com.ruoyi.system.service.ISysConfigService;
 
 /**
  * 参数配置 服务层实现
- * 
+ *
  * @author ruoyi
  */
 @Service
@@ -42,7 +42,7 @@ public class SysConfigServiceImpl implements ISysConfigService
 
     /**
      * 查询参数配置信息
-     * 
+     *
      * @param configId 参数配置ID
      * @return 参数配置信息
      */
@@ -57,7 +57,7 @@ public class SysConfigServiceImpl implements ISysConfigService
 
     /**
      * 根据键名查询参数配置信息
-     * 
+     *
      * @param configKey 参数key
      * @return 参数键值
      */
@@ -80,9 +80,20 @@ public class SysConfigServiceImpl implements ISysConfigService
         return StringUtils.EMPTY;
     }
 
+    @Override
+    public String selectConfigByKey(String configKey, String defaultValue)
+    {
+        String configValue = selectConfigByKey(configKey);
+        if (StringUtils.isEmpty(configValue))
+        {
+            return defaultValue;
+        }
+        return configValue;
+    }
+
     /**
      * 获取验证码开关
-     * 
+     *
      * @return true开启,false关闭
      */
     @Override
@@ -120,7 +131,7 @@ public class SysConfigServiceImpl implements ISysConfigService
 
     /**
      * 查询参数配置列表
-     * 
+     *
      * @param config 参数配置信息
      * @return 参数配置集合
      */
@@ -132,7 +143,7 @@ public class SysConfigServiceImpl implements ISysConfigService
 
     /**
      * 新增参数配置
-     * 
+     *
      * @param config 参数配置信息
      * @return 结果
      */
@@ -149,7 +160,7 @@ public class SysConfigServiceImpl implements ISysConfigService
 
     /**
      * 修改参数配置
-     * 
+     *
      * @param config 参数配置信息
      * @return 结果
      */
@@ -172,7 +183,7 @@ public class SysConfigServiceImpl implements ISysConfigService
 
     /**
      * 批量删除参数信息
-     * 
+     *
      * @param configIds 需要删除的参数ID
      */
     @Override
@@ -225,7 +236,7 @@ public class SysConfigServiceImpl implements ISysConfigService
 
     /**
      * 校验参数键名是否唯一
-     * 
+     *
      * @param config 参数配置信息
      * @return 结果
      */
@@ -248,7 +259,7 @@ public class SysConfigServiceImpl implements ISysConfigService
 
     /**
      * 设置cache key
-     * 
+     *
      * @param configKey 参数键
      * @return 缓存键key
      */