zhangjie 1 rok temu
rodzic
commit
a767714877

+ 3 - 0
package.json

@@ -21,6 +21,7 @@
     "axios": "^0.26.1",
     "axios-progress-bar": "^1.2.0",
     "axios-retry": "^3.3.1",
+    "crypto-js": "^4.2.0",
     "custom-cursor.js": "1.3.6",
     "lodash-es": "^4.17.21",
     "mitt": "^3.0.0",
@@ -30,9 +31,11 @@
     "ua-parser-js": "^1.0.2",
     "viewerjs": "^1.10.5",
     "vue": "^3.2.37",
+    "vue-ls": "^4.2.0",
     "vue-router": "^4.1.2"
   },
   "devDependencies": {
+    "@types/crypto-js": "^4.1.3",
     "@types/lodash-es": "^4.17.6",
     "@types/node": "^18.0.6",
     "@types/ua-parser-js": "^0.7.36",

+ 35 - 0
pnpm-lock.yaml

@@ -2,6 +2,7 @@ lockfileVersion: 5.4
 
 specifiers:
   '@ant-design/icons-vue': ^6.1.0
+  '@types/crypto-js': ^4.1.3
   '@types/lodash-es': ^4.17.6
   '@types/node': ^18.0.6
   '@types/ua-parser-js': ^0.7.36
@@ -15,6 +16,7 @@ specifiers:
   axios: ^0.26.1
   axios-progress-bar: ^1.2.0
   axios-retry: ^3.3.1
+  crypto-js: ^4.2.0
   custom-cursor.js: 1.3.6
   cypress: ^10.3.1
   eslint: ^8.20.0
@@ -35,6 +37,7 @@ specifiers:
   vite: ^3.0.2
   vue: ^3.2.37
   vue-eslint-parser: ^9.0.3
+  vue-ls: ^4.2.0
   vue-router: ^4.1.2
   vue-tsc: ^0.38.8
 
@@ -45,6 +48,7 @@ dependencies:
   axios: 0.26.1
   axios-progress-bar: 1.2.0_axios@0.26.1
   axios-retry: 3.3.1
+  crypto-js: registry.npmmirror.com/crypto-js/4.2.0
   custom-cursor.js: 1.3.6
   lodash-es: 4.17.21
   mitt: 3.0.0
@@ -54,12 +58,14 @@ dependencies:
   ua-parser-js: 1.0.2
   viewerjs: 1.10.5
   vue: 3.2.37
+  vue-ls: registry.npmmirror.com/vue-ls/4.2.0
   vue-router: 4.1.2_vue@3.2.37
 
 optionalDependencies:
   cypress: registry.npmmirror.com/cypress/10.3.1
 
 devDependencies:
+  '@types/crypto-js': registry.npmmirror.com/@types/crypto-js/4.1.3
   '@types/lodash-es': 4.17.6
   '@types/node': 18.0.6
   '@types/ua-parser-js': 0.7.36
@@ -2835,6 +2841,12 @@ packages:
     dev: false
     optional: true
 
+  registry.npmmirror.com/@types/crypto-js/4.1.3:
+    resolution: {integrity: sha512-YP1sYYayLe7Eg5oXyLLvOLfxBfZ5Fgpz6sVWkpB18wDMywCLPWmqzRz+9gyuOoLF0fzDTTFwlyNbx7koONUwqA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/crypto-js/-/crypto-js-4.1.3.tgz}
+    name: '@types/crypto-js'
+    version: 4.1.3
+    dev: true
+
   registry.npmmirror.com/@types/yauzl/2.10.0:
     resolution: {integrity: sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@types/yauzl/-/yauzl-2.10.0.tgz}
     name: '@types/yauzl'
@@ -2859,6 +2871,12 @@ packages:
       is-what: registry.npmmirror.com/is-what/3.14.1
     dev: true
 
+  registry.npmmirror.com/crypto-js/4.2.0:
+    resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/crypto-js/-/crypto-js-4.2.0.tgz}
+    name: crypto-js
+    version: 4.2.0
+    dev: false
+
   registry.npmmirror.com/cypress/10.3.1:
     resolution: {integrity: sha512-As9HrExjAgpgjCnbiQCuPdw5sWKx5HUJcK2EOKziu642akwufr/GUeqL5UnCPYXTyyibvEdWT/pSC2qnGW/e5w==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/cypress/-/cypress-10.3.1.tgz}
     name: cypress
@@ -3264,6 +3282,13 @@ packages:
     dev: true
     optional: true
 
+  registry.npmmirror.com/opencollective-postinstall/2.0.3:
+    resolution: {integrity: sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz}
+    name: opencollective-postinstall
+    version: 2.0.3
+    hasBin: true
+    dev: false
+
   registry.npmmirror.com/parse-node-version/1.0.1:
     resolution: {integrity: sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/parse-node-version/-/parse-node-version-1.0.1.tgz}
     name: parse-node-version
