zs 11 месяцев назад
Сommit
9655386eec

+ 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

+ 11 - 0
.eslintrc.json

@@ -0,0 +1,11 @@
+{
+  "extends": "./node_modules/mwts/",
+  "ignorePatterns": ["node_modules", "dist", "test", "jest.config.js", "typings"],
+  "env": {
+    "jest": true
+  },
+  "rules": {
+    "prettier/prettier": 0,
+    "max-len": ["warn", { "code": 250 }]
+  }
+}

+ 16 - 0
.gitignore

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

+ 4 - 0
.prettierrc.js

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

+ 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();

+ 17 - 0
ecosystem.config.js

@@ -0,0 +1,17 @@
+'use strict';
+
+const app = 'zdlyjszy';
+module.exports = {
+  apps: [{
+    name: app, // 应用名称
+    script: './bootstrap.js', // 实际启动脚本
+    out: `./logs/${app}.log`,
+    error: `./logs/${app}.err`,
+    watch: [ // 监控变化的目录,一旦变化,自动重启
+      'app', 'config',
+    ],
+    env: {
+      NODE_ENV: 'production', // 环境参数,当前指定为生产环境
+    },
+  }],
+};

+ 6 - 0
jest.config.js

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

+ 59 - 0
package.json

@@ -0,0 +1,59 @@
+{
+  "name": "service",
+  "version": "1.0.0",
+  "description": "",
+  "private": true,
+  "dependencies": {
+    "@midwayjs/bootstrap": "^3.12.0",
+    "@midwayjs/core": "^3.12.0",
+    "@midwayjs/decorator": "^3.12.0",
+    "@midwayjs/info": "^3.12.0",
+    "@midwayjs/jwt": "^3.13.7",
+    "@midwayjs/koa": "^3.12.0",
+    "@midwayjs/logger": "^2.14.0",
+    "@midwayjs/swagger": "^3.13.7",
+    "@midwayjs/typegoose": "^3.0.0",
+    "@midwayjs/validate": "^3.13.7",
+    "@typegoose/typegoose": "^9.0.0",
+    "free-midway-component": "^1.0.50",
+    "lodash": "^4.17.21",
+    "mongoose": "^6.0.7"
+  },
+  "devDependencies": {
+    "@midwayjs/cli": "^2.0.0",
+    "@midwayjs/mock": "^3.12.0",
+    "@types/jest": "^29.2.0",
+    "@types/koa": "^2.13.4",
+    "@types/lodash": "^4.14.202",
+    "@types/node": "14",
+    "cross-env": "^6.0.0",
+    "jest": "^29.2.2",
+    "mwts": "^1.3.0",
+    "swagger-ui-dist": "^5.10.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 TS_NODE_TYPE_CHECK=false TS_NODE_TRANSPILE_ONLY=true 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"
+  },
+  "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: '1702860978682_3888',
+  koa: {
+    port: 6100,
+  },
+} as MidwayConfig;

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

@@ -0,0 +1,32 @@
+import { MidwayConfig } from '@midwayjs/core';
+const ip = 'host.docker.internal';
+const project = 'baoan';
+export default {
+  // use for cookie sign key, should change to your own and keep security
+  keys: '1697684406848_4978',
+  koa: {
+    port: 6100,
+    globalPrefix: '/api',
+  },
+  jwt: {
+    secret: 'Ziyouyanfa!@#',
+    expiresIn: '2d',
+  },
+  mongoose: {
+    dataSource: {
+      default: {
+        uri: `mongodb://${ip}:27017/${project}`,
+        options: {
+          user: 'admin',
+          pass: 'admin',
+          authSource: 'admin',
+          useNewUrlParser: true,
+        },
+        entities: ['./entity'],
+      },
+    },
+  },
+  upload: {
+    whitelist: null,
+  },
+} 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;

+ 42 - 0
src/configuration.ts

