Selaa lähdekoodia

禁止网络请求

dsy 9 kuukautta sitten
vanhempi
commit
feaa3a2670
6 muutettua tiedostoa jossa 97 lisäystä ja 80 poistoa
  1. 32 10
      main.js
  2. 9 9
      preload.js
  3. 12 50
      src/components/CommonPreview.vue
  4. 35 3
      src/utils/http.js
  5. 8 8
      src/views/eep_view/index.vue
  6. 1 0
      src/views/select_eep/index.vue

+ 32 - 10
main.js

@@ -25,6 +25,28 @@ const createWindow = () => {
     },
   });
 
+  // 全局拦截会话级别的网络请求,禁止所有外部网络访问(离线包专用)
+  try {
+    const ses = win.webContents.session;
+    // 拦截所有协议的请求,允许本地资源协议通过
+    ses.webRequest.onBeforeRequest({ urls: ['*://*/*'] }, (details, callback) => {
+      const u = details && details.url ? String(details.url) : '';
+      // 允许本地文件和 data/ about 等内部资源
+      if (
+        u.startsWith('file:') ||
+        u.startsWith('data:') ||
+        u.startsWith('about:') ||
+        u.startsWith('chrome-devtools://')
+      ) {
+        return callback({ cancel: false });
+      }
+      // 其它所有网络请求全部取消 (http/https/ftp/...)。
+      return callback({ cancel: true });
+    });
+  } catch (e) {
+    console.error('Failed to set webRequest blocker:', e);
+  }
+
   win.loadURL(
     url.format({
       pathname: path.join(__dirname, './dist', 'index.html'),
@@ -85,14 +107,14 @@ app.whenReady().then(() => {
  * 使用 7z 解压(支持 -p 密码),返回临时目录与条目列表
  * @param {string} filePath EEP 文件路径
  * @param {string} password 密码
- * @returns {Promise<{tmpDir: string, entries: Array<{entryName: string, isDirectory: boolean}>}>} 解压结果
+ * @returns {Promise<{tempDir: string, entries: Array<{entryName: string, isDirectory: boolean}>}>} 解压结果
  */
-let _tmpDirs = '';
+let _tempDirs = '';
 ipcMain.handle('extract-zip', async (event, { filePath, password }) => {
-  const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'eep-'));
-  _tmpDirs = tmpDir;
+  const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'eep-'));
+  _tempDirs = tempDir;
   const sevenPath = sevenBin.path7za; // 可跨平台
-  const args = ['x', filePath, `-o${tmpDir}`, '-y'];
+  const args = ['x', filePath, `-o${tempDir}`, '-y'];
   if (password) args.push(`-p${password}`);
 
   await new Promise((resolve, reject) => {
@@ -125,7 +147,7 @@ ipcMain.handle('extract-zip', async (event, { filePath, password }) => {
     isDirectory: e.isDirectory,
   }));
 
-  return { tmpDir, entries };
+  return { tempDir, entries };
 });
 
 /**
@@ -253,13 +275,13 @@ ipcMain.on('install-update', (event, filePath) => {
 app.on('window-all-closed', () => {
   if (process.platform !== 'darwin') {
     // 清理临时目录
-    if (_tmpDirs) {
+    if (_tempDirs) {
       try {
-        fs.rmSync(_tmpDirs, { recursive: true, force: true });
+        fs.rmSync(_tempDirs, { recursive: true, force: true });
       } catch (e) {
-        console.error(`Failed to remove temp directory ${_tmpDirs}:`, e);
+        console.error(`Failed to remove temp directory ${_tempDirs}:`, e);
       }
-      _tmpDirs = '';
+      _tempDirs = '';
     }
     app.quit();
   }

+ 9 - 9
preload.js

@@ -16,32 +16,32 @@ contextBridge.exposeInMainWorld('fileAPI', {
 
   /**
    * 读取解压后的文件内容
-   * @param {string} tmpDir 临时解压目录路径
+   * @param {string} tempDir 临时解压目录路径
    * @param {string} entryName 文件条目名称
    * @returns {Buffer} 文件内容
    */
-  readZipFileSync: (tmpDir, entryName) => {
-    const filePath = path.join(tmpDir, entryName);
+  readZipFileSync: (tempDir, entryName) => {
+    const filePath = path.join(tempDir, entryName);
     return fs.readFileSync(filePath);
   },
 
   /**
    * 读取解压后的目录内容
-   * @param {string} tmpDir 临时解压目录路径
+   * @param {string} tempDir 临时解压目录路径
    * @param {string} entryName 目录条目名称
    * @returns {string[]} 目录下的文件和子目录列表
    */
-  readZipDirSync: (tmpDir, entryName) => {
-    const dirPath = path.join(tmpDir, entryName);
+  readZipDirSync: (tempDir, entryName) => {
+    const dirPath = path.join(tempDir, entryName);
     return fs.readdirSync(dirPath);
   },
 
   /**
    * 删除临时解压目录
-   * @param {string} tmpDir 临时解压目录路径
+   * @param {string} tempDir 临时解压目录路径
    */
-  deleteTempDir: (tmpDir) => {
-    fs.rmSync(tmpDir, { recursive: true, force: true });
+  deleteTempDir: (tempDir) => {
+    fs.rmSync(tempDir, { recursive: true, force: true });
   },
 
   /**

+ 12 - 50
src/components/CommonPreview.vue

@@ -33,8 +33,8 @@
               <SvgIcon icon-class="catalogue" size="54" />
             </div>
             <div class="courseware">
-              <div class="name nowrap-ellipsis" :title="courseware_info.book_name">
-                {{ courseware_info.book_name }}
+              <div class="name nowrap-ellipsis" :title="project.name">
+                {{ project.name }}
               </div>
               <div class="editor" :title="project.editor">
                 {{ project.editor }}
@@ -224,8 +224,8 @@ import MindMap from '@/components/MindMap.vue';
 // import AuditRemark from '@/components/AuditRemark.vue';
 import * as OpenCC from 'opencc-js';
 
-import { GetBookCoursewareInfo, GetProjectBaseInfo, GetProjectInfo } from '@/api/project';
-import { GetBookBaseInfo, MangerGetBookMindMap, PageQueryBookResourceList, GetLanguageTypeList } from '@/api/book';
+import { GetProjectBaseInfo } from '@/api/project';
+import { MangerGetBookMindMap, PageQueryBookResourceList } from '@/api/book';
 
 export default {
   name: 'CommonPreview',
@@ -250,7 +250,7 @@ export default {
       type: Array,
       required: true,
     },
-    tmpDir: {
+    tempDir: {
       type: String,
       required: true,
     },
@@ -400,16 +400,18 @@ export default {
         const struct = await this.readFileContent('struct.json'); // 读取章节结构文件
         this.node_list = struct.node_list;
 
-        // 过滤掉根节点
-        if (struct.node_list.length > 1) {
-          this.node_list = this.node_list.slice(1);
-        }
         if (this.curSelectId.length === 0 && this.node_list.length > 0) {
           let find = this.node_list.find(({ is_leaf_chapter }) => isTrue(is_leaf_chapter));
           if (find) {
             this.selectChapterNode(find.id, isTrue(find.is_leaf_chapter));
           }
         }
+
+        const { book_info } = await this.readFileContent('book_info.json'); // 读取教材信息文件
+        this.project = book_info;
+
+        const languageTypeList = await this.readFileContent('language_type_list.json'); // 读取语言列表文件
+        this.langList = languageTypeList.language_type_list || [];
       },
       deep: true,
       immediate: true,
@@ -436,7 +438,7 @@ export default {
      * @param subdirectory 子目录
      */
     async readFileContent(entryName, subdirectory = '') {
-      const content = await window.fileAPI.readZipFileSync(`${this.tmpDir}/${subdirectory}`, entryName);
+      const content = await window.fileAPI.readZipFileSync(`${this.tempDir}/${subdirectory}`, entryName);
       const text = new TextDecoder().decode(content);
       const obj = JSON.parse(text);
       return obj;
@@ -448,46 +450,6 @@ export default {
       });
     },
 
-    getBookBaseInfo() {
-      GetBookBaseInfo({ id: this.projectId }).then(({ book_info }) => {
-        this.courseware_info = { ...this.courseware_info, ...book_info, book_name: book_info.name };
-        this.project = {
-          editor: book_info.editor,
-          cover_image_file_id: book_info.cover_image_file_id,
-          cover_image_file_url: book_info.cover_image_file_url,
-        };
-      });
-    },
-
-    getProjectInfo() {
-      GetProjectInfo({ id: this.projectId }).then(({ project_info }) => {
-        if (project_info.cover_image_file_url) {
-          this.project = project_info;
-        }
-      });
-    },
-
-    /**
-     * 得到教材课件信息
-     * @param {string} id - 课件ID
-     */
-    getBookCoursewareInfo(id) {
-      GetBookCoursewareInfo({ id, is_contain_producer: 'true', is_contain_auditor: 'true' }).then(
-        ({ courseware_info }) => {
-          this.courseware_info = { ...this.courseware_info, ...courseware_info };
-          this.getLangList();
-        },
-      );
-    },
-
-    getLangList() {
-      GetLanguageTypeList({ book_id: this.courseware_info.book_id, is_contain_zh: 'true' }).then(
-        ({ language_type_list }) => {
-          this.langList = language_type_list;
-        },
-      );
-    },
-
     /**
      * 选择节点
      * @param {string} nodeId - 节点ID

+ 35 - 3
src/utils/http.js

@@ -5,6 +5,17 @@ import router from '@/router';
 import { getToken, getLocalStore } from '@/utils/auth';
 import { Message } from 'element-ui';
 
+// 全局开关:拦截所有请求(默认开启)
+let BLOCK_ALL_REQUESTS = true;
+
+// 设置拦截开关(外部可调用)
+export function setBlockAllRequests(flag) {
+  BLOCK_ALL_REQUESTS = Boolean(flag);
+}
+
+export function isBlockAllRequests() {
+  return BLOCK_ALL_REQUESTS;
+}
 const service = axios.create({
   baseURL: getLocalStore('server_address') || process.env.VUE_APP_EEP,
   timeout: 60000,
@@ -112,7 +123,12 @@ export const http = {
    * @param {String} url 请求地址
    * @param {object} config 请求配置
    */
-  get: (url, config) => service.get(url, config),
+  get: (url, config = {}) => {
+    if (BLOCK_ALL_REQUESTS) {
+      return Promise.resolve({ code: 200, status: 1, data: null, message: '请求已被拦截' });
+    }
+    return service.get(url, config);
+  },
   /**
    * @param {string} url 请求地址
    * @param {object} data 请求数据
@@ -122,6 +138,9 @@ export const http = {
    * @param {boolean} options.newAccessToken 是否使用新的AccessToken
    */
   post: (url, data = {}, config = {}, noTransmit = false, options = { newAccessToken: false }) => {
+    if (BLOCK_ALL_REQUESTS) {
+      return Promise.resolve({ code: 200, status: 1, data: null, message: '请求已被拦截' });
+    }
     config.params = {
       ...config.params,
       ...getRequestParams(noTransmit, options),
@@ -129,14 +148,27 @@ export const http = {
     return service.post(url, data, config);
   },
   postForm: (url, data = {}, config = {}, noTransmit = false, options = { newAccessToken: false }) => {
+    if (BLOCK_ALL_REQUESTS) {
+      return Promise.resolve({ code: 200, status: 1, data: null, message: '请求已被拦截' });
+    }
     config.params = {
       ...config.params,
       ...getRequestParams(noTransmit, options),
     };
     return service.postForm(url, data, config);
   },
-  put: (url, data, config) => service.put(url, data, config),
-  delete: (url, data, config) => service.delete(url, data, config),
+  put: (url, data, config = {}) => {
+    if (BLOCK_ALL_REQUESTS) {
+      return Promise.resolve({ code: 200, status: 1, data: null, message: '请求已被拦截' });
+    }
+    return service.put(url, data, config);
+  },
+  delete: (url, data, config = {}) => {
+    if (BLOCK_ALL_REQUESTS) {
+      return Promise.resolve({ code: 200, status: 1, data: null, message: '请求已被拦截' });
+    }
+    return service.delete(url, data, config);
+  },
 };
 
 /**

+ 8 - 8
src/views/eep_view/index.vue

@@ -1,6 +1,6 @@
 <template>
   <div class="eep-viewer">
-    <CommonPreview :entries="entries" :tmp-dir="tmpDir" />
+    <CommonPreview :entries="entries" :temp-dir="tempDir" />
   </div>
 </template>
 
@@ -16,7 +16,7 @@ export default {
     return {
       filePath: this.$route.query.filePath || '', // 从路由参数获取文件路径
       entries: null, // 压缩包内的条目列表
-      tmpDir: '', // 临时目录
+      tempDir: '', // 临时目录
     };
   },
   created() {
@@ -36,8 +36,8 @@ export default {
       try {
         const result = await window.fileAPI.extractZip(fp, '1234567a');
         this.entries = result.entries;
-        this.tmpDir = result.tmpDir;
-        this.setBaseForTmpDir(this.tmpDir);
+        this.tempDir = result.tempDir;
+        this.setBaseForTempDir(this.tempDir);
       } catch (e) {
         console.error(e);
         this.$message.error(`读取离线包失败: ${e && e.message ? e.message : String(e)}`);
@@ -47,13 +47,13 @@ export default {
     /**
      * 在 document.head 中插入/更新一个 <base>,指向临时解压目录
      * 注意:<base> 会影响整个文档的相对 URL 解析(链接、脚本等),谨慎使用
-     * @param {string} tmpDir 临时目录路径
+     * @param {string} tempDir 临时目录路径
      */
-    setBaseForTmpDir(tmpDir) {
-      if (!tmpDir) return;
+    setBaseForTempDir(tempDir) {
+      if (!tempDir) return;
       let base = document.getElementById('eep-base');
       // 将 Windows 路径的反斜杠转换为正斜杠,并确保末尾有斜杠
-      const normalized = tmpDir.replace(/\\/g, '/').replace(/\/?$/, '/');
+      const normalized = tempDir.replace(/\\/g, '/').replace(/\/?$/, '/');
       const href = `file://${normalized}`;
       if (!base) {
         base = document.createElement('base');

+ 1 - 0
src/views/select_eep/index.vue

@@ -31,6 +31,7 @@ export default {
 <style lang="scss" scoped>
 .select-eep {
   padding: 20px;
+  margin-top: 20vh;
 
   h2 {
     margin-bottom: 30px;