@@ -3324,3 +3349,13 @@ packages:
     resolution: {integrity: sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/tslib/-/tslib-2.4.0.tgz}
     name: tslib
     version: 2.4.0
+
+  registry.npmmirror.com/vue-ls/4.2.0:
+    resolution: {integrity: sha512-aEQvaFzIH6A0u0in4ghsxiqMgXNxf2+mtgOW8BaiLM6f3gTfvJMjZBjSrycDgdde7P5VfXOqLFGk4+Tf0Mre6Q==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/vue-ls/-/vue-ls-4.2.0.tgz}
+    name: vue-ls
+    version: 4.2.0
+    engines: {node: '>=6.11.5'}
+    requiresBuild: true
+    dependencies:
+      opencollective-postinstall: registry.npmmirror.com/opencollective-postinstall/2.0.3
+    dev: false

+ 29 - 0
src/constants/app.ts

@@ -0,0 +1,29 @@
+import { parseHrefParam, randomCode } from "../utils/utils";
+
+// domain
+export function getOrgCode() {
+  let domain;
+  // const ipFormat = new RegExp(/\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/);
+  const paramCode =
+    parseHrefParam(window.location.href, "code") ||
+    window.sessionStorage.getItem("paramDomainCode");
+  if (paramCode) {
+    domain = paramCode;
+    window.sessionStorage.setItem("paramDomainCode", domain);
+    return domain;
+  }
+
+  domain = window.localStorage.getItem("domain_in_url");
+  if (domain) return domain;
+
+  const hostname = window.location.hostname;
+  domain = hostname.split(".")[0];
+  return domain;
+}
+
+export const PLATFORM = "WEB";
+
+if (!localStorage.getItem("deviceId")) {
+  localStorage.setItem("deviceId", randomCode(32));
+}
+export const DEVICE_ID = localStorage.getItem("deviceId");

+ 46 - 0
src/plugins/axiosApp.ts

@@ -4,6 +4,10 @@ import { notifyInvalidTokenThrottled } from "./axiosNotice";
 import axiosRetry from "axios-retry";
 import { message } from "ant-design-vue";
 import { store } from "@/store/store";