@@ -0,0 +1,42 @@
+import {
+  Configuration,
+  App,
+  Inject,
+  MidwayDecoratorService,
+} from '@midwayjs/core';
+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 jwt from '@midwayjs/jwt';
+import { VerifyTokenInit } from './decorator/verifyToken.decorator';
+import { CheckTokenMiddleware } from './middleware/checkToken.middleware';
+import * as swagger from '@midwayjs/swagger';
+@Configuration({
+  imports: [
+    FreeFrame,
+    validate,
+    jwt,
+    {
+      component: info,
+      enabledEnvironment: ['local'],
+    },
+    {
+      component: swagger,
+      enabledEnvironment: ['local'],
+    },
+  ],
+  importConfigs: [join(__dirname, './config')],
+})
+export class MainConfiguration {
+  @App('koa')
+  app: koa.Application;
+  @Inject()
+  decoratorService: MidwayDecoratorService;
+  async onReady() {
+    this.app.getMiddleware().insertFirst(CheckTokenMiddleware);
+    // 注解
+    VerifyTokenInit(this.decoratorService);
+  }
+}

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

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

+ 81 - 0
src/controller/user/admin.controller.ts

@@ -0,0 +1,81 @@
+import { Body, Controller, Del, Get, Inject, Param, Post, Query } from '@midwayjs/decorator';
+import { BaseController, FrameworkErrorEnum, ServiceError } from 'free-midway-component';
+import { AdminService } from '../../service/user/admin.service';
+import { CDTO_admin, CVO_admin, FVO_admin, QDTO_admin, QVO_admin, UDTO_admin, UVAO_admin } from '../../interface/user/admin.interface';
+import { ApiResponse, ApiTags, ApiQuery, ApiOperation } from '@midwayjs/swagger';
+import { Validate } from '@midwayjs/validate';
+import { Context } from '@midwayjs/koa';
+import get = require('lodash/get');
+@ApiTags(['管理员表'])
+@Controller('/admin')
+export class AdminController extends BaseController {
+  @Inject()
+  service: AdminService;
+  @Inject()
+  ctx: Context;
+  @Post('/initSuper')
+  @ApiOperation({ summary: '初始化超级管理员' })
+  async initSuper() {
+    const dbCode = get(this.ctx, 'request.headers.dbcode');
+    if (!dbCode) throw new ServiceError(FrameworkErrorEnum.BAD_PARAMS, '缺少参数,无法使用初始化管理员');
+    if (dbCode !== 'zdlyjszy') throw new ServiceError(FrameworkErrorEnum.BAD_PARAMS, '缺少标识,无法使用初始化管理员');
+    await this.service.initSuper();
+    return 'ok';
+  }
+  @Post('/')
+  @Validate()
+  @ApiOperation({ summary: '创建' })
+  @ApiResponse({ type: CVO_admin })
+  async create(@Body() data: CDTO_admin) {
+    const dbData = await this.service.create(data);
+    const result = new CVO_admin(dbData);
+    return result;
+  }
+  @Get('/')
+  @ApiQuery({ name: 'query' })
+  @ApiResponse({ type: QVO_admin })
+  async query(@Query() filter: QDTO_admin, @Query('skip') skip: number, @Query('limit') limit: number) {
+    const list = await this.service.query(filter, { skip, limit });
+    const data = [];
+    for (const i of list) {
+      const newData = new QVO_admin(i);
+      data.push(newData);
+    }
+    const total = await this.service.count(filter);
+    return { data, total };
+  }
+
+  @Get('/:id')
+  @ApiResponse({ type: FVO_admin })
+  async fetch(@Param('id') id: string) {
+    const data = await this.service.fetch(id);
+    const result = new FVO_admin(data);
+    return result;
+  }
+
+  @Post('/:id')
+  @Validate()
+  @ApiResponse({ type: UVAO_admin })
+  async update(@Param('id') id: string, @Body() body: UDTO_admin) {
+    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.');
+  }
+}

+ 68 - 0
src/controller/user/examinee.controller.ts

