lrf 2 年之前
當前提交
a957b3f2e9

+ 11 - 0
.editorconfig

@@ -0,0 +1,11 @@
+# 🎨 editorconfig.org
+
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+indent_style = space
+indent_size = 2
+trim_trailing_whitespace = true
+insert_final_newline = true

+ 16 - 0
.eslintrc.json

@@ -0,0 +1,16 @@
+{
+  "extends": "./node_modules/mwts/",
+  "ignorePatterns": [
+    "node_modules",
+    "dist",
+    "test",
+    "jest.config.js",
+    "typings"
+  ],
+  "env": {
+    "jest": true
+  },
+  "rules": {
+    "prettier/prettier": "off"
+  }
+}

+ 15 - 0
.gitignore

@@ -0,0 +1,15 @@
+logs/
+npm-debug.log
+yarn-error.log
+node_modules/
+package-lock.json
+yarn.lock
+coverage/
+dist/
+.idea/
+run/
+.DS_Store
+*.sw*
+*.un~
+.tsbuildinfo
+.tsbuildinfo.*

+ 4 - 0
.prettierrc.js

@@ -0,0 +1,4 @@
+module.exports = {
+  ...require('mwts/.prettierrc.json'),
+  printWidth: 200,
+};

+ 26 - 0
.vscode/launch.json

@@ -0,0 +1,26 @@
+{
+  // 使用 IntelliSense 了解相关属性。
+  // 悬停以查看现有属性的描述。
+  // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
+  "version": "0.2.0",
+  "configurations": [
+    {
+      "name": "聊天服务",
+      "type": "node",
+      "request": "launch",
+      "cwd": "${workspaceRoot}",
+      "runtimeExecutable": "npm",
+      "windows": {
+        "runtimeExecutable": "npm.cmd"
+      },
+      "runtimeArgs": ["run", "dev"],
+      "env": {
+        "NODE_ENV": "local"
+      },
+      "console": "integratedTerminal",
+      "protocol": "auto",
+      "restart": true,
+      "autoAttachChildProcesses": true
+    }
+  ]
+}

+ 29 - 0
README.md

@@ -0,0 +1,29 @@
+# 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

+ 29 - 0
README.zh-CN.md

@@ -0,0 +1,29 @@
+# my_midway_project
+
+## 快速入门
+
+<!-- 在此次添加使用文档 -->
+
+如需进一步了解,参见 [midway 文档][midway]。
+
+### 本地开发
+
+```bash
+$ npm i
+$ npm run dev
+$ open http://localhost:7001/
+```
+
+### 部署
+
+```bash
+$ npm start
+```
+
+### 内置指令
+
+- 使用 `npm run lint` 来做代码风格检查。
+- 使用 `npm test` 来执行单元测试。
+
+
+[midway]: https://midwayjs.org

+ 2 - 0
bootstrap.js

@@ -0,0 +1,2 @@
+const { Bootstrap } = require('@midwayjs/bootstrap');
+Bootstrap.run();

+ 6 - 0
jest.config.js

@@ -0,0 +1,6 @@
+module.exports = {
+  preset: 'ts-jest',
+  testEnvironment: 'node',
+  testPathIgnorePatterns: ['<rootDir>/test/fixtures'],
+  coveragePathIgnorePatterns: ['<rootDir>/test/'],
+};

+ 67 - 0
package.json

