lrf 1 gadu atpakaļ
vecāks
revīzija
a870554120

+ 3 - 29
README.md

@@ -1,29 +1,3 @@
-# my_midway_project
-
-## QuickStart
-
-<!-- add docs here for user -->
-
-see [midway docs][midway] for more detail.
-
-### Development
-
-```bash
-$ npm i
-$ npm run dev
-$ open http://localhost:7001/
-```
-
-### Deploy
-
-```bash
-$ npm start
-```
-
-### npm scripts
-
-- Use `npm run lint` to check code style.
-- Use `npm test` to run unit test.
-
-
-[midway]: https://midwayjs.org
+# vue3js-template-service
+## 1.单点登录
+* 1.不需要登录就可以使用的接口: 请求函数的方法注解参数添加 description: 'ignore'即可

+ 3 - 0
package.json

@@ -4,6 +4,7 @@
   "description": "",
   "private": true,
   "dependencies": {
+    "@elastic/elasticsearch": "^8.12.2",
     "@midwayjs/bootstrap": "^3.12.0",
     "@midwayjs/core": "^3.12.0",
     "@midwayjs/decorator": "^3.12.0",
@@ -11,10 +12,12 @@
     "@midwayjs/jwt": "^3.13.7",
     "@midwayjs/koa": "^3.12.0",
     "@midwayjs/logger": "^3.1.0",
+    "@midwayjs/redis": "3",
     "@midwayjs/swagger": "^3.13.7",
     "@midwayjs/typegoose": "^3.0.0",
     "@midwayjs/validate": "^3.12.0",
     "@typegoose/typegoose": "^9.0.0",
+    "crypto-js": "^4.2.0",
     "dayjs": "^1.11.10",
     "free-midway-component": "^1.0.50",
     "lodash": "^4.17.21",

+ 67 - 0
pnpm-lock.yaml

@@ -5,6 +5,9 @@ settings:
   excludeLinksFromLockfile: false
 
 dependencies:
+  '@elastic/elasticsearch':
+    specifier: ^8.12.2
+    version: 8.12.2
   '@midwayjs/bootstrap':
     specifier: ^3.12.0
     version: 3.14.12
@@ -26,6 +29,9 @@ dependencies:
   '@midwayjs/logger':
     specifier: ^3.1.0
     version: 3.2.0
+  '@midwayjs/redis':
+    specifier: '3'
+    version: 3.15.6
   '@midwayjs/swagger':
     specifier: ^3.13.7
     version: 3.14.13
@@ -38,6 +44,9 @@ dependencies:
   '@typegoose/typegoose':
     specifier: ^9.0.0
     version: 9.13.2(mongoose@6.12.6)
+  crypto-js:
+    specifier: ^4.2.0
+    version: 4.2.0
   dayjs:
     specifier: ^1.11.10
     version: 1.11.10
@@ -1033,6 +1042,30 @@ packages:
       '@jridgewell/trace-mapping': 0.3.9
     dev: true
 
+  /@elastic/elasticsearch@8.12.2:
+    resolution: {integrity: sha512-04NvH3LIgcv1Uwguorfw2WwzC9Lhfsqs9f0L6uq6MrCw0lqe/HOQ6E8vJ6EkHAA15iEfbhtxOtenbZVVcE+mAQ==}
+    engines: {node: '>=18'}
+    dependencies:
+      '@elastic/transport': 8.4.1
+      tslib: 2.6.2
+    transitivePeerDependencies:
+      - supports-color
+    dev: false
+
+  /@elastic/transport@8.4.1:
+    resolution: {integrity: sha512-/SXVuVnuU5b4dq8OFY4izG+dmGla185PcoqgK6+AJMpmOeY1QYVNbWtCwvSvoAANN5D/wV+EBU8+x7Vf9EphbA==}
+    engines: {node: '>=16'}
+    dependencies:
+      debug: 4.3.4
+      hpagent: 1.2.0
+      ms: 2.1.3
+      secure-json-parse: 2.7.0
+      tslib: 2.6.2
+      undici: 5.28.3
+    transitivePeerDependencies:
+      - supports-color
+    dev: false
+
   /@eslint-community/eslint-utils@4.4.0(eslint@7.32.0):
     resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -1065,6 +1098,11 @@ packages:
       - supports-color
     dev: true
 
+  /@fastify/busboy@2.1.1:
+    resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==}
+    engines: {node: '>=14'}
+    dev: false
+
   /@hapi/hoek@9.3.0:
     resolution: {integrity: sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==}
     dev: false