@@ -0,0 +1,68 @@
+import { Body, Controller, Del, Get, Inject, Param, Post, Query } from '@midwayjs/decorator';
+import { BaseController } from 'free-midway-component';
+import { ExamineeService } from '../../service/user/examinee.service';
+import { CDTO_examinee, CVO_examinee, FVO_examinee, QDTO_examinee, QVO_examinee, UDTO_examinee, UVAO_examinee } from '../../interface/user/examinee.interface';
+import { ApiResponse, ApiTags, ApiQuery } from '@midwayjs/swagger';
+import { Validate } from '@midwayjs/validate';
+@ApiTags(['用户表'])
+@Controller('/examinee')
+export class ExamineeController extends BaseController {
+  @Inject()
+  service: ExamineeService;
+
+  @Post('/')
+  @Validate()
+  @ApiResponse({ type: CVO_examinee })
+  async create(@Body() data: CDTO_examinee) {
+    const dbData = await this.service.create(data);
+    const result = new CVO_examinee(dbData);
+    return result;
+  }
+  @Get('/')
+  @ApiQuery({ name: 'query' })
+  @ApiResponse({ type: QVO_examinee })
+  async query(@Query() filter: QDTO_examinee, @Query('skip') skip: number, @Query('limit') limit: number) {
+    const list = await this.service.query(filter, { skip, limit });
+    const data = [];
+    for (const i of list) {
+      const newData = new QVO_examinee(i);
+      data.push(newData);
+    }
+    const total = await this.service.count(filter);
+    return { data, total };
+  }
+
+  @Get('/:id')
+  @ApiResponse({ type: FVO_examinee })
+  async fetch(@Param('id') id: string) {
+    const data = await this.service.fetch(id);
+    const result = new FVO_examinee(data);
+    return result;
+  }
+
+  @Post('/:id')
+  @Validate()
+  @ApiResponse({ type: UVAO_examinee })
+  async update(@Param('id') id: string, @Body() body: UDTO_examinee) {
+    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.');
+  }
+}

+ 56 - 0
src/controller/user/login.controller.ts

@@ -0,0 +1,56 @@
+import { Body, Config, Controller, Inject, Post, Get } from '@midwayjs/core';
+import { ApiTags } from '@midwayjs/swagger';
+import { LoginDTO, LoginVO, UPwdDTO } from '../../interface/user/login.interface';
+import { LoginService } from '../../service/user/login.service';
+import { JwtService } from '@midwayjs/jwt';
+import { Context } from '@midwayjs/koa';
+const assert = require('assert');
+import get = require('lodash/get');
+@ApiTags(['登录服务'])
+@Controller('/login')
+export class LoginController {
+  @Inject()
+  loginService: LoginService;
+  @Inject()
+  jwtService: JwtService;
+  @Config('jwt.secret')
+  jwtSecret;
+  @Config('jwt.expiresIn')
+  jwtExpiresIn;
+  @Inject()
+  ctx: Context;
+  /**
+   * 账密登录
+   * @param data 用户名和密码
+   * @param type 用户类型
+   */
+  @Post('/exam_account')
+  async toLogin(@Body() data: LoginDTO) {
+    const user = await this.loginService.loginByAccount(data, 'Admin');
+    let vo = new LoginVO(user);
+    vo = JSON.parse(JSON.stringify(vo));
+    const token = await this.jwtService.sign(vo, this.jwtSecret, {
+      expiresIn: this.jwtExpiresIn,
+    });
+    return token;
+  }
+
+  /**
+   * 修改密码
+   * @param data 修改密码所需数据
+   * @param type 账户类型
+   */
+  @Post('/updatePwd/:type')
+  async updatePwd(@Body() data: UPwdDTO) {
+    await this.loginService.updatePwd(data, 'Admin');
+    return 'ok';
+  }
+
+  @Get('/analysis')
+  async analysisToken() {
+    const token = get(this.ctx, 'request.header.token');
+    assert(token, '缺少token信息');
+    const result = await this.jwtService.decodeSync(token);
+    return result;
+  }
+}

+ 25 - 0
src/controller/user/token.controller.ts

@@ -0,0 +1,25 @@
+import { Controller, Get, Inject } from '@midwayjs/decorator';
+import { Context } from '@midwayjs/koa';
+import { ApiResponse, ApiTags } from '@midwayjs/swagger';
+// import { TokenService } from '../service/token.service';
+const assert = require('assert');
+import { JwtService } from '@midwayjs/jwt';
+import get = require('lodash/get');
+@ApiTags(['工具'])
+@Controller('/token')
+export class TokenController {
+  @Inject()
+  jwtService: JwtService;
+
+  @Inject()
+  ctx: Context;
+
+  @Get('/tokenView')
+  @ApiResponse({})
+  async tokenView() {
+    const token = get(this.ctx, 'request.header.token');
+    assert(token, '缺少token信息');
+    const result: any = await this.jwtService.decode(token);
+    return result;
+  }
+}