@@ -0,0 +1,67 @@
+{
+  "name": "my-midway-project",
+  "version": "1.0.0",
+  "description": "",
+  "private": true,
+  "dependencies": {
+    "@midwayjs/axios": "^3.9.0",
+    "@midwayjs/bootstrap": "^3.0.0",
+    "@midwayjs/core": "^3.0.0",
+    "@midwayjs/decorator": "^3.0.0",
+    "@midwayjs/info": "^3.0.0",
+    "@midwayjs/jwt": "^3.9.9",
+    "@midwayjs/koa": "^3.0.0",
+    "@midwayjs/logger": "^2.14.0",
+    "@midwayjs/rabbitmq": "^3.9.0",
+    "@midwayjs/swagger": "^3.9.9",
+    "@midwayjs/typegoose": "^3.9.0",
+    "@midwayjs/validate": "^3.0.0",
+    "@typegoose/typegoose": "^10.0.0",
+    "amqp-connection-manager": "^4.1.10",
+    "amqplib": "^0.10.3",
+    "dayjs": "^1.11.7",
+    "free-midway-component": "^1.0.35",
+    "lodash": "^4.17.21",
+    "mongoose": "^6.8.3"
+  },
+  "devDependencies": {
+    "@midwayjs/cli": "^2.0.0",
+    "@midwayjs/mock": "^3.0.0",
+    "@types/amqplib": "^0.10.1",
+    "@types/jest": "^29.2.0",
+    "@types/jsonwebtoken": "^9.0.0",
+    "@types/koa": "^2.13.4",
+    "@types/lodash": "^4.14.191",
+    "@types/node": "14",
+    "cross-env": "^6.0.0",
+    "jest": "^29.2.2",
+    "mwts": "^1.0.5",
+    "swagger-ui-dist": "^4.15.5",
+    "ts-jest": "^29.0.3",
+    "typescript": "~4.8.0"
+  },
+  "engines": {
+    "node": ">=12.0.0"
+  },
+  "scripts": {
+    "start": "NODE_ENV=production node ./bootstrap.js",
+    "dev": "cross-env NODE_ENV=local midway-bin dev --ts",
+    "test": "midway-bin test --ts",
+    "cov": "midway-bin cov --ts",
+    "lint": "mwts check",
+    "lint:fix": "mwts fix",
+    "ci": "npm run cov",
+    "build": "midway-bin build -c",
+    "self_test": "midway-bin test -f test/mq.test.ts"
+  },
+  "midway-bin-clean": [
+    ".vscode/.tsbuildinfo",
+    "dist"
+  ],
+  "repository": {
+    "type": "git",
+    "url": ""
+  },
+  "author": "anonymous",
+  "license": "MIT"
+}

+ 9 - 0
src/config/config.default.ts

@@ -0,0 +1,9 @@
+import { MidwayConfig } from '@midwayjs/core';
+
+export default {
+  // use for cookie sign key, should change to your own and keep security
+  keys: '1673500377013_5737',
+  koa: {
+    port: 7001,
+  },
+} as MidwayConfig;

+ 63 - 0
src/config/config.local.ts

@@ -0,0 +1,63 @@
+import { MidwayConfig } from '@midwayjs/core';
+const ip = '120.48.146.1';
+const suffix = '_dev_local';
+export default {
+  // use for cookie sign key, should change to your own and keep security
+  keys: '1669597198171_1000',
+  koa: {
+    port: 12214,
+    globalPrefix: '/dev/point/chat/v1/api',
+  },
+  swagger: {
+    swaggerPath: '/dev/point/chat/v1/api/doc/api',
+  },
+  mongoose: {
+    dataSource: {
+      default: {
+        uri: `mongodb://${ip}:27017/tehq_chat-dev`,
+        options: {
+          user: 'admin',
+          pass: 'admin',
+          authSource: 'admin',
+          useNewUrlParser: true,
+        },
+        entities: ['./entity/chat'],
+      },
+    },
+  },
+  redis: {
+    client: {
+      port: 6379, // Redis port
+      host: ip, // Redis host
+      password: '123456',
+      db: 2,
+    },
+  },
+  redisKey: {
+    orderKeyPrefix: 'orderKey:',
+  },
+  redisTimeout: 600,
+
+  jwt: {
+    secret: 'Ziyouyanfa!@#',
+  },
+  axios: {
+    clients: {
+      wechat: {
+        baseURL: 'https://broadcast.waityou24.cn/wechat/api', // http://127.0.0.1:14001/wechat/api
+      },
+      base: {
+        baseURL: 'https://broadcast.waityou24.cn/dev/point/v1/api', // http://127.0.0.1:12211
+      },
+    },
+  },
+  rabbitmq: {
+    url: `amqp://tehqDev:tehqDev@${ip}/tehqDev`,
+  },
+  mqConfig: {
+    normal: {
+      ex: `t_m${suffix}`,
+    },
+    timeout: 1, //min
+  },
+} as MidwayConfig;

+ 63 - 0
src/config/config.prod.ts