@@ -1648,6 +1686,15 @@ packages:
     engines: {node: '>=12'}
     dev: false
 
+  /@midwayjs/redis@3.15.6:
+    resolution: {integrity: sha512-0dWYAzjpQH2SuUceRP/Qyf83MVXPJlRNaAH/Sotp5aEF6PB0jA2vtpEsq9q9c8Xh26kY64eOCMAJ3f7e8AYGuw==}
+    engines: {node: '>=12'}
+    dependencies:
+      ioredis: 5.3.2
+    transitivePeerDependencies:
+      - supports-color
+    dev: false
+
   /@midwayjs/serverless-spec-builder@2.1.0:
     resolution: {integrity: sha512-OGf2zQ+RxctguVNpSXJAGAM9qeP97vD9S0Cc8hwiMomTYjNWW5WvRKtCyb8KWBnP2hEkWPuhPJ4WQo8wEg0AgQ==}
     engines: {node: '>= 10'}
@@ -3340,6 +3387,10 @@ packages:
     resolution: {integrity: sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==}
     dev: true
 
+  /crypto-js@4.2.0:
+    resolution: {integrity: sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==}
+    dev: false
+
   /crypto-random-string@2.0.0:
     resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==}
     engines: {node: '>=8'}
@@ -4205,6 +4256,11 @@ packages:
       lru-cache: 6.0.0
     dev: true
 
+  /hpagent@1.2.0:
+    resolution: {integrity: sha512-A91dYTeIB6NoXG+PxTQpCCDDnfHsW9kc06Lvpu1TEe9gnd6ZFeiBoRO9JvzEv6xK7EX97/dUE8g/vBMTqTS3CA==}
+    engines: {node: '>=14'}
+    dev: false
+
   /html-escaper@2.0.2:
     resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==}
     dev: true
@@ -6120,6 +6176,10 @@ packages:
     resolution: {integrity: sha512-o/mRQGk9Rcer/jEEw/yw4mwo3EU/NvYvp577/Btqrym9Qy5/MdWGBqipbALgd2lrdWTJ5/gqDusxfnQBxOxT2Q==}
     dev: false
 
+  /secure-json-parse@2.7.0:
+    resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==}
+    dev: false
+
   /semver-diff@3.1.1:
     resolution: {integrity: sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==}
     engines: {node: '>=8'}
@@ -6679,6 +6739,13 @@ packages:
     hasBin: true
     dev: true
 
+  /undici@5.28.3:
+    resolution: {integrity: sha512-3ItfzbrhDlINjaP0duwnNsKpDQk3acHI3gVJ1z4fmwMK31k5G9OVIAMLSIaP6w4FaGkaAkN6zaQO9LUvZ1t7VA==}
+    engines: {node: '>=14.0'}
+    dependencies:
+      '@fastify/busboy': 2.1.1
+    dev: false
+
   /unique-string@2.0.0:
     resolution: {integrity: sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==}
     engines: {node: '>=8'}

+ 16 - 3
src/config/config.local.ts

@@ -1,9 +1,14 @@
 import { MidwayConfig } from '@midwayjs/core';
 const ip = '127.0.0.1'; //120.48.146.1