+ 37 - 0
src/decorator/verifyToken.decorator.ts

@@ -0,0 +1,37 @@
+import {
+  JoinPoint,
+  MidwayDecoratorService,
+  REQUEST_OBJ_CTX_KEY,
+  createCustomMethodDecorator,
+} from '@midwayjs/core';
+import { FrameworkErrorEnum, ServiceError } from 'free-midway-component';
+export const VERIFYTOKEN_KEY = 'decorator:verify_token';
+/**
+ *
+ * 只检测是否有token,此装饰器不做具体用token处理什么.
+ */
+export function verifyToken() {
+  return createCustomMethodDecorator(VERIFYTOKEN_KEY, {});
+}
+/**
+ * 验证token装饰器实现
+ * @param decoratorService 装饰器服务
+ */
+export function VerifyTokenInit(decoratorService: MidwayDecoratorService) {
+  decoratorService.registerMethodHandler(VERIFYTOKEN_KEY, options => {
+    return {
+      around: async (joinPoint: JoinPoint) => {
+        const instance = joinPoint.target;
+        const ctx = instance[REQUEST_OBJ_CTX_KEY];
+        const user = ctx.user;
+        if (!user)
+          throw new ServiceError(
+            '未检测到登录信息,无法访问接口!',
+            FrameworkErrorEnum.NOT_LOGIN
+          );
+        const result = await joinPoint.proceed(...joinPoint.args);
+        return result;
+      },
+    };
+  });
+}

+ 31 - 0
src/entity/user/admin.entity.ts

@@ -0,0 +1,31 @@
+import { modelOptions, prop } from '@typegoose/typegoose';
+import { BaseModel } from 'free-midway-component';
+import { isString } from 'lodash';
+@modelOptions({
+  schemaOptions: { collection: 'admin' },
+})
+export class Admin extends BaseModel {
+  @prop({ required: true, index: true, zh: '账号' })
+  account: string;
+  @prop({ required: false, index: false, zh: '名称' })
+  nick_name: string;
+  @prop({
+    required: false,
+    index: false,
+    zh: '密码',
+    select: false,
+    set: (val: string | object) => {
+      if (isString(val)) {
+        return { secret: val };
+      }
+      return val;
+    },
+  })
+  password: object;
+  @prop({ required: false, index: false, zh: '是否是超级管理员', default: '1' })
+  is_super: string;
+  @prop({ required: false, index: false, zh: '菜单' })
+  menus: Array<any>;
+  @prop({ required: false, index: false, zh: '是否启用', default: '0' })
+  is_use: string;
+}

+ 33 - 0
src/entity/user/examinee.entity.ts

@@ -0,0 +1,33 @@
+import { modelOptions, prop } from '@typegoose/typegoose';
+import { BaseModel } from 'free-midway-component';
+@modelOptions({
+  schemaOptions: { collection: 'examinee' },
+})
+export class Examinee extends BaseModel {
+  @prop({ required: false, index: false, zh: '姓名' })
+  name: string;
+  @prop({ required: false, index: false, zh: '准考证号' })
+  exam_num: string;
+  @prop({ required: false, index: false, zh: '考点编号' })
+  testsite_num: string;
+  @prop({ required: false, index: false, zh: '性别' })
+  gender: string;
+  @prop({ required: false, index: false, zh: '联系电话' })
+  phone: string;
+  @prop({ required: false, index: false, zh: '考试日期' })
+  exam_date: string;
+  @prop({ required: false, index: false, zh: '考试时间' })
+  exam_time: string;
+  @prop({ required: false, index: false, zh: '考试地址' })
+  exam_addr: string;
+  @prop({ required: false, index: false, zh: '座位号' })
+  seat_num: string;
+  @prop({ required: false, index: false, zh: '考试等级' })
+  exam_grade: string;
+  @prop({ required: false, index: false, zh: '考试类型' })
+  exam_type: string;
+  @prop({ required: false, index: false, zh: '缴费状况' })
+  is_money: string;
+  @prop({ required: false, index: false, zh: '状态' })
+  status: string;
+}

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

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

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