@@ -0,0 +1,63 @@
+import { MidwayConfig } from '@midwayjs/core';
+const ip = '127.0.0.1';
+const suffix = '_dev';
+export default {
+  // use for cookie sign key, should change to your own and keep security
+  keys: '1669597198171_1000',
+  koa: {
+    port: 12214,
+    globalPrefix: '/point/chat/v1/api',
+  },
+  swagger: {
+    swaggerPath: '/point/chat/v1/api/doc/api',
+  },
+  mongoose: {
+    dataSource: {
+      default: {
+        uri: `mongodb://${ip}:27017/tehq_chat`,
+        options: {
+          user: 'admin',
+          pass: 'admin',
+          authSource: 'admin',
+          useNewUrlParser: true,
+        },
+        entities: ['./entity/chat'],
+      },
+    },
+  },
+  redis: {
+    client: {
+      port: 6379, // Redis port
+      host: ip, // Redis host
+      password: '123456',
+      db: 1,
+    },
+  },
+  redisKey: {
+    orderKeyPrefix: 'orderKey:',
+  },
+  redisTimeout: 600,
+
+  jwt: {
+    secret: 'Ziyouyanfa!@#',
+  },
+  axios: {
+    clients: {
+      wechat: {
+        baseURL: 'http://127.0.0.1:14001/wechat/api', // http://127.0.0.1:14001/wechat/api
+      },
+      base: {
+        baseURL: 'http://127.0.0.1', // http://127.0.0.1:12211
+      },
+    },
+  },
+  rabbitmq: {
+    url: `amqp://tehqDev:tehqDev@${ip}/tehq`,
+  },
+  mqConfig: {
+    normal: {
+      ex: `t_m${suffix}`,
+    },
+    timeout: 1, //min
+  },
+} as MidwayConfig;

+ 7 - 0
src/config/config.unittest.ts

@@ -0,0 +1,7 @@
+import { MidwayConfig } from '@midwayjs/core';
+
+export default {
+  koa: {
+    port: null,
+  },
+} as MidwayConfig;

+ 37 - 0
src/configuration.ts

@@ -0,0 +1,37 @@
+import { Configuration, App } from '@midwayjs/decorator';
+import * as koa from '@midwayjs/koa';
+import * as validate from '@midwayjs/validate';
+import * as info from '@midwayjs/info';
+import { join } from 'path';
+import * as FreeFrame from 'free-midway-component';
+import * as swagger from '@midwayjs/swagger';
+import * as jwt from '@midwayjs/jwt';
+import { CheckTokenMiddleware } from './middleware/checkToken.middleware';
+import * as axios from '@midwayjs/axios';
+import { Inject } from '@midwayjs/core';
+import * as rabbitmq from '@midwayjs/rabbitmq';
+import { Context } from '@midwayjs/koa';
+@Configuration({
+  imports: [
+    FreeFrame,
+    validate,
+    jwt,
+    axios,
+    swagger,
+    rabbitmq,
+    {
+      component: info,
+      enabledEnvironment: ['local'],
+    },
+  ],
+  importConfigs: [join(__dirname, './config')],
+})
+export class ContainerLifeCycle {
+  @App()
+  app: koa.Application;
+  @Inject()
+  ctx: Context;
+  async onReady() {
+    this.app.getMiddleware().insertFirst(CheckTokenMiddleware);
+  }
+}

+ 124 - 0
src/controller/chatRecord.controller.ts