+import vls from "../utils/storage";
+import { getAuthorization } from "../utils/crypto";
+import { DEVICE_ID, PLATFORM } from "../constants/app";
+import { initSyncTime, fetchTime } from "../utils/syncServerTime";
 
 const config = {
   // baseURL: process.env.baseURL || process.env.apiUrl || ""
@@ -14,11 +18,48 @@ const config = {
 const _axiosApp = axios.create(config);
 axiosRetry(_axiosApp);
 
+// 为请求头添加鉴权信息
+function setAuth(config) {
+  const token = vls.get("token");
+  if (token) {
+    const ids = {
+      privilegeId: vls.get("privilegeId", ""),
+      orgId: vls.get("orgId", ""),
+      schoolId: vls.get("schoolId", ""),
+      userId: vls.get("user", { id: "" }).id,
+    };
+    Object.entries(ids).forEach(([key, val]) => {
+      if (val === null || val === "null" || val === "") return;
+      config.headers[key] = val;
+    });
+
+    // 新版鉴权
+    const sessionId = ls.get("user", { sessionId: "" }).sessionId;
+    const timestamp = fetchTime();
+    const Authorization = getAuthorization(
+      {
+        token,
+        timestamp,
+        sessionId,
+        uri: config.url.split("?")[0],
+        method: config.method,
+      },
+      "token"
+    );
+    config.headers["Authorization"] = Authorization;
+    config.headers["time"] = timestamp;
+  }
+  config.headers["deviceId"] = DEVICE_ID;
+  config.headers["platform"] = PLATFORM;
+  config.headers["domain"] = window.location.origin;
+}
+
 _axiosApp.interceptors.request.use(
   function (config) {
     if (config.setGlobalMask) {
       store.globalMask = true;
     }
+    setAuth(config);
     return config;
   },
   function (error) {
@@ -34,12 +75,17 @@ _axiosApp.interceptors.request.use(
 // Add a response interceptor
 _axiosApp.interceptors.response.use(
   (response) => {
+    initSyncTime(new Date(response.headers.date).getTime());
+
     if (response.config.setGlobalMask) {
       store.globalMask = false;
     }
     return response;
   },
   (error) => {
+    if (error.response) {
+      initSyncTime(new Date(error.response.headers.date).getTime());
+    }
     if (error.config?.setGlobalMask) {
       store.globalMask = false;
     }

+ 1 - 1
src/router/index.ts

@@ -77,7 +77,7 @@ const routes = [
 // keep it simple for now.
 const router = createRouter({
   // 4. Provide the history implementation to use. We are using the hash history for simplicity here.
-  history: createWebHistory("web"),
+  history: createWebHistory("stmms"),
   routes, // short for `routes: routes`
 });
 

+ 4 - 1
src/styles/page.less

@@ -365,7 +365,6 @@
         background: #f0f0f0;
         border-radius: 0px 3px 3px 0px;
         border: 1px solid #d9d9d9;
-        padding-right: 12px;
         width: 132px;
         input {
           padding: 0 12px;
@@ -376,6 +375,10 @@
         .ant-pagination-slash {
           margin: 0;
         }
+
+        &::after {
+          content: "页";
+        }
       }
       .anticon {
         vertical-align: middle;

+ 57 - 0
src/utils/crypto.ts

@@ -0,0 +1,57 @@
+import Base64 from "crypto-js/enc-base64";
+import Utf8 from "crypto-js/enc-utf8";
+import AES from "crypto-js/aes";
+import SHA1 from "crypto-js/sha1";
+
+export const getBase64 = (content: string) => {
+  const words = Utf8.parse(content);
+  const base64Str = Base64.stringify(words);
+
+  return base64Str;
+};
+
+export const getAES = (content: string) => {
+  const KEY = "1234567890123456";
+  const IV = "1234567890123456";
+
+  const key = Utf8.parse(KEY);
+  const iv = Utf8.parse(IV);
+  const encrypted = AES.encrypt(content, key, { iv: iv });
+  return encrypted.toString();
+};
+
+type AuthInfo = {
+  method: string;
+  uri: string;
+  timestamp: number;
+  accessKey?: string;
+  token?: string;
+  sessionId?: string;
+};
+
+/**
+ * 获取authorisation
+ * @param {Object} infos 相关信息
+ * @param {String} type 类别:secret、token两种
+ */
+export const getAuthorization = (
+  infos: AuthInfo,
+  type: "secret" | "token"
+): string => {
+  // {type} {invoker}:base64(sha1(method&uri&timestamp&{secret}))
+  if (type === "secret") {
+    // accessKey | method&uri&timestamp&accessSecret
+    const str = `${infos.method.toLowerCase()}&${infos.uri}&${
+      infos.timestamp
+    }&${infos.accessSecret}`;
+    const sign = Base64.stringify(SHA1(str));
+    return `Secret ${infos.accessKey}:${sign}`;
+  } else if (type === "token") {
+    // userId | method&uri&timestamp&token
+    const str = `${infos.method.toLowerCase()}&${infos.uri}&${
+      infos.timestamp
+    }&${infos.token}`;
+    const sign = Base64.stringify(SHA1(str));
+    return `Token ${infos.sessionId}:${sign}`;
+  }
+};

+ 10 - 0
src/utils/storage.ts

@@ -0,0 +1,10 @@
+import Storage from "vue-ls";
+const options = {
+  namespace: "vs_", // key prefix
+  name: "ls", // name variable Vue.[ls] or this.[$ls],
+  storage: "session", // storage name session, local, memory
+};
+
+const { ls } = Storage.useStorage(options);
+
+export default ls;

+ 28 - 0
src/utils/syncServerTime.ts

@@ -0,0 +1,28 @@
+let initLocalTime = null;
+let initServerTime = null;
+
+function getStorgeTime() {
+  const st = localStorage.getItem("st");
+  const unvalidVals = ["Infinity", "NaN", "null", "undefined"];
+  if (unvalidVals.includes(st + "")) {
+    return [Date.now(), Date.now()];
+  } else {
+    const [s, t] = st.split("_");
+    return [s * 1, t * 1];
+  }
+}
+
+const [serverTime, localTime] = getStorgeTime();
+initSyncTime(serverTime, localTime);
+
+function initSyncTime(serverTime, localTime = Date.now()) {
+  initLocalTime = localTime;
+  initServerTime = serverTime;
+  localStorage.setItem("st", `${initServerTime}_${initLocalTime}`);
+}
+
+function fetchTime() {
+  return Date.now() + initServerTime - initLocalTime;
+}
+
+export { initSyncTime, fetchTime };

+ 41 - 0
src/utils/utils.ts

@@ -355,3 +355,44 @@ export function addHeaderTrackColorAttr(headerTrack: any): any {
     return item;
   });
 }
+
+/**
+ * 获取随机code,默认获取16位
+ * @param {Number} len 推荐8的倍数
+ *
+ */
+export function randomCode(len = 16): string {
+  if (len <= 0) return;
+  const steps = Math.ceil(len / 8);
+  const stepNums = [];
+  for (let i = 0; i < steps; i++) {
+    const ranNum = Math.random().toString(32).slice(-8);
+    stepNums.push(ranNum);
+  }
+
+  return stepNums.join("");
+}
+
+/**
+ * 解析url获取指定参数值
+ * @param urlStr url
+ * @param paramName 参数名
+ * @returns 参数值
+ */
+export function parseHrefParam(
+  urlStr: string,
+  paramName: string = null
+): string | object {
+  if (!urlStr) return;
+  const url = new URL(urlStr);
+
+  const urlParams = new URLSearchParams(url.search);
+
+  if (paramName) return urlParams.get(paramName);
+
+  const params = {};
+  for (const kv of urlParams.entries()) {
+    params[kv[0]] = kv[1];
+  }
+  return params;
+}