@@ -0,0 +1,10 @@
+import { Catch, 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;
+}

+ 91 - 0
src/interface/user/admin.interface.ts

@@ -0,0 +1,91 @@
+import { Rule, RuleType } from '@midwayjs/validate';
+import { ApiProperty } from '@midwayjs/swagger';
+import { FrameworkErrorEnum, SearchBase, ServiceError } from 'free-midway-component';
+import get = require('lodash/get');
+const dealVO = (cla, data) => {
+  for (const key in cla) {
+    const val = get(data, key);
+    if (val || val === 0) cla[key] = val;
+  }
+};
+export class FVO_admin {
+  constructor(data: object) {
+    dealVO(this, data);
+  }
+  @ApiProperty({ description: '数据id' })
+  _id: string = undefined;
+  @ApiProperty({ description: '账号' })
+  'account': string = undefined;
+  @ApiProperty({ description: '名称' })
+  'nick_name': string = undefined;
+  @ApiProperty({ description: '密码' })
+  'password': string = undefined;
+  @ApiProperty({ description: '是否是超级管理员' })
+  'is_super': string = undefined;
+  @ApiProperty({ description: '菜单' })
+  'menus': Array<any> = undefined;
+  @ApiProperty({ description: '是否启用' })
+  'is_use': string = undefined;
+}
+
+export class QDTO_admin extends SearchBase {
+  constructor() {
+    const like_prop = ['account'];
+    const props = ['account'];
+    const mapping = [];
+    super({ like_prop, props, mapping });
+  }
+  @ApiProperty({ description: '账号' })
+  'account': string = undefined;
+}
+
+export class QVO_admin extends FVO_admin {
+  constructor(data: object) {
+    super(data);
+    dealVO(this, data);
+  }
+}
+
+export class CDTO_admin {
+  @ApiProperty({ description: '账号' })
+  @Rule(RuleType['string']().required().error(new ServiceError('缺少账号', FrameworkErrorEnum.NEED_BODY)))
+  'account': string = undefined;
+  @ApiProperty({ description: '名称' })
+  @Rule(RuleType['string']().empty(''))
+  'nick_name': string = undefined;
+  @ApiProperty({ description: '密码' })
+  @Rule(RuleType['string']().empty(''))
+  'password': string = undefined;
+  @ApiProperty({ description: '是否是超级管理员' })
+  @Rule(RuleType['string']().empty(''))
+  'is_super': string = undefined;
+  @ApiProperty({ description: '菜单' })
+  @Rule(RuleType['array']().empty(''))
+  'menus': Array<any> = undefined;
+  @ApiProperty({ description: '是否启用' })
+  @Rule(RuleType['string']().empty(''))
+  'is_use': string = undefined;
+}
+
+export class CVO_admin extends FVO_admin {
+  constructor(data: object) {
+    super(data);
+    dealVO(this, data);
+  }
+}
+
+export class UDTO_admin extends CDTO_admin {
+  @ApiProperty({ description: '数据id' })
+  @Rule(RuleType['string']().empty(''))
+  _id: string = undefined;
+  @ApiProperty({ description: '账号' })
+  @Rule(RuleType['string']().empty(''))
+  'account': string = undefined;
+}
+
+export class UVAO_admin extends FVO_admin {
+  constructor(data: object) {
+    super(data);
+    dealVO(this, data);
+  }
+}

+ 121 - 0
src/interface/user/examinee.interface.ts