@@ -0,0 +1,124 @@
+import { Body, Controller, Get, Inject, Param, Post, Query } from '@midwayjs/decorator';
+import { BaseController, FrameworkErrorEnum, ServiceError } from 'free-midway-component';
+import { ChatRecordService } from '../service/chatRecord.service';
+import {
+  CreateDTO_chatRecord,
+  CreateVO_chatRecord,
+  FetchVO_chatRecord,
+  QueryDTO_chatRecord,
+  QueryVO_chatRecord,
+  ReadDTO,
+  UpdateDTO_chatRecord,
+  UpdateVO_chatRecord,
+} from '../interface/chatRecord.interface';
+import { ApiResponse, ApiTags, ApiQuery } from '@midwayjs/swagger';
+import { Validate } from '@midwayjs/validate';
+import * as dayjs from 'dayjs';
+// import CustomParseFormat = require('dayjs/plugin/customParseFormat');
+// dayjs.extend(CustomParseFormat);
+import { RoomService } from '../service/room.service';
+import get = require('lodash/get');
+import { MqSender } from '../service/mq/mqSender.service';
+@ApiTags(['聊天记录'])
+@Controller('/chatRecord')
+export class ChatRecordController extends BaseController {
+  @Inject()
+  service: ChatRecordService;
+
+  @Inject()
+  roomService: RoomService;
+
+  @Inject()
+  mqSender: MqSender;
+  @Post('/')
+  @Validate()
+  @ApiResponse({ type: CreateVO_chatRecord })
+  async create(@Body() data: CreateDTO_chatRecord) {
+    data.time = dayjs().format('YYYY-MM-DD HH:mm:ss');
+    let result;
+    if (data.room) {
+      result = await this.service.create(data);
+    } else {
+      // 先创建room,再创建聊天记录
+      const { customer, shop, speaker } = data;
+      const roomData: any = {};
+      if (customer) {
+        roomData.customer = customer;
+        roomData.shop = speaker;
+      } else if (shop) {
+        roomData.customer = speaker;
+        roomData.shop = shop;
+      } else throw new ServiceError('缺少参数', FrameworkErrorEnum.BAD_BODY);
+      delete data.customer;
+      delete data.shop;
+      const roomId = await this.roomService.checkRoomIsExist(roomData);
+      if (roomId) {
+        data.room = roomId;
+      } else {
+        roomData.last_chat = data.content;
+        roomData.last_person = speaker;
+        roomData.last_time = data.time;
+        const roomDbData = await this.roomService.create(roomData);
+        if (!roomDbData) throw new ServiceError('房间创建失败', FrameworkErrorEnum.SERVICE_FAULT);
+        data.room = get(roomDbData, '_id');
+      }
+      result = await this.service.create(data);
+    }
+    // mq 发送消息
+    const room = await this.roomService.fetch(data.room);
+    let receiver;
+    if (get(room, 'customer') === get(data, 'speaker')) receiver = get(room, 'shop');
+    else receiver = get(room, 'customer');
+    await this.mqSender.toSendMsg(receiver, data);
+    return result;
+  }
+  @Get('/')
+  @ApiQuery({ name: 'query' })
+  @ApiResponse({ type: QueryVO_chatRecord })
+  async query(@Query() filter: QueryDTO_chatRecord, @Query('skip') skip: number, @Query('limit') limit: number) {
+    const data = await this.service.query(filter, { skip, limit });
+    const total = await this.service.count(filter);
+    return { data, total };
+  }
+
+  @Post('/read')
+  @Validate()
+  async read(@Body() body: ReadDTO) {
+    await this.service.toRead(body.ids);
+    return 'ok';
+  }
+
+  @Get('/:id')
+  @ApiResponse({ type: FetchVO_chatRecord })
+  async fetch(@Param('id') id: string) {
+    const data = await this.service.fetch(id);
+    const result = new FetchVO_chatRecord(data);
+    return result;
+  }
+
+  // @Post('/:id')
+  @Validate()
+  @ApiResponse({ type: UpdateVO_chatRecord })
+  async update(@Param('id') id: string, @Body() body: UpdateDTO_chatRecord) {
+    const result = await this.service.updateOne(id, body);
+    return result;
+  }
+
+  // @Del('/:id')
+  @Validate()
+  async delete(@Param('id') id: string) {
+    await this.service.delete(id);
+    return 'ok';
+  }
+  async createMany(...args: any[]) {
+    throw new Error('Method not implemented.');
+  }
+
+  async updateMany(...args: any[]) {
+    throw new Error('Method not implemented.');
+  }
+
+  async deleteMany(...args: any[]) {
+    throw new Error('Method not implemented.');
+  }
+}

+ 9 - 0
src/controller/home.controller.ts

@@ -0,0 +1,9 @@
+import { Controller, Get } from '@midwayjs/decorator';
+
+@Controller('/')
+export class HomeController {
+  @Get('/')
+  async home(): Promise<string> {
+    return 'Hello Midwayjs!';
+  }
+}

+ 62 - 0
src/controller/room.controller.ts

@@ -0,0 +1,62 @@
+import { Body, Controller, Get, Inject, Param, Query } from '@midwayjs/decorator';
+import { BaseController } from 'free-midway-component';
+import { RoomService } from '../service/room.service';
+import { CreateDTO_room, CreateVO_room, FetchVO_room, QueryDTO_room, QueryVO_room, UpdateDTO_room, UpdateVO_room } from '../interface/room.interface';
+import { ApiResponse, ApiTags, ApiQuery } from '@midwayjs/swagger';
+import { Validate } from '@midwayjs/validate';
+@ApiTags(['房间表'])
+@Controller('/room')
+export class RoomController extends BaseController {
+  @Inject()
+  service: RoomService;
+
+  // @Post('/')
+  @Validate()
+  @ApiResponse({ type: CreateVO_room })
+  async create(@Body() data: CreateDTO_room) {
+    const result = await this.service.create(data);
+    return result;
+  }
+  @Get('/')
+  @ApiQuery({ name: 'query' })
+  @ApiResponse({ type: QueryVO_room })
+  async query(@Query() filter: QueryDTO_room, @Query('skip') skip: number, @Query('limit') limit: number) {
+    const data = await this.service.query(filter, { skip, limit });
+    const total = await this.service.count(filter);
+    return { data, total };
+  }
+
+  @Get('/:id')
+  @ApiResponse({ type: FetchVO_room })
+  async fetch(@Param('id') id: string) {
+    const data = await this.service.fetch(id);
+    const result = new FetchVO_room(data);
+    return result;
+  }
+
+  // @Post('/:id')
+  @Validate()
+  @ApiResponse({ type: UpdateVO_room })
+  async update(@Param('id') id: string, @Body() body: UpdateDTO_room) {
+    const result = await this.service.updateOne(id, body);
+    return result;
+  }
+
+  // @Del('/:id')
+  @Validate()
+  async delete(@Param('id') id: string) {
+    await this.service.delete(id);
+    return 'ok';
+  }
+  async createMany(...args: any[]) {
+    throw new Error('Method not implemented.');
+  }
+
+  async updateMany(...args: any[]) {
+    throw new Error('Method not implemented.');
+  }
+
+  async deleteMany(...args: any[]) {
+    throw new Error('Method not implemented.');
+  }
+}

