Jelajahi Sumber

feat: 集成 cache,redis,http,mongoose

qinlaiqiao 4 tahun lalu
induk
melakukan
a356a2f5f4

+ 12 - 12
.eslintrc.js

@@ -15,24 +15,23 @@ module.exports = {
     parser: "@typescript-eslint/parser",
   },
   rules: {
-    /* "import/extensions": [
-                            "error",
-                            "ignorePackages",
-                            {
-                              js: "never",
-                              mjs: "never",
-                              jsx: "never",
-                              ts: "never",
-                              tsx: "never",
-                            },
-                          ], */
+    "import/extensions": [
+      "error",
+      {
+        js: "never",
+        mjs: "never",
+        jsx: "never",
+        ts: "never",
+        tsx: "never",
+      },
+    ],
     "import/no-unresolved": "off",
     "@typescript-eslint/no-empty-function": "off",
     "@typescript-eslint/no-explicit-any": "off",
     "@typescript-eslint/explicit-module-boundary-types": "off",
     "vuejs-accessibility/form-control-has-label": "off",
     "@typescript-eslint/no-non-null-assertion": "off",
-    "import/extensions": "off",
+    // "import/extensions": "off",
     "vuejs-accessibility/click-events-have-key-events": "off",
     "vuejs-accessibility/mouse-events-have-key-events": "off",
     "no-unused-expressions": [
@@ -58,6 +57,7 @@ module.exports = {
     "no-return-await": "off",
     "prefer-destructuring": "off",
     "@typescript-eslint/no-this-alias": "off",
+    "no-console": "off",
     "import/order": [
       "error",
       {

File diff ditekan karena terlalu besar
+ 557 - 197
package-lock.json


+ 13 - 0
package.json

@@ -12,10 +12,17 @@
     "test": "jest"
   },
   "dependencies": {
+    "@midwayjs/axios": "^2.13.4",
     "@midwayjs/bootstrap": "^2.13.4",
+    "@midwayjs/cache": "^2.13.4",
+    "@midwayjs/core": "^2.13.4",
     "@midwayjs/decorator": "^2.13.2",
     "@midwayjs/hooks": "^2.2.2",
     "@midwayjs/koa": "^2.11.0",
+    "@midwayjs/mongoose": "^2.13.4",
+    "@midwayjs/process-agent": "^2.13.4",
+    "@midwayjs/redis": "^2.13.4",
+    "@midwayjs/view-nunjucks": "^2.13.4",
     "@sc/handsontable": "^6.3.11",
     "@sc/tree": "^1.0.18",
     "@sc/util": "^1.0.9",
@@ -23,11 +30,14 @@
     "animejs": "^3.2.1",
     "ant-design-vue": "2.2.7",
     "axios": "^0.24.0",
+    "cache-manager": "^3.6.0",
     "echarts": "^5.2.2",
     "element-plus": "^1.1.0-beta.24",
     "koa-bodyparser": "^4.3.0",
+    "koa-session": "^6.2.0",
     "lodash": "^4.17.21",
     "mitt": "^2.1.0",
+    "mongoose": "^5.13.13",
     "vue": "^3.2.16",
     "vue-router": "^4.0.12",
     "vuex": "^4.0.2",
@@ -41,8 +51,11 @@
     "@midwayjs/vite-plugin-hooks": "^2.2.3",
     "@sc/types": "^1.0.29",
     "@types/animejs": "^3.1.4",
+    "@types/cache-manager": "^3.4.2",
+    "@types/ioredis": "^4.28.1",
     "@types/jest": "^26.0.23",
     "@types/koa-bodyparser": "^4.3.1",
+    "@types/koa-session": "^5.10.4",
     "@types/lodash": "^4.14.176",
     "@typescript-eslint/eslint-plugin": "^4.18.0",
     "@typescript-eslint/parser": "^4.18.0",

+ 59 - 0
src/apis/config/config.default.ts

@@ -0,0 +1,59 @@
+export const redis = {
+  client: {
+    port: 6379,
+    host: "192.168.1.90",
+    password: "smartcost3850888",
+    db: 0,
+  },
+};
+
+// cookie 的密钥
+export const keys = ["163751$@GFD164371969_47@$@#&%HDFGfd46_Abc"];
+
+export const session = {
+  key: "SESS",
+  prefix: "sess-",
+  maxAge: 86400000,
+  httpOnly: true,
+  encrypt: true,
+  renew: true, // 延长会话有效期
+  rolling: true,
+};
+
+export const axios = {
+  // baseURL: "https://api.example.com",
+  //
+  // headers: {
+  //   "X-Requested-With": "XMLHttpRequest",
+  // },
+  // timeout: 2000,
+  //
+  // withCredentials: false,
+};
+
+export const cache = {
+  store: "memory",
+  options: {
+    max: 100,
+    ttl: 5, // 默认过期时间
+  },
+};
+
+export const view = {
+  defaultViewEngine: "nunjucks",
+  mapping: {
+    ".html": "nunjucks",
+  },
+};
+
+export const mongoose = {
+  client: {
+    uri: "mongodb://192.168.1.90:60666/auditPlatform",
+    options: {
+      useNewUrlParser: true,
+      useUnifiedTopology: true,
+      user: "wisecost",
+      pass: "Smartcost3850888",
+    },
+  },
+};

+ 11 - 0
src/apis/config/config.local.ts

@@ -0,0 +1,11 @@
+/**
+ * 这里加入这段是因为 egg 默认的安全策略,在 post 请求的时候如果不传递 token 会返回 403
+ * 由于大部分新手用户不太了解这个机制,所以在本地和单测环境做了默认处理
+ * 请注意,线上环境依旧会有该错误,需要手动开启
+ * 如果想了解更多细节,请访问 https://eggjs.org/zh-cn/core/security.html#安全威胁-csrf-的防范
+ */
+export const security = {
+  csrf: false,
+};
+
+export const XXX = "";

+ 5 - 0
src/apis/config/config.unittest.ts

@@ -0,0 +1,5 @@
+export const security = {
+  csrf: false,
+};
+
+export const XXX = "";

+ 6 - 0
src/apis/config/plugin.ts

@@ -0,0 +1,6 @@
+// import { EggPlugin } from "egg";
+//
+// export default {
+//   logrotator: false, // disable when use @midwayjs/logger
+//   static: false,
+// } as EggPlugin;

+ 53 - 10
src/apis/configuration.ts

@@ -1,11 +1,54 @@
-import { hooks, createConfiguration } from "@midwayjs/hooks";
+import { App, Configuration, Config } from "@midwayjs/decorator";
+import { hooks } from "@midwayjs/hooks";
+import { ILifeCycle, IMidwayContainer } from "@midwayjs/core";
+import { Application } from "@midwayjs/koa";
+import { join } from "path";
 import bodyParser from "koa-bodyparser";
-import error from "@/apis/middleware/error";
-
-export default createConfiguration({
-  imports: [
-    hooks({
-      middleware: [error, bodyParser()],
-    }),
-  ],
-});
+import session from "koa-session";
+import * as redis from "@midwayjs/redis";
+import * as axios from "@midwayjs/axios";
+import * as cache from "@midwayjs/cache";
+import * as view from "@midwayjs/view-nunjucks";
+import * as mongoose from "@midwayjs/mongoose";
+
+@Configuration({
+  imports: [redis, axios, cache, view, mongoose, hooks()],
+  importConfigs: [join(__dirname, "./config/")],
+  conflictCheck: true,
+})
+export default class ContainerLifeCycle implements ILifeCycle {
+  @App()
+  app: Application;
+
+  @Config("session")
+  sessionConfig;
+
+  @Config("keys")
+  keys;
+
+  async onReady(container: IMidwayContainer) {
+    // 全局错误处理中间件
+    this.app.use(await this.app.generateMiddleware("errorMiddleware"));
+
+    this.app.keys = this.keys;
+
+    // session 中间件
+    this.app.use(session(this.sessionConfig, this.app));
+
+    // post 请求 bodyParser 中间件
+    this.app.use(bodyParser());
+
+    // axios 拦截器
+    const httpService = await container.getAsync(axios.HttpService);
+    httpService.interceptors.request.use(
+      (config) => {
+        // Do something before request is sent
+        return config;
+      },
+      (error) => {
+        // Do something with request error
+        return Promise.reject(error);
+      }
+    );
+  }
+}

+ 6 - 1
src/apis/controller/user.ts

@@ -1,13 +1,18 @@
 import { useUserService } from "@/apis/hooks/service";
 import Response from "@/utils/common/Response";
+import useContext from "@/apis/hooks/context";
 
 export async function login(username: string, password: string) {
   const userService = await useUserService();
-
+  const ctx = useContext();
   const userID = await userService.login(username, password);
+  await userService.test();
+  ctx.session.userID = userID;
   return new Response(userID);
 }
 
 export const userInfo = async (userID: string) => {
   //
 };
+
+export const test = async () => {};

+ 5 - 1
src/apis/hooks/context.ts

@@ -1,6 +1,10 @@
 import { Context } from "@midwayjs/koa";
 import { useContext as useCtx } from "@midwayjs/hooks";
 
+export interface IContext extends Context {
+  session: any;
+}
+
 export default function useContext() {
-  return useCtx<Context>();
+  return useCtx<IContext>();
 }

+ 28 - 0
src/apis/middleware/ErrorMiddleware.ts

@@ -0,0 +1,28 @@
+import { Provide } from "@midwayjs/decorator";
+import {
+  IWebMiddleware,
+  IMidwayKoaContext,
+  IMidwayKoaNext,
+} from "@midwayjs/koa";
+import ErrNo from "@/constants/errorEnum";
+
+@Provide()
+export default class ErrorMiddleware implements IWebMiddleware {
+  resolve() {
+    return async (ctx: IMidwayKoaContext, next: IMidwayKoaNext) => {
+      try {
+        await next();
+      } catch (err: any) {
+        console.log(err);
+        const errno = err.errno || ErrNo.ERROR;
+        const result = {
+          errno,
+          message: `${err.message}`,
+          data: null,
+        };
+        ctx.status = 400;
+        ctx.body = result;
+      }
+    };
+  }
+}

+ 0 - 21
src/apis/middleware/error.ts

@@ -1,21 +0,0 @@
-import useContext from "../hooks/context";
-import { IResult } from "@sc/types";
-import ErrNo from "@/constants/errorEnum";
-
-const error = async (next: any) => {
-  const ctx = useContext();
-  try {
-    await next();
-  } catch (err: any) {
-    console.log(err);
-    const errno = err.errno || ErrNo.ERROR;
-    const result: IResult = {
-      errno,
-      message: `${err.message}`,
-      data: null,
-    };
-    ctx.status = 400;
-    ctx.body = result;
-  }
-};
-export default error;

+ 0 - 0
src/apis/model/.gitkeep


+ 13 - 0
src/apis/model/index.ts

@@ -0,0 +1,13 @@
+import ModelName from "@/constants/dbEnum";
+import { Schema } from "mongoose";
+import UserSchemaMap from "./User";
+import MessageSchemaMap from "./Message";
+
+const schemaMap = {
+  ...UserSchemaMap,
+  ...MessageSchemaMap,
+};
+
+export default function getSchemaByModelName(modelName: ModelName): Schema {
+  return schemaMap[modelName];
+}

+ 14 - 0
src/apis/model/message.ts

@@ -0,0 +1,14 @@
+import { Schema, Document } from "mongoose";
+import { IMessage } from "@/types/models/message";
+import ModelName from "@/constants/dbEnum";
+
+export type MessageDocument = IMessage & Document;
+
+const schema = new Schema({
+  title: { type: String, required: true },
+  content: { type: String, required: true },
+});
+
+export default {
+  [ModelName.Message]: schema,
+};

+ 15 - 0
src/apis/model/user.ts

@@ -0,0 +1,15 @@
+import { Schema, Document } from "mongoose";
+import { IUser } from "@/types/models/user";
+import ModelName from "@/constants/dbEnum";
+
+export type UserDocument = IUser & Document;
+
+const schema = new Schema({
+  name: { type: String, required: true },
+  email: { type: String, required: true },
+  avatar: String,
+});
+
+export default {
+  [ModelName.User]: schema,
+};

+ 33 - 0
src/apis/service/base.ts

@@ -0,0 +1,33 @@
+import { Inject } from "@midwayjs/decorator";
+import { RedisService } from "@midwayjs/redis";
+import { HttpService } from "@midwayjs/axios";
+import CacheService from "@/apis/service/cache";
+import { MongooseConnectionService } from "@midwayjs/mongoose";
+import ModelName from "@/constants/dbEnum";
+import getSchemaByModelName from "@/apis/model";
+
+// 抽象
+export default abstract class BaseService {
+  @Inject()
+  redisService: RedisService;
+
+  @Inject()
+  httpService: HttpService;
+
+  @Inject()
+  cacheService: CacheService;
+
+  @Inject()
+  conn: MongooseConnectionService;
+
+  // 获取数据库的指定 Model
+  getModel<T>(modelName: ModelName) {
+    const schema = getSchemaByModelName(modelName);
+    return this.conn.model<T>(modelName, schema, modelName);
+  }
+
+  // 获取数据库指定的 Collection
+  getCollection(modelName: ModelName) {
+    return this.conn.collection(modelName);
+  }
+}

+ 23 - 0
src/apis/service/cache.ts

@@ -0,0 +1,23 @@
+import { Provide, Scope, ScopeEnum, Inject } from "@midwayjs/decorator";
+import { RunInPrimary } from "@midwayjs/process-agent";
+import { CacheManager } from "@midwayjs/cache";
+
+// 单例
+@Provide()
+@Scope(ScopeEnum.Singleton)
+export default class CacheService {
+  @Inject()
+  private cache: CacheManager;
+
+  // 设置缓存
+  @RunInPrimary()
+  async set(key: string, val: any, ttl = 5) {
+    await this.cache.set(key, val, { ttl });
+  }
+
+  // 获取缓存
+  @RunInPrimary()
+  async get(key: string) {
+    return await this.cache.get(key);
+  }
+}

+ 36 - 3
src/apis/service/user.ts

@@ -1,13 +1,46 @@
-import { Provide } from "@midwayjs/decorator";
+import { Provide, Scope, ScopeEnum } from "@midwayjs/decorator";
 import AppError from "@/utils/common/AppError";
 import ErrNo from "@/constants/errorEnum";
+import BaseService from "@/apis/service/base";
+import ModelName from "@/constants/dbEnum";
+import { UserDocument } from "@/apis/model/User";
 
+// 单例
 @Provide()
-export default class UserService {
+@Scope(ScopeEnum.Singleton)
+export default class UserService extends BaseService {
+  private get UserModel() {
+    return this.getModel<UserDocument>(ModelName.User);
+  }
+
   async login(username: string, password: string) {
     if (username === "zhangsan" && password === "123") {
-      return "userID:zhangsan-123";
+      // 测试 redis
+      await this.redisService.set("foo", "bar123");
+      const result = await this.redisService.get("foo");
+
+      // 测试 cache
+      await this.cacheService.set("user-123", "张三");
+      const name = await this.cacheService.get("user-123");
+
+      return `userID:zhangsan-123_${result}_${name}`;
     }
     throw new AppError("用户名或密码错误", ErrNo.LOGIN_FAIL);
   }
+
+  async test() {
+    const doc = new this.UserModel({
+      name: "Bill",
+      email: "bill@initech.com",
+      avatar: "https://i.imgur.com/dM7Thhn.png",
+    });
+
+    const msg = new this.MessageModel({
+      title: "标题",
+      content: "内容",
+    });
+
+    await doc.save();
+    await msg.save();
+  }
 }

+ 6 - 0
src/constants/dbEnum.ts

@@ -0,0 +1,6 @@
+enum ModelName {
+  User = "user",
+  Message = "message",
+}
+
+export default ModelName;

+ 4 - 0
src/types/models/message.ts

@@ -0,0 +1,4 @@
+export interface IMessage {
+  title: string;
+  content: string;
+}

+ 5 - 0
src/types/models/user.ts

@@ -0,0 +1,5 @@
+export interface IUser {
+  name: string;
+  email: string;
+  avatar: string;
+}

+ 1 - 1
src/views/login/Login.vue

@@ -20,7 +20,7 @@ const handleSubmit = async () => {
     const userID = await login(username.value, password.value);
     console.log("用户 ID", userID);
   } catch (err: any) {
-    console.log("次哦判断是否", err.errno);
+    console.log("错误码", err.errno);
   }
 };
 </script>

+ 2 - 1
tsconfig.json

@@ -9,8 +9,8 @@
     "allowJs": false,
     "esModuleInterop": true,
     "allowSyntheticDefaultImports": true,
-    "strict": true,
     "skipLibCheck": true,
+    "noUnusedParameters": false,
     "forceConsistentCasingInFileNames": true,
     "module": "ESNext",
     "moduleResolution": "Node",
@@ -21,6 +21,7 @@
     "experimentalDecorators": true,
     "emitDecoratorMetadata": true,
     "baseUrl": ".",
+    "noImplicitThis": true,
     "types": [
       "vite/client",
       "jest"