@@ -0,0 +1,121 @@
+import { Rule, RuleType } from '@midwayjs/validate';
+import { ApiProperty } from '@midwayjs/swagger';
+import { SearchBase } from 'free-midway-component';
+import get = require('lodash/get');
+const dealVO = (cla, data) => {
+  for (const key in cla) {
+    const val = get(data, key);
+    if (val || val === 0) cla[key] = val;
+  }
+};
+export class FVO_examinee {
+  constructor(data: object) {
+    dealVO(this, data);
+  }
+  @ApiProperty({ description: '数据id' })
+  _id: string = undefined;
+  @ApiProperty({ description: '姓名' })
+  'name': string = undefined;
+  @ApiProperty({ description: '准考证号' })
+  'exam_num': string = undefined;
+  @ApiProperty({ description: '考点编号' })
+  'testsite_num': string = undefined;
+  @ApiProperty({ description: '性别' })
+  'gender': string = undefined;
+  @ApiProperty({ description: '联系电话' })
+  'phone': string = undefined;
+  @ApiProperty({ description: '考试日期' })
+  'exam_date': string = undefined;
+  @ApiProperty({ description: '考试时间' })
+  'exam_time': string = undefined;
+  @ApiProperty({ description: '考试地址' })
+  'exam_addr': string = undefined;
+  @ApiProperty({ description: '座位号' })
+  'seat_num': string = undefined;
+  @ApiProperty({ description: '考试等级' })
+  'exam_grade': string = undefined;
+  @ApiProperty({ description: '考试类型' })
+  'exam_type': string = undefined;
+  @ApiProperty({ description: '缴费状况' })
+  'is_money': string = undefined;
+  @ApiProperty({ description: '状态' })
+  'status': string = undefined;
+}
+
+export class QDTO_examinee extends SearchBase {
+  constructor() {
+    const like_prop = [];
+    const props = [];
+    const mapping = [];
+    super({ like_prop, props, mapping });
+  }
+}
+
+export class QVO_examinee extends FVO_examinee {
+  constructor(data: object) {
+    super(data);
+    dealVO(this, data);
+  }
+}
+
+export class CDTO_examinee {
+  @ApiProperty({ description: '姓名' })
+  @Rule(RuleType['string']().empty(''))
+  'name': string = undefined;
+  @ApiProperty({ description: '准考证号' })
+  @Rule(RuleType['string']().empty(''))
+  'exam_num': string = undefined;
+  @ApiProperty({ description: '考点编号' })
+  @Rule(RuleType['string']().empty(''))
+  'testsite_num': string = undefined;
+  @ApiProperty({ description: '性别' })
+  @Rule(RuleType['string']().empty(''))
+  'gender': string = undefined;
+  @ApiProperty({ description: '联系电话' })
+  @Rule(RuleType['string']().empty(''))
+  'phone': string = undefined;
+  @ApiProperty({ description: '考试日期' })
+  @Rule(RuleType['string']().empty(''))
+  'exam_date': string = undefined;
+  @ApiProperty({ description: '考试时间' })
+  @Rule(RuleType['string']().empty(''))
+  'exam_time': string = undefined;
+  @ApiProperty({ description: '考试地址' })
+  @Rule(RuleType['string']().empty(''))
+  'exam_addr': string = undefined;
+  @ApiProperty({ description: '座位号' })
+  @Rule(RuleType['string']().empty(''))
+  'seat_num': string = undefined;
+  @ApiProperty({ description: '考试等级' })
+  @Rule(RuleType['string']().empty(''))
+  'exam_grade': string = undefined;
+  @ApiProperty({ description: '考试类型' })
+  @Rule(RuleType['string']().empty(''))
+  'exam_type': string = undefined;
+  @ApiProperty({ description: '缴费状况' })
+  @Rule(RuleType['string']().empty(''))
+  'is_money': string = undefined;
+  @ApiProperty({ description: '状态' })
+  @Rule(RuleType['string']().empty(''))
+  'status': string = undefined;
+}
+
+export class CVO_examinee extends FVO_examinee {
+  constructor(data: object) {
+    super(data);
+    dealVO(this, data);
+  }
+}
+
+export class UDTO_examinee extends CDTO_examinee {
+  @ApiProperty({ description: '数据id' })
+  @Rule(RuleType['string']().empty(''))
+  _id: string = undefined;
+}
+
+export class UVAO_examinee extends FVO_examinee {
+  constructor(data: object) {
+    super(data);
+    dealVO(this, data);
+  }
+}

+ 36 - 0
src/interface/user/login.interface.ts