+ 19 - 0
src/entity/chat/chatRecord.entity.ts

@@ -0,0 +1,19 @@
+import { modelOptions, prop } from '@typegoose/typegoose';
+import { BaseModel } from 'free-midway-component';
+@modelOptions({
+  schemaOptions: { collection: 'chatRecord' },
+})
+export class ChatRecord extends BaseModel {
+  @prop({ required: false, index: true, zh: '房间' })
+  room: string;
+  @prop({ required: false, index: true, zh: '发言人' })
+  speaker: string;
+  @prop({ required: false, index: false, zh: '内容' })
+  content: string;
+  @prop({ required: false, index: true, zh: '时间' })
+  time: string;
+  @prop({ required: false, index: true, zh: '消息类型', remark: '字典:msg_type', default: '0' })
+  msg_type: string;
+  @prop({ required: false, index: true, zh: '是否已读', remark: '字典:is_read默认未读', default: '0' })
+  is_read: string;
+}

+ 17 - 0
src/entity/chat/room.entity.ts

@@ -0,0 +1,17 @@
+import { modelOptions, prop } from '@typegoose/typegoose';
+import { BaseModel } from 'free-midway-component';
+@modelOptions({
+  schemaOptions: { collection: 'room' },
+})
+export class Room extends BaseModel {
+  @prop({ required: false, index: true, zh: '顾客' })
+  customer: string;
+  @prop({ required: false, index: true, zh: '店铺' })
+  shop: string;
+  @prop({ required: false, index: false, zh: '最后发言' })
+  last_chat: string;
+  @prop({ required: false, index: false, zh: '最后发言人' })
+  last_person: string;
+  @prop({ required: false, index: false, zh: '最后发言时间' })
+  last_time: string;
+}

+ 13 - 0
src/filter/default.filter.ts

@@ -0,0 +1,13 @@
+import { Catch } from '@midwayjs/decorator';
+import { Context } from '@midwayjs/koa';
+
+@Catch()
+export class DefaultErrorFilter {
+  async catch(err: Error, ctx: Context) {
+    // 所有的未分类错误会到这里
+    return {
+      success: false,
+      message: err.message,
+    };
+  }
+}

+ 11 - 0
src/filter/notfound.filter.ts

@@ -0,0 +1,11 @@
+import { Catch } from '@midwayjs/decorator';
+import { httpError, MidwayHttpError } from '@midwayjs/core';
+import { Context } from '@midwayjs/koa';
+
+@Catch(httpError.NotFoundError)
+export class NotFoundFilter {
+  async catch(err: MidwayHttpError, ctx: Context) {
+    // 404 错误会到这里
+    ctx.redirect('/404.html');
+  }
+}

+ 6 - 0
src/interface.ts

@@ -0,0 +1,6 @@
+/**
+ * @description User-Service parameters
+ */
+export interface IUserOptions {
+  uid: number;
+}

+ 83 - 0
src/interface/chatRecord.interface.ts