-const project = 'vue3js-template-test';
+const redisHost = '127.0.0.1';
+const redisPwd = '123456';
+const redisDB = 6;
+const projectDB = 'vue3js-template-test';
+const loginSign = 'tsFrameDev';
 export default {
   // use for cookie sign key, should change to your own and keep security
   keys: '1697684406848_4978',
+  loginSign,
   koa: {
     port: 9700,
     globalPrefix: '/ts/frame/api',
@@ -13,12 +18,12 @@ export default {
   },
   jwt: {
     secret: 'Ziyouyanfa!@#',
-    expiresIn: '2d',
+    expiresIn: 3600, // 3600
   },
   mongoose: {
     dataSource: {
       default: {
-        uri: `mongodb://${ip}:27017/${project}`,
+        uri: `mongodb://${ip}:27017/${projectDB}`,
         options: {
           user: 'admin',
           pass: 'admin',
@@ -29,6 +34,14 @@ export default {
       },
     },
   },
+  redis: {
+    client: {
+      port: 6379, // Redis port
+      host: redisHost, // Redis host
+      password: redisPwd,
+      db: redisDB,
+    },
+  },
   upload: {
     whitelist: null,
   },

+ 16 - 3
src/config/config.prod.ts

@@ -1,9 +1,14 @@
 import { MidwayConfig } from '@midwayjs/core';
 const ip = 'host.docker.internal';
-const project = 'st-midway';
+const redisHost = ip;
+const redisPwd = '123456';
+const redisDB = 6;
+const projectDB = 'vue3js-template-test';
+const loginSign = 'tsFrame';
 export default {
   // use for cookie sign key, should change to your own and keep security
   keys: '1697684406848_4978',
+  loginSign,
   koa: {
     port: 9700,
     globalPrefix: '/ts/frame/api',
@@ -13,12 +18,12 @@ export default {
   },
   jwt: {
     secret: 'Ziyouyanfa!@#',
-    expiresIn: '2d',
+    expiresIn: 3600, // 3600
   },
   mongoose: {
     dataSource: {
       default: {
-        uri: `mongodb://${ip}:27017/${project}`,
+        uri: `mongodb://${ip}:27017/${projectDB}`,
         options: {
           user: 'admin',
           pass: 'admin',
@@ -29,6 +34,14 @@ export default {
       },
     },
   },
+  redis: {
+    client: {
+      port: 6379, // Redis port
+      host: redisHost, // Redis host
+      password: redisPwd,
+      db: redisDB,
+    },
+  },
   upload: {
     whitelist: null,
   },

+ 4 - 0
src/configuration.ts

@@ -9,12 +9,15 @@ import { VerifyTokenInit } from './decorator/verifyToken.decorator';
 import { CheckPermissionCodeInit } from './decorator/checkPermissionCode';
 import { CheckTokenMiddleware } from './middleware/checkToken.middleware';
 import * as swagger from '@midwayjs/swagger';
+import * as redis from '@midwayjs/redis';
+import { CheckOnePointLoginMiddleware } from './middleware/checkOnePointLogin.middleware';
 @Configuration({
   imports: [
     koa,
     validate,
     FreeFrame,
     jwt,
+    redis,
     {
       component: info,
       enabledEnvironment: ['local'],
@@ -34,6 +37,7 @@ export class MainConfiguration {
 
   async onReady() {
     this.app.getMiddleware().insertFirst(CheckTokenMiddleware);
+    this.app.getMiddleware().insertAfter(CheckOnePointLoginMiddleware, 'checkToken');
     // 注解
     VerifyTokenInit(this.decoratorService);
     CheckPermissionCodeInit(this.decoratorService);

+ 42 - 0
src/controller/es.controller.ts

@@ -0,0 +1,42 @@
+import { Body, Controller, Del, Get, Inject, Param, Post,  Query } from '@midwayjs/core';
+import { ElasticsearchService } from '../service/elasticsearch';
+
+@Controller('/es')
+export class ESController {
+  @Inject()
+  esService: ElasticsearchService;
+
+  @Post('/async')
+  async asyncData() {
+    await this.esService.asyncData();
+    return 'ok';
+  }
+
+  @Get('/')
+  async search(@Query() query: any) {
+    query.build_time = ['2023-01-01', '2024-01-01']
+    const result = await this.esService.search(query);
+    return result;
+  }
+
+  @Post('/')
+  async create(@Body() data: any) {
+    const result = await this.esService.create(data);
+    return 'created';
+  }
+
+  @Post('/:id')
+  async update(@Param('id') id: string, @Body() data: any) {
+    const result = await this.esService.update(id, data);
+    console.log(id);
+    console.log(data);
+    return 'update';
+  }
+
+  @Del('/:id')
+  async delete(@Param('id') id: string) {
+    const result = await this.esService.delete(id);
+    console.log(id);
+    return 'delete';
+  }
+}

+ 2 - 2
src/controller/home.controller.ts

@@ -2,8 +2,8 @@ import { Controller, Get } from '@midwayjs/core';
 
 @Controller('/')
 export class HomeController {
-  @Get('/')
+  @Get('/', { description: 'ignore' })
   async home(): Promise<string> {
-    return 'Hello Midwayjs!';
+    return 'connect success!';
   }
 }

+ 1 - 1
src/controller/init.controller.ts

@@ -9,7 +9,7 @@ export class InitController {
   @Inject()
   service: InitService;
 
-  @Post('/')
+  @Post('/', { description: 'ignore' })
   async index() {
     await this.service.adminUser();
     // 未初始化,则执行初始化程序

+ 3 - 4
src/controller/login.controller.ts

@@ -29,15 +29,14 @@ export class LoginController {
    * @param data 用户名和密码
    * @param type 用户类型
    */
-  @Post('/:type')
+  @Post('/:type', { description: 'ignore' })
   async toLogin(@Body() data: LoginDTO, @Param('type') type: string) {
     const user = await this.loginService.loginByAccount(data, LoginType[type]);
     if (user) user.role = type;
     let vo = new LoginVO(user);
     vo = JSON.parse(JSON.stringify(vo));
-    const token = await this.jwtService.sign(vo, this.jwtSecret, {
-      expiresIn: this.jwtExpiresIn,
-    });
+    vo.login_code = await this.loginService.onePointLogin(vo)
+    const token = await this.jwtService.sign(vo, this.jwtSecret);
     return token;
   }
 

+ 65 - 0
src/entity/basic.entity.ts

@@ -0,0 +1,65 @@
+import { modelOptions, prop } from '@typegoose/typegoose';
+import { BaseModel } from 'free-midway-component';
+@modelOptions({
+  schemaOptions: { collection: 'basic' },
+})
+export class Basic extends BaseModel {
+  @prop({ required: false, index: true, zh: '实验室类别', remark: '字典:laboratory_type' })
+  type: string;
+  @prop({ required: false, index: true, zh: '实验室名称' })
+  name: string;
+  @prop({ required: false, index: false, zh: '英文名称' })
+  english_name: string;
+  @prop({ required: false, index: false, zh: '所属学科' })
+  subject: string;
+  @prop({ required: false, index: false, zh: '所属专业' })
+  major: string;
+  @prop({ required: false, index: false, zh: '实验室面积' })
+  lab_acreage: string;
+  @prop({ required: false, index: false, zh: '批建时间' })
+  build_time: string;
+  @prop({ required: false, index: false, zh: '实验室地址' })
+  lab_address: string;
+  @prop({ required: false, index: false, zh: '依托单位id', remark: 'unit_name不需要了,现在所有的实验室都有依托单位' })
+  unit: string;
+  @prop({ required: false, index: false, zh: '实验室定位' })
+  position: string;
+  @prop({ required: false, index: false, zh: '实验室建设方案' })
+  plan: Array<any>;
+  @prop({ required: false, index: false, zh: '实验室主任' })
+  chief_name: string;
+  @prop({ required: false, index: false, zh: '实验室联系人' })
+  lab_person: string;
+  @prop({ required: false, index: false, zh: '实验室联系人电话' })
+  lab_phone: string;
+  @prop({ required: false, index: false, zh: '电子邮箱' })
+  lab_email: string;
+  @prop({ required: false, index: false, zh: '申报单位' })
+  declare_unit: object;
+  @prop({ required: false, index: false, zh: '平台负责人' })
+  leading_cadre: object;
+  @prop({ required: false, index: false, zh: '科研助理' })
+  assistant: object;
+  @prop({ required: false, index: false, zh: '财务助理是否与科研助理同一人' })
+  is_alike: boolean;
+  @prop({ required: false, index: false, zh: '财务助理' })
+  finance: object;
+  @prop({ required: false, index: false, zh: '所属吉林省重点实验室' })
+  ss_laboratory: string;
+  @prop({ required: false, index: false, zh: '整合基础' })
+  zh_basis: string;
+  @prop({ required: false, index: false, zh: '是否公开', remark: '字典:info_show' })
+  is_show: string;
+  @prop({ required: false, index: false, zh: '地区' })
+  region: string;
+  @prop({ required: false, index: true, zh: '状态', default: '2' })
+  status: string;
+  @prop({ required: false, index: false, zh: '主任入驻记录' })
+  chief_logs: Array<any>;
+  @prop({ required: false, index: false, zh: '发展前景' })
+  develop: string;
+  @prop({ required: false, index: false, zh: '运行机制' })
+  mechanism: string;
+  @prop({ required: false, index: false, zh: '运行机制文件' })
+  mechanism_file: Array<any>;
+}

+ 2 - 0
src/interface/login.interface.ts

@@ -36,4 +36,6 @@ export class LoginVO {
   openid: string;
   role: string;
   is_super: number;
+  @ApiProperty({ description: '登录标识' })
+  'login_code': string = undefined;
 }

+ 24 - 0
src/middleware/checkOnePointLogin.middleware.ts

@@ -0,0 +1,24 @@
+import { IMiddleware, Inject, MidwayWebRouterService } from '@midwayjs/core';
+import { Middleware } from '@midwayjs/decorator';
+import { NextFunction, Context } from '@midwayjs/koa';
+import { LoginService } from '../service/login.service';
+
+@Middleware()
+export class CheckOnePointLoginMiddleware implements IMiddleware<Context, NextFunction> {
+  @Inject()
+  webRouterService: MidwayWebRouterService;
+  resolve() {
+    return async (ctx: Context, next: NextFunction) => {
+      const routeInfo = await this.webRouterService.getMatchedRouterInfo(ctx.path, ctx.method);
+      const desc = routeInfo.description;
+      if (desc !== 'ignore') {
+        const loginService = await ctx.requestContext.getAsync(LoginService);
+        await loginService.onePointCheck();
+      }
+      await next();
+    };
+  }
+  static getName(): string {
+    return 'checkOnePonitLogin';
+  }
+}

+ 209 - 0
src/service/elasticsearch.ts

@@ -0,0 +1,209 @@
+import { Provide } from '@midwayjs/core';
+import { Client, estypes } from '@elastic/elasticsearch';
+import { get, isArray, pickBy } from 'lodash';
+import { ReturnModelType } from '@typegoose/typegoose';
+import { Basic } from '../entity/basic.entity';
+import { InjectEntityModel } from '@midwayjs/typegoose';
+import { FrameworkErrorEnum, ServiceError } from 'free-midway-component';
+import * as dayjs from 'dayjs';
+import { Types } from 'mongoose';
+const ObjectId = Types.ObjectId;
+
+@Provide()
+export class ElasticsearchService {
+  @InjectEntityModel(Basic)
+  basicModel: ReturnModelType<typeof Basic>;
+
+  esClient: Client;
+  /**
+   * 同步数据至es
+   * 同步数据需要过滤,有些数据类型,如果为不存在或者未 '' e.g.: 字符串日期,被识别成日期.那这个字符串就不能为 '',加入索引会报错
+   */
+  async asyncData() {
+    if (!this.esClient) await this.init();
+    const list = await this.basicModel.find({}).lean();
+    let nowData;
+    try {
+      for (const i of list) {
+        const _id = new ObjectId(i._id).toString();
+        const doc = pickBy(i, (val, key) => {
+          if (key === '_id') return false;
+          if (val && val !== '') return true;
+        });
+        const obj = {
+          index: 'basic',
+          id: _id,
+          document: doc,
+        };
+        nowData = obj;
+        await this.esClient.index(obj);
+      }
+    } catch (e) {
+      console.log(e);
+    }
+  }
+
+  /**
+   * 查询es中的数据
+   * @param query 查询条件
+   * @returns
+   */
+  async search({ index, skip, limit, ...query }) {
+    if (!this.esClient) await this.init();
+    const res1 = await this.esClient.indices.exists({ index: 'test1' });
+    const res2 = await this.esClient.indices.exists({ index: 'testtime1' });
+    console.log(res1)
+    console.log(res2)
+    // 需要对查询条件进行处理
+    const matchList = ['name', 'english', 'type', 'subject'];
+    const rangeList = ['lab_acreage'];
+    const timeList = ['build_time'];
+    const obj: any = {};
+    const oq: any = {};
+    for (const key in query) {
+      if (matchList.includes(key)) {
+        if (!oq.match) oq.match = {};
+        oq.match[key] = query[key];
+      } else if (rangeList.includes(key)) {
+        if (!oq.match) oq.range = {};
+        const value = query[key];
+        if (!isArray(value)) {
+          oq.range[key] = { gte: value };
+        } else {
+          oq.range[key] = { gte: value[0], lte: value[1] };
+        }
+      } else if (timeList.includes(key)) {
+        if (!oq.match) oq.range = {};
+        const value = query[key];
+        if (!isArray(value)) {
+          oq.range[`${key}`] = { gte: value };
+        } else {
+          oq.range[`${key}`] = { gte: 'now-1y', lte: 'now' };
+        }
+      }
+    }
+    if (skip || skip === 0 || skip === '0') obj.from = skip;
+    if (limit && limit !== '0') obj.size = limit;
+    if (Object.keys(oq).length > 0) obj.query = oq;
+    const r = await this.esClient.search(obj);
+    const result = this.getSearchResult(r);
+    const total = this.getSearchTotal(r);
+    return { data: result, total };
+  }
+  /**
+   * 创建数据
+   * @param index 索引
+   * @param data 数据
+   */
+  async create({ index, data }) {
+    if (!this.esClient) await this.init();
+    console.log(data);
+    const mapping: Record<estypes.PropertyName, estypes.MappingProperty> = {
+      key: {
+        type: 'text',
+      },
+      keyword: {
+        type: 'keyword',
+      },
+      num: {
+        type: 'float',
+      },
+      time: {
+        type: 'date',
+        format: 'yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis',
+      },
+      boo: {
+        type: 'boolean',
+      },
+      sort: {
+        type: 'integer',
+      },
+    };
+    for (let k = 1; k <= 3; k++) {
+      await this.esClient.indices.create({
+        index: `test${k}`,
+        mappings: {
+          properties: mapping,
+        },
+      });
+      for (let i = 1; i <= 3; i++) {
+        await this.esClient.index({
+          index: `test${k}`,
+          document: {
+            key: `${k}-菜单${i}`,
+            keyword: `${k}-菜${i}`,
+            time: `2023-06-0${i}`,
+            num: `${i}`,
+            boo: i % 2 === 0,
+            sort: i,
+            mkey: `${k}-单${i}`,
+          },
+        });
+      }
+    }
+
+    // return r;
+  }
+  /**
+   * 更新数据
+   * @param id 数据在es中的id
+   * @param data 新数据
+   */
+  async update(id: string, data: any) {
+    if (!this.esClient) await this.init();
+    const result = await this.esClient.update({
+      index: 'basic',
+      id,
+      doc: {
+        doc: data,
+      },
+    });
+  }
+  /**
+   * 删除数据
+   * @param id 数据在es的1id
+   */
+  async delete(id: string) {
+    if (!this.esClient) await this.init();
+    try {
+      const r = await this.esClient.delete({ index: 'basic', id: null });
+    } catch (e) {
+      const code = e.statusCode;
+      if (code === 404) throw new ServiceError('es未找到数据!', FrameworkErrorEnum.DATA_NOT_FOUND);
+      else if (code === 401) throw new ServiceError('es鉴权发生错误!', FrameworkErrorEnum.DATA_NOT_FOUND);
+    }
+  }
+  /**
+   * 删除索引
+   * @param index 索引
+   */
+  async deleteIndex(index: string) {
+    await this.esClient.indices.delete({ index });
+  }
+  getSearchResult(result) {
+    const res = get(result, 'hits.hits');
+    let rData;
+    if (isArray(res)) {
+      // 整理结果,将_id放到数据中
+      rData = res.map(i => ({ _id: i._id, ...get(i, '_source.doc') }));
+    } else {
+      rData = get(res, '_source');
+    }
+    return rData;
+  }
+  getSearchTotal(result) {
+    const res = get(result, 'hits.total.value', 0);
+    return res;
+  }
+
+  async init() {
+    const esClient = new Client({
+      node: 'http://localhost:9200',
+      auth: {
+        username: 'elastic',
+        password: 'NAjqFz_7tS2DkdpU7p*x',
+      },
+    });
+    this.esClient = esClient;
+  }
+}

+ 57 - 2
src/service/login.service.ts

@@ -1,14 +1,66 @@
-import { Inject, Provide } from '@midwayjs/core';
+import { Config, Inject, Provide } from '@midwayjs/core';
 import { FrameworkErrorEnum, GetModel, ServiceError } from 'free-midway-component';
 import { get, isEqual, upperFirst } from 'lodash';
 import { LoginDTO, LoginType, UPwdDTO } from '../interface/login.interface';
 import { RoleService } from '../service/system/role.service';
-
+import { RedisService } from '@midwayjs/redis';
+import * as Crypto from 'crypto-js';
+import { Context } from '@midwayjs/koa';
 @Provide()
 export class LoginService {
   @Inject()
   roleService: RoleService;
 
+  @Inject()
+  ctx: Context;
+  @Config('loginSign')
+  loginSign;
+  @Config('jwt.secret')
+  jwtSecret;
+  @Config('jwt.expiresIn')
+  jwtExpiresIn;
+  @Inject()
+  redisService: RedisService;
+
+  async onePointLogin(loginVo) {
+    const { _id, role } = loginVo;
+    const rediskey = `${this.loginSign}:${role}:${_id}`;
+    // 随机字符串 验证用户登录用
+    const str = this.randomStr();
+    // 拼接成token内的登录code 加密前数据
+    const val = `${rediskey}:${str}`;
+    // 加密后存入token内
+    const code = Crypto.AES.encrypt(val, this.jwtSecret).toString();
+    // 设置redis登录记录 将 随机字符串存在redis中. 验证时需要将加密的字符串解密,解密后和redis中的对比验证
+    await this.redisService.set(rediskey, str, 'EX', this.jwtExpiresIn);
+    return code;
+  }
+
+  async onePointCheck() {
+    const user = this.ctx.user;
+    if (!user) throw new ServiceError('未检测到登录信息,请重新登录!', FrameworkErrorEnum.NOT_LOGIN);
+    const { _id, role, login_code } = user;
+    if (!login_code) throw new ServiceError('未检测到登录标识,请重新登录!', FrameworkErrorEnum.NOT_LOGIN);
+    // 解密
+    const decodeResult = Crypto.AES.decrypt(login_code, this.jwtSecret);
+    const decode = Crypto.enc.Utf8.stringify(decodeResult).toString();
+    // 取出code
+    const codeArr = decode.split(':');
+    const code = codeArr[codeArr.length - 1];
+    // 拼接redis的key
+    const rediskey = `${this.loginSign}:${role}:${_id}`;
+    // 取出当前记录在案的 code
+    const redisCode = await this.redisService.get(rediskey);
+    if(!redisCode) throw new ServiceError('账号登录已失效,请重新登录!', FrameworkErrorEnum.NOT_LOGIN);
+    // 判断是否一致
+    if (code === redisCode) {
+      // 一致,延时
+      await this.redisService.expire(rediskey, this.jwtExpiresIn);
+    } else {
+      throw new ServiceError('本账号已在其他地方登录,请重新登录!', FrameworkErrorEnum.NOT_LOGIN);
+    }
+  }
+
   /**
    * 账密登录
    * @param data 用户名和密码
@@ -66,4 +118,7 @@ export class LoginService {
   //   if (user) return { ...user, role };
   //   throw new ServiceError('未找到用户信息!', FrameworkErrorEnum.NOT_FOUND_DATA);
   // }
+  randomStr(len = 32) {
+    return Math.random().toString(36).slice(-len);
+  }
 }