@@ -0,0 +1,36 @@
+import { ApiProperty } from '@midwayjs/swagger';
+import { Rule, RuleType } from '@midwayjs/validate';
+import { get } from 'lodash';
+
+export class LoginDTO {
+  @ApiProperty({ description: '账号' })
+  @Rule(RuleType['string']().required())
+  phone: string = undefined;
+  @ApiProperty({ description: '密码' })
+  @Rule(RuleType['string']().required())
+  password: string = undefined;
+}
+
+export class UPwdDTO {
+  @ApiProperty({ description: '用户数据id' })
+  @Rule(RuleType['string']().required())
+  _id: string = undefined;
+  @ApiProperty({ description: '密码' })
+  @Rule(RuleType['string']().required())
+  password: string = undefined;
+}
+
+export class LoginVO {
+  constructor(data: object) {
+    this._id = get(data, '_id');
+    this.nick_name = get(data, 'nick_name');
+    this.name = get(data, 'name');
+    this.role = get(data, 'role');
+    this.is_super = get(data, 'is_super');
+  }
+  _id: string;
+  nick_name: string;
+  name: string;
+  role: string;
+  is_super: number;
+}

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

@@ -0,0 +1,33 @@
+import { IMiddleware } from '@midwayjs/core';
+import { Middleware, Inject } from '@midwayjs/decorator';
+import { NextFunction, Context } from '@midwayjs/koa';
+import get = require('lodash/get');
+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';
+  }
+}

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

@@ -0,0 +1,27 @@
+import { Middleware, IMiddleware } from '@midwayjs/core';
+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';
+  }
+}

+ 23 - 0
src/service/user/admin.service.ts

@@ -0,0 +1,23 @@
+import { Provide } from '@midwayjs/decorator';
+import { InjectEntityModel } from '@midwayjs/typegoose';
+import { ReturnModelType } from '@typegoose/typegoose';
+import { BaseService } from 'free-midway-component';
+import { Admin } from '../../entity/user/admin.entity';
+type modelType = ReturnModelType<typeof Admin>;
+@Provide()
+export class AdminService extends BaseService<modelType> {
+  @InjectEntityModel(Admin)
+  model: modelType;
+
+  async initSuper() {
+    const data = {
+      account: 'admin',
+      password: '1qaz2wsx',
+      nick_name: '系统管理员',
+      is_super: '0',
+    };
+    const is_exist = await this.model.count({ is_super: '0' });
+    if (!is_exist) await this.model.create(data);
+    return;
+  }
+}

+ 11 - 0
src/service/user/examinee.service.ts

@@ -0,0 +1,11 @@
+import { Provide } from '@midwayjs/decorator';
+import { InjectEntityModel } from '@midwayjs/typegoose';
+import { ReturnModelType } from '@typegoose/typegoose';
+import { BaseService } from 'free-midway-component';
+import { Examinee } from '../../entity/user/examinee.entity';
+type modelType = ReturnModelType<typeof Examinee>;
+@Provide()
+export class ExamineeService extends BaseService<modelType> {
+  @InjectEntityModel(Examinee)
+  model: modelType;
+}

+ 29 - 0
src/service/user/login.service.ts

@@ -0,0 +1,29 @@
+import { Provide } from '@midwayjs/core';
+import { FrameworkErrorEnum, GetModel, ServiceError } from 'free-midway-component';
+import { isEqual, upperFirst } from 'lodash';
+import { LoginDTO, UPwdDTO } from '../../interface/user/login.interface';
+
+@Provide()
+export class LoginService {
+  /**
+   * 账密登录
+   * @param data 用户名和密码
+   * @param type 用户类型
+   * @returns 用户信息/空值
+   */
+  async loginByAccount(data: LoginDTO, type: string) {
+    const model = GetModel(upperFirst(type));
+    const user = await model.findOne({ account: data.phone }, '+password').lean();
+    if (!user) throw new ServiceError('未找到用户信息', FrameworkErrorEnum.NOT_FOUND_DATA);
+    if (!isEqual(user.password.secret, data.password)) throw new ServiceError('密码错误', FrameworkErrorEnum.SERVICE_FAULT);
+    return user;
+  }
+
+  async updatePwd(data: UPwdDTO, type: string) {
+    const model = GetModel(upperFirst(type));
+    const user = await model.findById(data._id);
+    if (!user) new ServiceError('未找到用户信息!', FrameworkErrorEnum.DATA_NOT_FOUND);
+    user.password = data.password;
+    await user.save();
+  }
+}

+ 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);
+  });
+
+});

+ 25 - 0
tsconfig.json

@@ -0,0 +1,25 @@
+{
+  "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"
+  },
+  "exclude": [
+    "dist",
+    "node_modules",
+    "test"
+  ]
+}