@@ -0,0 +1,83 @@
+import { OmitDto, Rule, RuleType } from '@midwayjs/validate';
+import { ApiProperty } from '@midwayjs/swagger';
+import _ = require('lodash');
+import { SearchBase } from 'free-midway-component';
+export class FetchVO_chatRecord {
+  constructor(data: object) {
+    for (const key of Object.keys(this)) {
+      this[key] = _.get(data, key);
+    }
+  }
+  @ApiProperty({ description: '数据id' })
+  _id: string = undefined;
+  @ApiProperty({ description: '房间' })
+  'room': string = undefined;
+  @ApiProperty({ description: '发言人' })
+  'speaker': string = undefined;
+  @ApiProperty({ description: '内容' })
+  'content': string = undefined;
+  @ApiProperty({ description: '时间' })
+  'time': string = undefined;
+  @ApiProperty({ description: '消息类型' })
+  'msg_type': string = undefined;
+  @ApiProperty({ description: '是否已读' })
+  'is_read': string = undefined;
+}
+
+export class QueryDTO_chatRecord extends SearchBase {
+  constructor() {
+    const like_prop = [];
+    const props = ['room', 'speaker', 'time'];
+    const mapping = [];
+    super({ like_prop, props, mapping });
+  }
+  @ApiProperty({ description: '房间' })
+  'room': string = undefined;
+  @ApiProperty({ description: '发言人' })
+  'speaker': string = undefined;
+  @ApiProperty({ description: '时间' })
+  'time': string = undefined;
+}
+
+export class QueryVO_chatRecord extends FetchVO_chatRecord {}
+
+export class CreateDTO_chatRecord {
+  @ApiProperty({ description: '房间' })
+  @Rule(RuleType['string']().empty(''))
+  'room': string = undefined;
+  @ApiProperty({ description: '发言人' })
+  @Rule(RuleType['string']().empty(''))
+  'speaker': string = undefined;
+  @ApiProperty({ description: '内容' })
+  @Rule(RuleType['string']().empty(''))
+  'content': string = undefined;
+  @ApiProperty({ description: '时间' })
+  @Rule(RuleType['string']().empty(''))
+  'time': string = undefined;
+  @ApiProperty({ description: '消息类型' })
+  @Rule(RuleType['string']().empty(''))
+  'msg_type': string = undefined;
+  // 如果没有房间id, 那么顾客id和店铺id就传1个就行,另一个位置为发言人
+  @ApiProperty({ description: '顾客' })
+  @Rule(RuleType['string']().empty(''))
+  'customer': string = undefined;
+  @ApiProperty({ description: '店铺' })
+  @Rule(RuleType['string']().empty(''))
+  'shop': string = undefined;
+}
+
+export class CreateVO_chatRecord extends FetchVO_chatRecord {}
+
+export class UpdateDTO_chatRecord extends OmitDto(CreateDTO_chatRecord, ['customer', 'shop']) {
+  @ApiProperty({ description: '数据id' })
+  @Rule(RuleType['string']().empty(''))
+  _id: string = undefined;
+}
+
+export class UpdateVO_chatRecord extends FetchVO_chatRecord {}
+
+export class ReadDTO {
+  @ApiProperty({ description: '阅读的数据id' })
+  @Rule(RuleType.array().min(1))
+  ids: Array<string> = undefined;
+}

+ 66 - 0
src/interface/room.interface.ts

@@ -0,0 +1,66 @@
+import { Rule, RuleType } from '@midwayjs/validate';
+import { ApiProperty } from '@midwayjs/swagger';
+import _ = require('lodash');
+import { SearchBase } from 'free-midway-component';
+export class FetchVO_room {
+  constructor(data: object) {
+    for (const key of Object.keys(this)) {
+      this[key] = _.get(data, key);
+    }
+  }
+  @ApiProperty({ description: '数据id' })
+  _id: string = undefined;
+  @ApiProperty({ description: '顾客' })
+  'customer': string = undefined;
+  @ApiProperty({ description: '店铺' })
+  'shop': string = undefined;
+  @ApiProperty({ description: '最后发言' })
+  'last_chat': string = undefined;
+  @ApiProperty({ description: '最后发言人' })
+  'last_person': string = undefined;
+  @ApiProperty({ description: '最后发言时间' })
+  'last_time': string = undefined;
+}
+
+export class QueryDTO_room extends SearchBase {
+  constructor() {
+    const like_prop = [];
+    const props = ['customer', 'shop'];
+    const mapping = [];
+    super({ like_prop, props, mapping });
+  }
+  @ApiProperty({ description: '顾客' })
+  'customer': string = undefined;
+  @ApiProperty({ description: '店铺' })
+  'shop': string = undefined;
+}
+
+export class QueryVO_room extends FetchVO_room {}
+
+export class CreateDTO_room {
+  @ApiProperty({ description: '顾客' })
+  @Rule(RuleType['string']().empty(''))
+  'customer': string = undefined;
+  @ApiProperty({ description: '店铺' })
+  @Rule(RuleType['string']().empty(''))
+  'shop': string = undefined;
+  @ApiProperty({ description: '最后发言' })
+  @Rule(RuleType['string']().empty(''))
+  'last_chat': string = undefined;
+  @ApiProperty({ description: '最后发言人' })
+  @Rule(RuleType['string']().empty(''))
+  'last_person': string = undefined;
+  @ApiProperty({ description: '最后发言时间' })
+  @Rule(RuleType['string']().empty(''))
+  'last_time': string = undefined;
+}
+
+export class CreateVO_room extends FetchVO_room {}
+
+export class UpdateDTO_room extends CreateDTO_room {
+  @ApiProperty({ description: '数据id' })
+  @Rule(RuleType['string']().empty(''))
+  _id: string = undefined;
+}
+
+export class UpdateVO_room extends FetchVO_room {}

+ 31 - 0
src/middleware/checkToken.middleware.ts

@@ -0,0 +1,31 @@
+import { IMiddleware } from '@midwayjs/core';
+import { Middleware, Inject } from '@midwayjs/decorator';
+import { NextFunction, Context } from '@midwayjs/koa';
+import _ = require('lodash');
+import { JwtService } from '@midwayjs/jwt';
+
+@Middleware()
+export class CheckTokenMiddleware implements IMiddleware<Context, NextFunction> {
+  @Inject()
+  jwtService: JwtService;
+  resolve() {
+    return async (ctx: Context, next: NextFunction) => {
+      const token: any = _.get(ctx.request, 'header.token');
+      if (token) {
+        const data = this.jwtService.decodeSync(token);
+        if (data) ctx.user = data;
+      }
+      // 添加管理员身份
+      const adminToken: any = _.get(ctx.request, 'header.admin-token');
+      if (adminToken) {
+        const data = this.jwtService.decodeSync(adminToken);
+        if (data) ctx.admin = data;
+      }
+      await next();
+    };
+  }
+
+  static getName(): string {
+    return 'checkToken';
+  }
+}

+ 28 - 0
src/middleware/report.middleware.ts

@@ -0,0 +1,28 @@
+import { IMiddleware } from '@midwayjs/core';
+import { Middleware } from '@midwayjs/decorator';
+import { NextFunction, Context } from '@midwayjs/koa';
+
+@Middleware()
+export class ReportMiddleware implements IMiddleware<Context, NextFunction> {
+  resolve() {
+    return async (ctx: Context, next: NextFunction) => {
+      // 控制器前执行的逻辑
+      const startTime = Date.now();
+      // 执行下一个 Web 中间件,最后执行到控制器
+      // 这里可以拿到下一个中间件或者控制器的返回值
+      const result = await next();
+      // 控制器之后执行的逻辑
+      ctx.logger.info(
+        `Report in "src/middleware/report.middleware.ts", rt = ${
+          Date.now() - startTime
+        }ms`
+      );
+      // 返回给上一个中间件的结果
+      return result;
+    };
+  }
+
+  static getName(): string {
+    return 'report';
+  }
+}

+ 16 - 0
src/service/chatRecord.service.ts

@@ -0,0 +1,16 @@
+import { Provide } from '@midwayjs/decorator';
+import { InjectEntityModel } from '@midwayjs/typegoose';
+import { ReturnModelType } from '@typegoose/typegoose';
+import { BaseService } from 'free-midway-component';
+import { ChatRecord } from '../entity/chat/chatRecord.entity';
+type modelType = ReturnModelType<typeof ChatRecord>;
+@Provide()
+export class ChatRecordService extends BaseService<modelType> {
+  @InjectEntityModel(ChatRecord)
+  model: modelType;
+
+  async toRead(list: Array<string>) {
+    const res = await this.model.updateMany({ _id: list }, { is_read: '1' });
+    return res;
+  }
+}

+ 63 - 0
src/service/mq/mqSender.service.ts

@@ -0,0 +1,63 @@
+import { App, Provide, Config } from '@midwayjs/decorator';
+import * as amqp from 'amqp-connection-manager';
+import { Application } from '@midwayjs/koa';
+import _ = require('lodash');
+import { ChannelWrapper } from 'amqp-connection-manager';
+
+@Provide()
+export class MqSender {
+  @App()
+  app: Application;
+
+  private connection: amqp.AmqpConnectionManager;
+
+  private channelWrapper: ChannelWrapper;
+
+  @Config('rabbitmq.url')
+  mqUrl: object;
+
+  @Config('mqConfig.normal')
+  mqConfig: object;
+
+  @Config('mqConfig.timeout')
+  timeout: number;
+  /**
+   * 发送实时提示
+   * 接收人需要有该提示
+   * App的情况,应该是一个手机只能存在一个webstock的连接,所以需要区分属于哪部分,应该如何处理
+   * @param receiver 接收人
+   * @param data 数据
+   */
+  async toSendMsg(receiver, data) {
+    await this.connect(receiver);
+    const res = await this.sendMsg(receiver, data);
+    console.log(res);
+    await this.close();
+  }
+
+  async connect(queue) {
+    // 创建连接,你可以把配置放在 Config 中,然后注入进来
+    this.connection = await amqp.connect(this.mqUrl);
+    // 创建 channel
+    this.channelWrapper = this.connection.createChannel({
+      json: true,
+      setup: channel => {
+        return Promise.all([
+          // 绑定队列
+          channel.assertExchange(_.get(this.mqConfig, 'ex'), 'direct', { durable: true }),
+          channel.assertQueue(queue, { durable: false, exclusive: false }),
+          channel.bindQueue(queue, _.get(this.mqConfig, 'ex')),
+        ]);
+      },
+    });
+  }
+
+  async sendMsg(q: string, data: object) {
+    return this.channelWrapper.publish(_.get(this.mqConfig, 'ex'), q, { ...data, type: 'chat' });
+  }
+
+  async close() {
+    await this.channelWrapper.close();
+    await this.connection.close();
+  }
+}

+ 17 - 0
src/service/room.service.ts

@@ -0,0 +1,17 @@
+import { Provide } from '@midwayjs/decorator';
+import { InjectEntityModel } from '@midwayjs/typegoose';
+import { ReturnModelType } from '@typegoose/typegoose';
+import { BaseService } from 'free-midway-component';
+import { Room } from '../entity/chat/room.entity';
+import get = require('lodash/get');
+type modelType = ReturnModelType<typeof Room>;
+@Provide()
+export class RoomService extends BaseService<modelType> {
+  @InjectEntityModel(Room)
+  model: modelType;
+
+  async checkRoomIsExist(query) {
+    const data = await this.model.findOne(query, { _id: 1 });
+    return get(data, '_id');
+  }
+}

+ 20 - 0
test/controller/api.test.ts

@@ -0,0 +1,20 @@
+import { createApp, close, createHttpRequest } from '@midwayjs/mock';
+import { Framework } from '@midwayjs/koa';
+
+describe('test/controller/home.test.ts', () => {
+
+  it('should POST /api/get_user', async () => {
+    // create app
+    const app = await createApp<Framework>();
+
+    // make request
+    const result = await createHttpRequest(app).get('/api/get_user').query({ uid: 123 });
+
+    // use expect by jest
+    expect(result.status).toBe(200);
+    expect(result.body.message).toBe('OK');
+
+    // close app
+    await close(app);
+  });
+});

+ 21 - 0
test/controller/home.test.ts

@@ -0,0 +1,21 @@
+import { createApp, close, createHttpRequest } from '@midwayjs/mock';
+import { Framework } from '@midwayjs/koa';
+
+describe('test/controller/home.test.ts', () => {
+
+  it('should GET /', async () => {
+    // create app
+    const app = await createApp<Framework>();
+
+    // make request
+    const result = await createHttpRequest(app).get('/');
+
+    // use expect by jest
+    expect(result.status).toBe(200);
+    expect(result.text).toBe('Hello Midwayjs!');
+
+    // close app
+    await close(app);
+  });
+
+});

+ 12 - 0
test/mq.test.ts

@@ -0,0 +1,12 @@
+import { close, createApp } from '@midwayjs/mock';
+
+describe('/test/index.test.ts', () => {
+  it('should test create message and get from app', async () => {
+    const app = await createApp();
+    console.log(app);
+
+    // wait a moment
+
+    await close(app);
+  });
+});

+ 22 - 0
tsconfig.json

@@ -0,0 +1,22 @@
+{
+  "compileOnSave": true,
+  "compilerOptions": {
+    "target": "es2018",
+    "module": "commonjs",
+    "moduleResolution": "node",
+    "experimentalDecorators": true,
+    "emitDecoratorMetadata": true,
+    "inlineSourceMap": true,
+    "noImplicitThis": true,
+    "noUnusedLocals": true,
+    "stripInternal": true,
+    "skipLibCheck": true,
+    "pretty": true,
+    "declaration": true,
+    "forceConsistentCasingInFileNames": true,
+    "typeRoots": ["./typings", "./node_modules/@types"],
+    "outDir": "dist",
+    "allowSyntheticDefaultImports": true
+  },
+  "exclude": ["dist", "node_modules", "test"]
+}