Kaynağa Gözat

Tg 机器人后端

zhejiushizhuce 1 yıl önce
işleme
31de361176
100 değiştirilmiş dosya ile 6942 ekleme ve 0 silme
  1. 28 0
      .autod.conf.js
  2. 5 0
      .eslintignore
  3. 9 0
      .eslintrc
  4. 28 0
      .gitignore
  5. 12 0
      .travis.yml
  6. 21 0
      LICENSE
  7. 180 0
      README.md
  8. 15 0
      agent.ts
  9. 6 0
      apidoc.json
  10. 68 0
      app.ts
  11. 133 0
      app/controller/admin/comm/login.ts
  12. 141 0
      app/controller/admin/space/image.ts
  13. 117 0
      app/controller/admin/sys/dept.ts
  14. 33 0
      app/controller/admin/sys/login_log.ts
  15. 167 0
      app/controller/admin/sys/menu.ts
  16. 58 0
      app/controller/admin/sys/online.ts
  17. 49 0
      app/controller/admin/sys/req_log.ts
  18. 137 0
      app/controller/admin/sys/role.ts
  19. 159 0
      app/controller/admin/sys/task.ts
  20. 33 0
      app/controller/admin/sys/task_log.ts
  21. 120 0
      app/controller/admin/sys/user.ts
  22. 61 0
      app/controller/admin/tg/tgbot.ts
  23. 122 0
      app/controller/admin/tg/tggroup.ts
  24. 74 0
      app/controller/base.ts
  25. 116 0
      app/decorator/router_register.ts
  26. 40 0
      app/dto/admin/space/image.ts
  27. 68 0
      app/dto/admin/sys/dept.ts
  28. 11 0
      app/dto/admin/sys/log.ts
  29. 93 0
      app/dto/admin/sys/menu.ts
  30. 8 0
      app/dto/admin/sys/online.ts
  31. 53 0
      app/dto/admin/sys/role.ts
  32. 121 0
      app/dto/admin/sys/task.ts
  33. 80 0
      app/dto/admin/sys/user.ts
  34. 38 0
      app/dto/admin/tg/bot.ts
  35. 71 0
      app/dto/admin/tg/group.ts
  36. 62 0
      app/dto/admin/verify.ts
  37. 45 0
      app/dto/comm.ts
  38. 18 0
      app/entities/admin/image/space/info.ts
  39. 12 0
      app/entities/admin/image/space/type.ts
  40. 18 0
      app/entities/admin/sys/department.ts
  41. 20 0
      app/entities/admin/sys/login_log.ts
  42. 42 0
      app/entities/admin/sys/menu.ts
  43. 29 0
      app/entities/admin/sys/req_log.ts
  44. 23 0
      app/entities/admin/sys/role.ts
  45. 14 0
      app/entities/admin/sys/role_department.ts
  46. 14 0
      app/entities/admin/sys/role_menu.ts
  47. 45 0
      app/entities/admin/sys/task.ts
  48. 18 0
      app/entities/admin/sys/task_log.ts
  49. 39 0
      app/entities/admin/sys/user.ts
  50. 14 0
      app/entities/admin/sys/user_role.ts
  51. 66 0
      app/entities/admin/tg/TgBot.ts
  52. 30 0
      app/entities/admin/tg/TgBotSendLog.ts
  53. 81 0
      app/entities/admin/tg/TgGroup.ts
  54. 53 0
      app/entities/admin/tg/TgGroupBot.ts
  55. 98 0
      app/entities/admin/tg/TgMessage.ts
  56. 9 0
      app/entities/base.ts
  57. 106 0
      app/extend/helper.ts
  58. 87 0
      app/middleware/admin_authority.ts
  59. 19 0
      app/middleware/admin_req_log.ts
  60. 24 0
      app/middleware/execption.ts
  61. 32 0
      app/queue/sys.ts
  62. 9 0
      app/router.ts
  63. 32 0
      app/service/admin/comm/email.ts
  64. 78 0
      app/service/admin/comm/oss.ts
  65. 105 0
      app/service/admin/comm/verify.ts
  66. 84 0
      app/service/admin/space/image.ts
  67. 92 0
      app/service/admin/sys/dept.ts
  68. 65 0
      app/service/admin/sys/login_log.ts
  69. 155 0
      app/service/admin/sys/menu.ts
  70. 52 0
      app/service/admin/sys/online.ts
  71. 76 0
      app/service/admin/sys/req_log.ts
  72. 205 0
      app/service/admin/sys/role.ts
  73. 211 0
      app/service/admin/sys/task.ts
  74. 71 0
      app/service/admin/sys/task_log.ts
  75. 296 0
      app/service/admin/sys/user.ts
  76. 23 0
      app/service/admin/tg/tgbase.ts
  77. 72 0
      app/service/admin/tg/tgbot.ts
  78. 157 0
      app/service/admin/tg/tgbot_api.ts
  79. 159 0
      app/service/admin/tg/tggroup.ts
  80. 9 0
      app/service/admin/tg/tggrouptype.ts
  81. 65 0
      app/service/admin/tg/tglog.ts
  82. 12 0
      app/service/admin/tg/tgmsghandler.ts
  83. 31 0
      app/service/base.ts
  84. 33 0
      app/service/reptile/book.ts
  85. 14 0
      appveyor.yml
  86. 117 0
      config/config.default.ts
  87. 28 0
      config/error_constants.yaml
  88. 31 0
      config/plugin.ts
  89. 685 0
      db/init.sql
  90. 398 0
      db/oldinit.sql
  91. 2 0
      db/seed/image_space_info.sql
  92. 2 0
      db/seed/image_space_type.sql
  93. 2 0
      db/seed/sys_department.sql
  94. 4 0
      db/seed/sys_login_log.sql
  95. 47 0
      db/seed/sys_menu.sql
  96. 0 0
      db/seed/sys_req_log.sql
  97. 2 0
      db/seed/sys_role.sql
  98. 2 0
      db/seed/sys_role_department.sql
  99. 19 0
      db/seed/sys_role_menu.sql
  100. 4 0
      db/seed/sys_task.sql

+ 28 - 0
.autod.conf.js

@@ -0,0 +1,28 @@
+'use strict';
+
+module.exports = {
+  write: true,
+  plugin: 'autod-egg',
+  prefix: '^',
+  devprefix: '^',
+  exclude: [
+    'test/fixtures',
+    'coverage',
+  ],
+  dep: [
+    'egg',
+    'egg-scripts',
+  ],
+  devdep: [
+    'autod',
+    'autod-egg',
+    'egg-bin',
+    'tslib',
+    'typescript',
+  ],
+  keep: [
+  ],
+  semver: [
+  ],
+  test: 'scripts',
+};

+ 5 - 0
.eslintignore

@@ -0,0 +1,5 @@
+**/*.d.ts
+node_modules/
+docs/
+.vscode
+public/

+ 9 - 0
.eslintrc

@@ -0,0 +1,9 @@
+{
+  "extends": "eslint-config-egg/typescript",
+  "parserOptions": {
+    "project": "./tsconfig.json"
+  },
+  "rules": {
+    "jsdoc/check-tag-names": "off"
+  }
+}

+ 28 - 0
.gitignore

@@ -0,0 +1,28 @@
+logs/
+npm-debug.log
+node_modules/
+coverage/
+.idea/
+run/
+logs/
+.DS_Store
+.vscode
+*.swp
+*.lock
+#*.js
+!.autod.conf.js
+package-lock.json
+
+app/**/*.js
+test/**/*.js
+config/**/*.js
+app/**/*.map
+test/**/*.map
+config/**/*.map
+
+config/config.local.ts
+config/config.prod.ts
+
+typings/app/*
+typings/config/*
+typings/typeorm.d.ts

+ 12 - 0
.travis.yml

@@ -0,0 +1,12 @@
+
+language: node_js
+node_js:
+  - '8'
+before_install:
+  - npm i npminstall@5 -g
+install:
+  - npminstall
+script:
+  - npm run ci
+after_script:
+  - npminstall codecov && codecov

+ 21 - 0
LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2020 Mr.Yang
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 180 - 0
README.md

@@ -0,0 +1,180 @@
+### sf-egg-admin
+
+![](https://img.shields.io/github/commit-activity/m/hackycy/sf-egg-admin) ![](https://img.shields.io/github/license/hackycy/sf-egg-admin) ![](https://img.shields.io/github/repo-size/hackycy/sf-egg-admin) ![](https://img.shields.io/github/languages/top/hackycy/sf-egg-admin)
+
+**基于EggJs + TypeScript + TypeORM + Redis + MySql + Vue + Element-UI编写的一款简单高效的前后端分离的权限管理系统。希望这个项目在全栈的路上能够帮助到你。**
+
+> egg版已不作维护,请转移至midway或者nest版本
+
+前端项目地址:[传送门](https://github.com/hackycy/sf-vue-admin)
+
+midway版:[传送门](https://github.com/hackycy/sf-midway-admin)
+
+nest版:[传送门](https://github.com/hackycy/sf-nest-admin)
+
+### 演示地址
+
+- [http://opensource.admin.si-yee.com](http://opensource.admin.si-yee.com/)
+
+- [Api文档](http://blog.si-yee.com/sf-egg-admin/api/index.html)
+
+演示环境账号密码:
+
+|     账号     |  密码  |           权限           |
+| :----------: | :----: | :----------------------: |
+|  openadmin   | 123456 | 仅只有各个功能的查询权限 |
+| monitoradmin | 123456 |  系统监控页面及按钮权限  |
+
+本地部署账号密码:
+
+|   账号    |  密码  |    权限    |
+| :-------: | :----: | :--------: |
+| rootadmin | 123456 | 超级管理员 |
+
+### 系统模块
+
+```bash
+├─系统管理
+│  ├─用户管理
+│  ├─角色管理
+│  ├─菜单管理
+├─系统监控
+│  ├─在线用户
+│  ├─登录日志
+│  ├─请求追踪
+├─任务调度
+│  ├─定时任务
+│  └─任务日志
+```
+
+### 系统特点
+
+- 前后端请求参数校验
+- JWT 认证
+- 基于 EggJS 框架,内置了基础的中间件支持(用户认证、访问日志、请求追踪等)
+
+- 用户权限动态刷新
+
+- 代码简单,结构清晰
+
+### 技术选型
+
+#### 后端
+
+- EggJS + TypeScript
+- TypeORM(MYSQL)
+- ioredis(Redis)
+- bull(队列)
+
+#### 前端
+
+- Vue全家桶
+- Element-UI
+
+### 本地开发
+
+#### 初始化数据库,以及服务启动
+
+新建并导入数据库`MySql>=5.7`.
+
+数据库脚本位于 `db/init.sql`
+
+修改数据库配置信息,在`config`目录下更改对应模式下的配置
+
+基于[apidoc](https://github.com/apidoc/apidoc)自动生成的API文档,在Controller下写好注释后,运行命令`npm run apidoc`即可自动更新生成API文档。
+
+**参考对应配置请参考:[config.local.ts](https://github.com/hackycy/sf-egg-admin/blob/master/docs/sample/config.local.ts)**
+
+#### 项目启动
+
+``` bash
+$ npm i
+$ npm run dev
+```
+
+> 请不要在开发模式下运行`npm run tsc`,如果不小心运行了你需要在`npm run dev`前运行`npm run clean`
+
+### 系统截图
+
+![](https://raw.githubusercontent.com/hackycy/sf-egg-admin/master/docs/screenshot/1.png)
+
+![](https://raw.githubusercontent.com/hackycy/sf-egg-admin/master/docs/screenshot/2.png)
+
+![](https://raw.githubusercontent.com/hackycy/sf-egg-admin/master/docs/screenshot/2.png)
+
+![](https://raw.githubusercontent.com/hackycy/sf-egg-admin/master/docs/screenshot/4.png)
+
+### 项目部署
+
+#### 执行
+
+```
+$ npm run tsc
+$ npm start
+```
+
+#### 反向代理配置示例
+
+```conf
+server
+{
+    # ... 省略
+		
+	# 请添加以下配置
+    location / {
+      try_files $uri $uri/ /index.html;
+    }
+
+    location /api/
+    {
+        proxy_pass http://127.0.0.1:7001/;
+        proxy_set_header Host $host;
+        proxy_set_header X-Real-IP $remote_addr;
+        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+        proxy_set_header REMOTE-HOST $remote_addr;
+
+        #缓存相关配置
+        #proxy_cache cache_one;
+        #proxy_cache_key $host$request_uri$is_args$args;
+        #proxy_cache_valid 200 304 301 302 1h;
+
+        #持久化连接相关配置
+        proxy_connect_timeout 3000s;
+        proxy_read_timeout 86400s;
+        proxy_send_timeout 3000s;
+        #proxy_http_version 1.1;
+        #proxy_set_header Upgrade $http_upgrade;
+        #proxy_set_header Connection "upgrade";
+
+        add_header X-Cache $upstream_cache_status;
+
+        #expires 12h;
+    }
+    
+    # ... 省略
+}
+```
+
+### 环境要求
+
+- Node.js 8.x
+- Typescript 2.8+
+- MYSQL 5.7+
+- Redis 6.0+
+
+### 依赖的第三方库
+
+- [egg-typeorm](https://github.com/hackycy/egg-typeorm)
+- [egg-class-validator](https://github.com/hackycy/egg-class-validator)
+- [egg-bull](https://github.com/hackycy/egg-bull)
+- [egg-redis](https://github.com/eggjs/egg-redis/)
+- [svg-captcha](https://github.com/produck/svg-captcha)
+- ...略
+
+### 欢迎Star && PR
+
+**如果项目有帮助到你可以点个Star支持下。有更好的实现欢迎PR。**
+
+### 致谢
+
+- [vue-element-admin](https://github.com/PanJiaChen/vue-element-admin)

+ 15 - 0
agent.ts

@@ -0,0 +1,15 @@
+import { Agent } from 'egg';
+
+/**
+ * https://eggjs.org/zh-cn/core/cluster-and-ipc.html
+ */
+export default (agent: Agent) => {
+  agent.messenger.on('egg-ready', () => {
+    agent.messenger.sendRandom('init-task', { name: 'init-task' });
+    agent.messenger.sendRandom('init-tgboot-api', { name: 'init-tgboot-api' });
+  });
+  // 挂了重置机器人
+  agent.messenger.on('reset-tgboot-api', () => {
+    agent.messenger.sendRandom('init-tgboot-api', { name: 'init-tgboot-api' });
+  });
+};

+ 6 - 0
apidoc.json

@@ -0,0 +1,6 @@
+{
+  "name": "sf-admin",
+  "version": "0.0.1",
+  "description": "sf-admin通用后台权限管理系统API文档",
+  "title": "API文档"
+}

+ 68 - 0
app.ts

@@ -0,0 +1,68 @@
+/**
+ * Application
+ */
+
+import { Application, IBoot, Context } from 'egg';
+import * as moment from 'moment';
+// import { worker } from 'cluster';
+
+export default class AdminBoot implements IBoot {
+  // private readonly app: Application;
+  private readonly ctx: Context;
+
+  constructor(app: Application) {
+    // this.app = app;
+    this.ctx = app.createAnonymousContext();
+    // agent send
+    app.messenger.on('init-task', () => {
+      // 启动任务
+      this.ctx.service.admin.sys.task.initTask();
+    });
+    // tgboot
+    app.messenger.on('init-tgboot-api', () => {
+      // const tg_wid = worker.id;
+      this.ctx.service.admin.tg.tgbotApi.initTgbot();
+      app.messenger.on('req-tgboot-api', async ({ reqId, cmd, data }) => {
+        const result = await this.ctx.service.admin.tg.tgbotApi.handleReq(cmd, data);
+        app.messenger.sendToApp(`resp-tgboot-api-${reqId}`, result);
+      });
+    });
+
+    // Date time
+    // eslint-disable-next-line no-extend-native
+    Date.prototype.toJSON = function() {
+      return moment(this).format('YYYY-MM-DD HH:mm:ss');
+    };
+  }
+
+  configWillLoad() {
+    // Ready to call configDidLoad,
+    // Config, plugin files are referred,
+    // this is the last chance to modify the config.
+  }
+
+  configDidLoad() {
+    // Config, plugin files have loaded.
+  }
+
+  async didLoad() {
+    // All files have loaded, start plugin here.
+  }
+
+  async willReady() {
+    // All plugins have started, can do some thing before app ready.
+  }
+
+  async didReady() {
+    // Worker is ready, can do some things
+    // don't need to block the app boot.
+  }
+
+  async serverDidReady() {
+    // Server is listening.
+  }
+
+  async beforeClose() {
+    // Do some thing before app close.
+  }
+}

+ 133 - 0
app/controller/admin/comm/login.ts

@@ -0,0 +1,133 @@
+import BaseController from '../../base';
+import { AdminRoute } from '../../../decorator/router_register';
+import { LoginInfoDto, UpdatePersonInfoDto } from '../../../dto/admin/verify';
+
+/**
+ * 通用功能控制器
+ */
+export default class CommController extends BaseController {
+
+  /**
+   * @api {get} /admin/captcha/img 获取图片验证码
+   * @apiGroup 登陆验证类
+   * @apiParam {Number} [width=100] 图片宽度
+   * @apiParam {Number} [height=50] 图片高度
+   * @apiUse BaseRes
+   * @apiUse Auth
+   * @apiSuccess {String} data.img base64格式的验证码图片字符串
+   * @apiSuccess {String} data.id 验证码对应ID
+   */
+  @AdminRoute('/captcha/img', 'get')
+  async captchaByImg() {
+    const result = await this.service.admin.comm.verify.getImgCaptcha(this.getQuery());
+    this.res({
+      data: result,
+    });
+  }
+
+  /**
+   * @api {post} /admin/login 管理员登陆
+   * @apiGroup 登陆验证类
+   * @apiParam {String} username 用户名,AES加密
+   * @apiParam {String} password 密码,AES加密
+   * @apiParam {String} captchaId 验证码ID
+   * @apiParam {String} verifyCode 填写的验证码
+   * @apiUse BaseRes
+   * @apiSuccess {String} data.token 用户Token
+   */
+  @AdminRoute('/login', 'post')
+  async login() {
+    const dto = await this.ctx.validate<LoginInfoDto>(LoginInfoDto);
+    // const { username, password, captchaId, verifyCode } = info;
+    const success = await this.service.admin.comm.verify.checkImgCaptcha(dto.captchaId, dto.verifyCode);
+    if (!success) {
+      this.res({
+        code: 10002,
+      });
+      return;
+    }
+    const sign = await this.service.admin.comm.verify.getLoginSign(dto.username, dto.password);
+    if (!sign) {
+      this.res({
+        code: 10003,
+      });
+      return;
+    }
+    this.res({
+      data: {
+        token: sign,
+      },
+    });
+  }
+
+  /**
+   * @api {post} /admin/logout 管理员登出
+   * @apiGroup 登陆验证类
+   * @apiUse Auth
+   * @apiUse BaseRes
+   */
+  @AdminRoute('/logout', 'post')
+  async logout() {
+    await this.service.admin.comm.verify.clearLoginStatus(this.ctx.token.uid);
+    this.res();
+  }
+
+  /**
+   * @api {get} /admin/permmenu 获取权限及菜单
+   * @apiGroup 登陆验证类
+   * @apiUse BaseRes
+   * @apiUse Auth
+   * @apiSuccess {Array} data.menus 菜单
+   * @apiSuccess {Array} data.perms 权限描述
+   */
+  @AdminRoute('/permmenu', 'get')
+  async permmenu() {
+    this.res({
+      data: await this.service.admin.comm.verify.getPermMenu(this.ctx.token.uid),
+    });
+  }
+
+  /**
+   * @api {get} /admin/person 获取当前登录用户信息
+   * @apiGroup 登陆验证类
+   * @apiUse Auth
+   * @apiUse BaseRes
+   * @apiSuccess {Object} data 管理员信息实体类
+   */
+  @AdminRoute('/person', 'get')
+  async person() {
+    this.res({
+      data: await this.service.admin.sys.user.person(this.ctx.token.uid),
+    });
+  }
+
+  /**
+   * @api {post} /admin/person 更新管理员信息
+   * @apiGroup 登陆验证类
+   * @apiUse Auth
+   * @apiUse BaseRes
+   * @apiParam {String} name 名称
+   * @apiParam {String} nickName 别名
+   * @apiParam {String} email 邮箱
+   * @apiParam {String} phone 手机
+   * @apiParam {String} originPassword 更改前密码
+   * @apiParam {String} newPassword 新密码
+   * @apiParam {String} remark 备注
+   * @apiParam {String} headImg 头像
+   */
+  @AdminRoute('/person', 'post')
+  async personUpdate() {
+    const dto = await this.ctx.validate<UpdatePersonInfoDto>(UpdatePersonInfoDto);
+    const result = await this.service.admin.sys.user.personUpdate(this.ctx.token.uid, dto);
+    if (!result) {
+      this.res({
+        code: 10011,
+      });
+    } else {
+      this.res({
+        data: await this.service.admin.sys.user.person(this.ctx.token.uid),
+      });
+    }
+  }
+
+}

+ 141 - 0
app/controller/admin/space/image.ts

@@ -0,0 +1,141 @@
+import { AdminRoute } from '../../../decorator/router_register';
+import BaseController from '../../base';
+import * as _ from 'lodash';
+import { QueryImageDto, CreateTypeDto, DeleteTypeDto, DeleteImageDto, UploadImageDto } from '../../../dto/admin/space/image';
+
+/**
+ * 图片空间控制器
+ */
+export default class ImageSpaceController extends BaseController {
+
+  /**
+   * @api {get} /admin/space/image/page 获取图片信息列表
+   * @apiGroup 图片空间
+   * @apiUse Auth
+   * @apiUse BaseRes
+   * @apiParam {Number} typeId 类别编号
+   * @apiUse Page
+   * @apiSuccess {ImageSpaceInfo[]} data.list 图片信息列表
+   */
+  @AdminRoute('/space/image/page', 'get')
+  async page() {
+    const dto = await this.ctx.validate<QueryImageDto>(QueryImageDto, this.getQuery());
+    this.res({
+      data: {
+        list: await this.service.admin.space.image.page(parseInt(dto.typeId), dto.current - 1, dto.pageSize),
+        pagination: {
+          page: dto.current,
+          size: dto.pageSize,
+          total: await this.service.admin.space.image.count(parseInt(dto.typeId)),
+        },
+      },
+    });
+  }
+
+  /**
+   * @api {get} /admin/space/image/type/list 获取图片空间类别列表
+   * @apiGroup 图片空间
+   * @apiUse Auth
+   * @apiUse BaseRes
+   * @apiSuccess {Array} data 图片空间类别列表
+   */
+  @AdminRoute('/space/image/type/list', 'get')
+  async typeList() {
+    this.res({
+      data: await this.service.admin.space.image.type(),
+    });
+  }
+
+  /**
+   * @api {post} /admin/space/image/type/add 新增图片空间类别
+   * @apiGroup 图片空间
+   * @apiUse Auth
+   * @apiUse BaseRes
+   * @apiParam {String} name 类别名称
+   */
+  @AdminRoute('/space/image/type/add', 'post')
+  async addType() {
+    const dto = await this.ctx.validate<CreateTypeDto>(CreateTypeDto);
+    await this.service.admin.space.image.addType(dto.name);
+    this.res();
+  }
+
+  /**
+   * @api {post} /admin/space/image/type/delete 删除图片空间类别
+   * @apiGroup 图片空间
+   * @apiUse Auth
+   * @apiUse BaseRes
+   * @apiParam {Number} typeId 类别编号
+   */
+  @AdminRoute('/space/image/type/delete', 'post')
+  async deleteType() {
+    const dto = await this.ctx.validate<DeleteTypeDto>(DeleteTypeDto);
+    const hasImage = await this.service.admin.space.image.findCurrentTypeHasImage(dto.typeId);
+    if (hasImage) {
+      this.res({
+        code: 20003,
+      });
+      return;
+    }
+    await this.service.admin.space.image.deleteType(dto.typeId);
+    this.res();
+  }
+
+  /**
+   * @api {post} /admin/space/image/delete 删除空间类别下的图片列表
+   * @apiGroup 图片空间
+   * @apiUse Auth
+   * @apiUse BaseRes
+   * @apiParam {Number[]} imageIds 图片ID列表
+   */
+  @AdminRoute('/space/image/delete', 'post')
+  async deleteImage() {
+    const dto = await this.ctx.validate<DeleteImageDto>(DeleteImageDto);
+    await this.service.admin.space.image.deleteImageByIds(dto.imageIds);
+    this.res();
+  }
+
+  /**
+   * @api {post} /admin/space/image/upload 图片上传(表单)
+   * @apiGroup 图片空间
+   * @apiUse Auth
+   * @apiUse BaseRes
+   * @apiParam {Number} typeId 图片ID列表
+   */
+  @AdminRoute('/space/image/upload', 'post')
+  async upload() {
+    const dto = await this.ctx.validate<UploadImageDto>(UploadImageDto);
+    const typeId = parseInt(dto.typeId);
+    if (typeId === -1) {
+      this.res({ code: 10000 });
+      return;
+    }
+    const file = this.ctx.request.files[0];
+    if (!file) {
+      this.res({ code: 20001 });
+      return;
+    }
+    const type = await this.service.admin.space.image.find(typeId);
+    // 查找图片空间是否存在
+    if (_.isEmpty(type)) {
+      this.res({ code: 20002 });
+      return;
+    }
+    const { cdnUrl } = this.config.qiniu;
+    try {
+      const data: any = await this.service.admin.comm.oss.upload(file, type!.name);
+      const { key, hash } = data;
+      const url = `${cdnUrl}/${key}`;
+      await this.service.admin.space.image.add(typeId, url, JSON.stringify({ key, hash }));
+      this.res({
+        data: {
+          url,
+          hash,
+        },
+      });
+    } catch (e) {
+      this.res({ code: 20001 });
+    }
+  }
+
+}

+ 117 - 0
app/controller/admin/sys/dept.ts

@@ -0,0 +1,117 @@
+import BaseController from '../../base';
+import { AdminRoute } from '../../../decorator/router_register';
+import { CreateDeptDto, DeleteDeptDto, InfoDeptDto, UpdateDeptDto, TransferDeptDto } from '../../../dto/admin/sys/dept';
+
+/**
+ * 系统部门控制器
+ */
+export default class SysDeptController extends BaseController {
+
+  /**
+   * @api {get} /admin/sys/dept/list 获取部门列表
+   * @apiGroup 系统部门
+   * @apiUse Auth
+   * @apiUse BaseRes
+   */
+  @AdminRoute('/sys/dept/list', 'get')
+  async list() {
+    this.res(
+      {
+        data: await this.service.admin.sys.dept.getDepts(this.ctx.token.uid),
+      },
+    );
+  }
+
+  /**
+   * @api {post} /admin/sys/dept/add 增加部门
+   * @apiGroup 系统部门
+   * @apiUse Auth
+   * @apiUse BaseRes
+   * @apiParam {Number} parentDepartmentId 父级部门编号,没有则为-1
+   * @apiParam {String} departmentName 部门名称
+   */
+  @AdminRoute('/sys/dept/add', 'post')
+  async add() {
+    const dto = await this.ctx.validate<CreateDeptDto>(CreateDeptDto);
+    await this.service.admin.sys.dept.add(dto.departmentName, dto.parentDepartmentId);
+    this.res();
+  }
+
+  /**
+   * @api {post} /admin/sys/dept/delete 删除部门
+   * @apiGroup 系统部门
+   * @apiUse Auth
+   * @apiUse BaseRes
+   * @apiParam {Number} departmentId 部门编号
+   */
+  @AdminRoute('/sys/dept/delete', 'post')
+  async delete() {
+    const dto = await this.ctx.validate<DeleteDeptDto>(DeleteDeptDto);
+    // 查询是否有关联用户,如果含有则无法删除
+    const count = await this.service.admin.sys.dept.countUserByDeptId(dto.departmentId);
+    if (count > 0) {
+      this.res({
+        code: 10009,
+      });
+      return;
+    }
+    // 查询是否有关联的角色,角色与部门存在数据相互绑定
+    const count2 = await this.service.admin.sys.dept.countRoleByDeptId(dto.departmentId);
+    if (count2) {
+      this.res({
+        code: 10010,
+      });
+      return;
+    }
+    await this.service.admin.sys.dept.delete(dto.departmentId);
+    this.res();
+  }
+
+  /**
+   * @api {get} /admin/sys/dept/info 获取部门信息
+   * @apiGroup 系统部门
+   * @apiUse Auth
+   * @apiUse BaseRes
+   * @apiParam {Number} departmentId 部门编号
+   * @apiSuccess {SysDepartment} data 部门信息实体
+   */
+  @AdminRoute('/sys/dept/info', 'get')
+  async info() {
+    const q = await this.ctx.validate<InfoDeptDto>(InfoDeptDto, this.getQuery());
+    this.res({
+      data: await this.service.admin.sys.dept.info(parseInt(q.departmentId)),
+    });
+  }
+
+  /**
+   * @api {post} /admin/sys/dept/update 获取部门信息
+   * @apiGroup 系统部门
+   * @apiUse Auth
+   * @apiUse BaseRes
+   * @apiParam {Number} id 部门编号
+   * @apiParam {Number} parentId 父级部门编号,没有则为-1
+   * @apiParam {String} name 部门名称
+   * @apiParam {Number} orderNum 排序
+   */
+  @AdminRoute('/sys/dept/update', 'post')
+  async update() {
+    const dto = await this.ctx.validate<UpdateDeptDto>(UpdateDeptDto);
+    await this.service.admin.sys.dept.update(dto);
+    this.res();
+  }
+
+  /**
+   * @api {post} /admin/sys/dept/transfer 人员部门转移
+   * @apiGroup 系统部门
+   * @apiUse Auth
+   * @apiUse BaseRes
+   * @apiParam {Number[]} userIds 管理员编号列表
+   * @apiParam {Number} departmentId 需要转移去的部门编号
+   */
+  @AdminRoute('/sys/dept/transfer', 'post')
+  async transfer() {
+    const dto = await this.ctx.validate<TransferDeptDto>(TransferDeptDto);
+    await this.service.admin.sys.dept.transfer(dto.userIds, dto.departmentId);
+    this.res();
+  }
+}

+ 33 - 0
app/controller/admin/sys/login_log.ts

@@ -0,0 +1,33 @@
+import { AdminRoute } from '../../../decorator/router_register';
+import BaseController from '../../base';
+import { PageGetDto } from '../../../dto/comm';
+
+/**
+ * 登录日志控制器
+ */
+export default class SysLoginLogController extends BaseController {
+
+  /**
+   * @api {get} /admin/sys/login-log/page 获取登录日志列表
+   * @apiGroup 登录日志
+   * @apiUse Auth
+   * @apiUse BaseRes
+   * @apiUse Page
+   * @apiSuccess {SysLoginLog[]} data.list 登录日志列表
+   */
+  @AdminRoute('/sys/login-log/page', 'get')
+  async page() {
+    const dto = await this.ctx.validate<PageGetDto>(PageGetDto, this.getQuery());
+    this.res({
+      data: {
+        list: await this.service.admin.sys.loginLog.page(dto.current - 1, dto.pageSize),
+        pagination: {
+          page: dto.current,
+          size: dto.pageSize,
+          total: await this.service.admin.sys.loginLog.count(),
+        },
+      },
+    });
+  }
+
+}

+ 167 - 0
app/controller/admin/sys/menu.ts

@@ -0,0 +1,167 @@
+import BaseController from '../../base';
+import { AdminRoute } from '../../../decorator/router_register';
+import * as _ from 'lodash';
+import { CreateMenuDto, InfoMenuDto, DeleteMenuDto, UpdateMenuDto } from '../../../dto/admin/sys/menu';
+
+export default class SysMenuController extends BaseController {
+
+  /**
+   * @api {get} /admin/sys/menu/list 获取所有菜单
+   * @apiGroup 系统菜单
+   * @apiUse Auth
+   * @apiUse BaseRes
+   */
+  @AdminRoute('/sys/menu/list', 'get')
+  async list() {
+    this.res(
+      {
+        data: await this.service.admin.sys.menu.getMenus(this.ctx.token.uid),
+      },
+    );
+  }
+
+  /**
+   * @api {post} /admin/sys/menu/add 增加菜单
+   * @apiGroup 系统菜单
+   * @apiUse Auth
+   * @apiUse BaseRes
+   * @apiParam {Number} type 菜单类别
+   * @apiParam {Number} parentId 父级菜单,无则为-1
+   * @apiParam {String} name 菜单名称
+   * @apiParam {Number} orderNum 排序
+   * @apiParam {String} router 路由地址
+   * @apiParam {Boolean} isShow 是否显示
+   * @apiParam {Boolean} keepalive 开启keepalive
+   * @apiParam {String} icon 对应svg图标文件名称
+   * @apiParam {String} perms 权限
+   * @apiParam {String} viewPath vue文件路径
+   */
+  @AdminRoute('/sys/menu/add', 'post')
+  async add() {
+    const dto = await this.ctx.validate<CreateMenuDto>(CreateMenuDto);
+    if (dto.type === 2 && dto.parentId === -1) {
+      // 无法直接创建权限,必须有ParentId
+      this.res({ code: 10005 });
+      return;
+    }
+    if (dto.type === 1 && dto.parentId !== -1) {
+      const parent = await this.service.admin.sys.menu.getMenuItemInfo(dto.parentId);
+      if (!parent) {
+        throw new Error('父节点菜单不存在!');
+      }
+      if (parent && parent.type === 1) {
+        // 当前新增为菜单但父节点也为菜单时为非法操作
+        this.res({
+          code: 10006,
+        });
+        return;
+      }
+    }
+    const insertData: any = { ...dto };
+    if (dto.parentId === -1) {
+      insertData.parentId = null;
+    }
+    const saveData = await this.service.admin.sys.menu.save(insertData);
+    if (dto.type === 3) {
+      // 如果是权限发生更改,则刷新所有在线用户的权限
+      await this.service.admin.sys.menu.refreshOnlineUserPerms();
+    }
+    this.res({
+      data: saveData,
+    });
+  }
+
+  /**
+   * @api {post} /admin/sys/menu/update 更新菜单
+   * @apiGroup 系统菜单
+   * @apiUse Auth
+   * @apiUse BaseRes
+   * @apiParam {Number} type 菜单类别
+   * @apiParam {Number} parentId 父级菜单,无则为-1
+   * @apiParam {String} name 菜单名称
+   * @apiParam {Number} orderNum 排序
+   * @apiParam {String} router 路由地址
+   * @apiParam {Boolean} isShow 是否显示
+   * @apiParam {Boolean} keepalive 开启keepalive
+   * @apiParam {String} icon 对应svg图标文件名称
+   * @apiParam {String} perms 权限
+   * @apiParam {String} viewPath vue文件路径
+   * @apiParam {Number} menuId 菜单编号
+   */
+  @AdminRoute('/sys/menu/update', 'post')
+  async update() {
+    const dto = await this.ctx.validate<UpdateMenuDto>(UpdateMenuDto);
+    // if (dto.type === 2 && dto.parentId === -1) {
+    //   // 无法直接创建权限,必须有ParentId
+    //   this.res({ code: 10005 });
+    //   return;
+    // }
+    const originMenu = await this.service.admin.sys.menu.getMenuItemInfo(dto.menuId);
+    if (originMenu && (originMenu.type !== dto.type)) {
+      // 节点类型发生变化则直接返回
+      this.res({
+        code: 10007,
+      });
+      return;
+    }
+    // if (dto.type === 1 && dto.parentId !== -1) {
+    //   const parent = await this.service.admin.sys.menu.getMenuItemInfo(dto.parentId);
+    //   if (!parent) {
+    //     throw new Error('父节点菜单不存在!');
+    //   }
+    //   if (parent && parent.type === 1) {
+    //     // 当前新增为菜单但父节点也为菜单时为非法操作
+    //     this.res({
+    //       code: 10006,
+    //     });
+    //     return;
+    //   }
+    // }
+    const updateData: any = { ...dto };
+    if (dto.parentId === -1) {
+      updateData.parentId = null;
+    }
+    // update id
+    updateData.id = dto.menuId;
+    const saveData = await this.service.admin.sys.menu.save(updateData);
+    // 如果是权限发生更改,则刷新所有在线用户权限
+    await this.service.admin.sys.menu.refreshOnlineUserPerms();
+    this.res({
+      data: saveData,
+    });
+  }
+
+  /**
+   * @api {post} /admin/sys/menu/delete 删除菜单
+   * @apiGroup 系统菜单
+   * @apiUse Auth
+   * @apiUse BaseRes
+   * @apiParam {Number} menuId 菜单编号
+   */
+  @AdminRoute('/sys/menu/delete', 'post')
+  async delete() {
+    const dto = await this.ctx.validate<DeleteMenuDto>(DeleteMenuDto);
+    // 如果有子目录,一并删除
+    const childMenus = await this.service.admin.sys.menu.findChildMenus(dto.menuId);
+    await this.service.admin.sys.menu.deleteMenuItem(_.flattenDeep([ dto.menuId, childMenus ]));
+    await this.service.admin.sys.menu.refreshOnlineUserPerms();
+    this.res({ data: dto.menuId });
+  }
+
+  /**
+   * @api {get} /admin/sys/menu/info 获取菜单信息
+   * @apiGroup 系统菜单
+   * @apiUse Auth
+   * @apiUse BaseRes
+   * @apiParam {String} menuId 菜单编号
+   */
+  @AdminRoute('/sys/menu/info', 'get')
+  async info() {
+    const dto = await this.ctx.validate<InfoMenuDto>(InfoMenuDto, this.getQuery());
+    const data = await this.service.admin.sys.menu.getMenuItemAndParentInfo(parseInt(dto.menuId));
+    this.res({
+      data,
+    });
+  }
+
+}

+ 58 - 0
app/controller/admin/sys/online.ts

@@ -0,0 +1,58 @@
+import { AdminRoute } from '../../../decorator/router_register';
+import { KickDto } from '../../../dto/admin/sys/online';
+import BaseController from '../../base';
+
+/**
+ * 在线用户控制器
+ */
+export default class SysOnlineController extends BaseController {
+
+  /**
+   * @api {get} /admin/sys/online/list 获取在线用户列表
+   * @apiGroup 在线用户
+   * @apiUse Auth
+   * @apiUse BaseRes
+   * @apiSuccess {Number} data.id 用户ID
+   * @apiSuccess {String} data.ip 用户登陆IP
+   * @apiSuccess {String} data.username 用户名
+   * @apiSuccess {Boolean} data.isCurrent 是否为当前用户
+   * @apiSuccess {String} data.time 登陆时间
+   * @apiSuccess {Number} data.status 状态
+   * @apiSuccess {String} data.os 登陆系统
+   * @apiSuccess {String} data.browser 登陆浏览器
+   * @apiSuccess {Boolean} data.disable 是否可用
+   */
+  @AdminRoute('/sys/online/list', 'get')
+  async page() {
+    this.res({
+      data: await this.service.admin.sys.online.list(),
+    });
+  }
+
+  /**
+   * @api {get} /admin/sys/online/list 下线当前用户
+   * @apiGroup 在线用户
+   * @apiUse Auth
+   * @apiUse BaseRes
+   * @apiParam {Number} id 当前用户ID
+   */
+  @AdminRoute('/sys/online/kick', 'post')
+  async kick() {
+    const dto = await this.ctx.validate<KickDto>(KickDto);
+    if (dto.id === this.ctx.token.uid) {
+      this.res({
+        code: 10012,
+      });
+      return;
+    }
+    if (dto.id === this.config.rootRoleId) {
+      this.res({
+        code: 10013,
+      });
+      return;
+    }
+    await this.service.admin.sys.user.forbidden(dto.id);
+    this.res();
+  }
+
+}

+ 49 - 0
app/controller/admin/sys/req_log.ts

@@ -0,0 +1,49 @@
+import { AdminRoute } from '../../../decorator/router_register';
+import { SearchLogDto } from '../../../dto/admin/sys/log';
+import BaseController from '../../base';
+import { PageGetDto } from '../../../dto/comm';
+
+/**
+ * 请求追踪控制器
+ */
+export default class SysReqLogController extends BaseController {
+
+  /**
+   * @api {get} /admin/sys/req-log/page 获取请求追踪列表
+   * @apiGroup 请求追踪
+   * @apiUse Auth
+   * @apiUse BaseRes
+   * @apiUse Page
+   * @apiSuccess {SysReqLog[]} data.list 请求追踪列表
+   */
+  @AdminRoute('/sys/req-log/page', 'get')
+  async page() {
+    const dto = await this.ctx.validate<PageGetDto>(PageGetDto, this.getQuery());
+    this.res({
+      data: {
+        list: await this.service.admin.sys.reqLog.page(dto.current - 1, dto.pageSize),
+        pagination: {
+          page: dto.current,
+          size: dto.pageSize,
+          total: await this.service.admin.sys.reqLog.count(),
+        },
+      },
+    });
+  }
+
+  /**
+   * @api {get} /admin/sys/req-log/search 请求追踪搜索
+   * @apiGroup 请求追踪
+   * @apiUse Auth
+   * @apiUse BaseRes
+   * @apiParam {String} q 条件
+   */
+  @AdminRoute('/sys/req-log/search', 'get')
+  async search() {
+    const dto = await this.ctx.validate<SearchLogDto>(SearchLogDto, this.getQuery());
+    this.res({
+      data: await this.service.admin.sys.reqLog.search(dto.current - 1, dto.pageSize, dto.q),
+    });
+  }
+
+}

+ 137 - 0
app/controller/admin/sys/role.ts

@@ -0,0 +1,137 @@
+import BaseController from '../../base';
+import { AdminRoute } from '../../../decorator/router_register';
+import { PageGetDto } from '../../../dto/comm';
+import { DeleteRoleDto, CreateRoleDto, UpdateRoleDto, InfoRoleDto } from '../../../dto/admin/sys/role';
+export default class SysRoleController extends BaseController {
+
+  /**
+   * @api {get} /admin/sys/role/list 获取全部系统角色列表
+   * @apiGroup 系统角色
+   * @apiUse Auth
+   * @apiUse BaseRes
+   * @apiSuccess {SysRole[]} data 角色列表
+   */
+  @AdminRoute('/sys/role/list', 'get')
+  async list() {
+    this.res({
+      data: await this.service.admin.sys.role.list(),
+    });
+  }
+
+  /**
+   * @api {get} /admin/sys/role/page 获取系统角色列表
+   * @apiGroup 系统角色
+   * @apiUse Auth
+   * @apiUse BaseRes
+   * @apiUse Page
+   * @apiSuccess {SysRole[]} data.list 角色列表
+   */
+  @AdminRoute('/sys/role/page', 'get')
+  async page() {
+    const dto = await this.ctx.validate<PageGetDto>(PageGetDto, this.getQuery());
+    this.res({
+      data: {
+        list: await this.service.admin.sys.role.page(dto.current - 1, dto.pageSize),
+        pagination: {
+          page: dto.current,
+          size: dto.pageSize,
+          total: await this.service.admin.sys.role.count(),
+        },
+      },
+    });
+  }
+
+  /**
+   * @api {post} /admin/sys/role/delete 删除角色
+   * @apiGroup 系统角色
+   * @apiUse Auth
+   * @apiUse BaseRes
+   * @apiParam {Number[]} roleIds 角色编号列表
+   */
+  @AdminRoute('/sys/role/delete', 'post')
+  async delete() {
+    const dto = await this.ctx.validate<DeleteRoleDto>(DeleteRoleDto);
+    const count = await this.service.admin.sys.role.countUserIdByRole(dto.roleIds);
+    if (count > 0) {
+      this.res({
+        code: 10008,
+      });
+      return;
+    }
+    await this.service.admin.sys.role.delete(dto.roleIds);
+    await this.service.admin.sys.menu.refreshOnlineUserPerms();
+    this.res();
+  }
+
+  /**
+   * @api {post} /admin/sys/role/add 新增角色
+   * @apiGroup 系统角色
+   * @apiUse Auth
+   * @apiUse BaseRes
+   * @apiParam {String} name 角色名称
+   * @apiParam {String} label 角色表示
+   * @apiParam {String} remark 备注
+   * @apiParam {Number[]} menus 关联菜单ID列表
+   * @apiParam {Number[]} depts 关联部门ID列表
+   */
+  @AdminRoute('/sys/role/add', 'post')
+  async add() {
+    const dto = await this.ctx.validate<CreateRoleDto>(CreateRoleDto);
+    this.res({
+      data: await this.service.admin.sys.role.add(dto, this.ctx.token.uid),
+    });
+  }
+
+  /**
+   * @api {post} /admin/sys/role/update 更新角色
+   * @apiGroup 系统角色
+   * @apiUse Auth
+   * @apiUse BaseRes
+   * @apiParam {String} name 角色名称
+   * @apiParam {String} label 角色表示
+   * @apiParam {String} remark 备注
+   * @apiParam {Number[]} menus 关联菜单ID列表
+   * @apiParam {Number[]} depts 关联部门ID列表
+   * @apiParam {Number} roleId 角色编号
+   */
+  @AdminRoute('/sys/role/update', 'post')
+  async update() {
+    const dto = await this.ctx.validate<UpdateRoleDto>(UpdateRoleDto);
+    await this.service.admin.sys.role.update(dto);
+    await this.service.admin.sys.menu.refreshOnlineUserPerms();
+    this.res();
+  }
+
+  /**
+   * @api {get} /admin/sys/role/list 获取全部系统角色列表
+   * @apiGroup 系统角色
+   * @apiUse Auth
+   * @apiUse BaseRes
+   * @apiSuccess {SysRole} data 角色信息
+   */
+  @AdminRoute('/sys/role/info', 'get')
+  async info() {
+    const dto = await this.ctx.validate<InfoRoleDto>(InfoRoleDto, this.getQuery());
+    this.res({
+      data: await this.service.admin.sys.role.info(parseInt(dto.roleId)),
+    });
+  }
+  // 新增 获取所有role按钮
+  @AdminRoute('/role_menu', 'get')
+  async role_menu() {
+    this.res({
+      data: await this.service.admin.sys.role.role_menu(),
+    });
+  }
+
+  /**
+   *通过角色id获取menu
+   */
+  @AdminRoute('/menu_by_roleid', 'get')
+  async menu_by_roleid() {
+    const dto = await this.ctx.validate<InfoRoleDto>(InfoRoleDto, this.getQuery());
+    this.res({
+      data: await this.service.admin.sys.role.menu_by_roleid(parseInt(dto.roleId)),
+    });
+  }
+}

+ 159 - 0
app/controller/admin/sys/task.ts

@@ -0,0 +1,159 @@
+import { AdminRoute } from '../../../decorator/router_register';
+import BaseController from '../../base';
+import { PageGetDto } from '../../../dto/comm';
+import { CreateTaskDto, UpdateTaskDto, CheckIdTaskDto } from '../../../dto/admin/sys/task';
+
+/**
+ * 请求追踪控制器
+ */
+export default class SysTaskController extends BaseController {
+
+  /**
+   * @api {get} /admin/sys/task/page 获取任务列表
+   * @apiGroup 任务调度
+   * @apiUse Auth
+   * @apiUse BaseRes
+   * @apiUse Page
+   * @apiSuccess {SysTask[]} data.list 任务列表
+   */
+  @AdminRoute('/sys/task/page', 'get')
+  async page() {
+    const dto = await this.ctx.validate<PageGetDto>(PageGetDto, this.getQuery());
+    this.res({
+      data: {
+        list: await this.service.admin.sys.task.page(dto.current - 1, dto.pageSize),
+        pagination: {
+          page: dto.current,
+          size: dto.pageSize,
+          total: await this.service.admin.sys.task.count(),
+        },
+      },
+    });
+  }
+
+  /**
+   * @api {post} /admin/sys/task/add 新增任务
+   * @apiGroup 任务调度
+   * @apiUse Auth
+   * @apiUse BaseRes
+   * @apiParam {String} name 任务名称
+   * @apiParam {String} service 调用服务路径
+   * @apiParam {Number} type 任务类型
+   * @apiParam {Number} status 任务状态
+   * @apiParam {String} startTime 启动时间
+   * @apiParam {String} endTime 启动时间
+   * @apiParam {Number} limit 最大运行次数,小于等于0则不限次数
+   * @apiParam {String} cron cron表达式
+   * @apiParam {Number} every 间隔时间
+   * @apiParam {String} data 运行参数,JSON格式的字符串
+   * @apiParam {String} remark 任务备注
+   */
+  @AdminRoute('/sys/task/add', 'post')
+  async add() {
+    const dto = await this.ctx.validate<CreateTaskDto>(CreateTaskDto);
+    await this.service.admin.sys.task.addOrUpdate(dto);
+    this.res();
+  }
+
+  /**
+   * @api {post} /admin/sys/task/update 更新任务
+   * @apiGroup 任务调度
+   * @apiUse Auth
+   * @apiUse BaseRes
+   * @apiParam {String} name 任务名称
+   * @apiParam {String} service 调用服务路径
+   * @apiParam {Number} type 任务类型
+   * @apiParam {Number} status 任务状态
+   * @apiParam {String} startTime 启动时间
+   * @apiParam {String} endTime 启动时间
+   * @apiParam {Number} limit 最大运行次数,小于等于0则不限次数
+   * @apiParam {String} cron cron表达式
+   * @apiParam {Number} every 间隔时间
+   * @apiParam {String} data 运行参数,JSON格式的字符串
+   * @apiParam {String} remark 任务备注
+   * @apiParam {Number} id 任务编号
+   */
+  @AdminRoute('/sys/task/update', 'post')
+  async update() {
+    const dto = await this.ctx.validate<UpdateTaskDto>(UpdateTaskDto);
+    await this.service.admin.sys.task.addOrUpdate(dto);
+    this.res();
+  }
+
+  /**
+   * @api {get} /admin/sys/task/info 获取任务信息
+   * @apiGroup 任务调度
+   * @apiUse Auth
+   * @apiUse BaseRes
+   * @apiParam {Number} id 任务编号
+   * @apiSuccess {SysTask} data 任务列表
+   */
+  @AdminRoute('/sys/task/info', 'post')
+  async info() {
+    const dto = await this.ctx.validate<CheckIdTaskDto>(CheckIdTaskDto);
+    this.res({
+      data: await this.service.admin.sys.task.info(dto.id),
+    });
+  }
+
+  /**
+   * @api {post} /admin/sys/task/once 手动执行一次任务
+   * @apiGroup 任务调度
+   * @apiUse Auth
+   * @apiUse BaseRes
+   * @apiParam {Number} id 任务编号
+   */
+  @AdminRoute('/sys/task/once', 'post')
+  async once() {
+    const dto = await this.ctx.validate<CheckIdTaskDto>(CheckIdTaskDto);
+    const task = await this.service.admin.sys.task.info(dto.id);
+    await this.service.admin.sys.task.once(task!);
+    this.res();
+  }
+
+  /**
+   * @api {post} /admin/sys/task/stop 停止任务
+   * @apiGroup 任务调度
+   * @apiUse Auth
+   * @apiUse BaseRes
+   * @apiParam {Number} id 任务编号
+   */
+  @AdminRoute('/sys/task/stop', 'post')
+  async stop() {
+    const dto = await this.ctx.validate<CheckIdTaskDto>(CheckIdTaskDto);
+    const task = await this.service.admin.sys.task.info(dto.id);
+    await this.service.admin.sys.task.stop(task!);
+    this.res();
+  }
+
+  /**
+   * @api {post} /admin/sys/task/start 启动任务
+   * @apiGroup 任务调度
+   * @apiUse Auth
+   * @apiUse BaseRes
+   * @apiParam {Number} id 任务编号
+   */
+  @AdminRoute('/sys/task/start', 'post')
+  async start() {
+    const dto = await this.ctx.validate<CheckIdTaskDto>(CheckIdTaskDto);
+    const task = await this.service.admin.sys.task.info(dto.id);
+    await this.service.admin.sys.task.start(task!);
+    this.res();
+  }
+
+  /**
+   * @api {post} /admin/sys/task/delete 删除任务
+   * @apiGroup 任务调度
+   * @apiUse Auth
+   * @apiUse BaseRes
+   * @apiParam {Number} id 任务编号
+   */
+  @AdminRoute('/sys/task/delete', 'post')
+  async delete() {
+    const dto = await this.ctx.validate<CheckIdTaskDto>(CheckIdTaskDto);
+    const task = await this.service.admin.sys.task.info(dto.id);
+    await this.service.admin.sys.task.delete(task!);
+    this.res();
+  }
+
+}

+ 33 - 0
app/controller/admin/sys/task_log.ts

@@ -0,0 +1,33 @@
+import { AdminRoute } from '../../../decorator/router_register';
+import BaseController from '../../base';
+import { PageGetDto } from '../../../dto/comm';
+
+/**
+ * 任务日志控制器
+ */
+export default class SysTaskLogController extends BaseController {
+
+  /**
+   * @api {get} /admin/sys/task-log/page 获取任务日志列表
+   * @apiGroup 任务调度
+   * @apiUse Auth
+   * @apiUse BaseRes
+   * @apiUse Page
+   * @apiSuccess {SysTaskLog[]} data.list 任务日志列表
+   */
+  @AdminRoute('/sys/task-log/page', 'get')
+  async page() {
+    const dto = await this.ctx.validate<PageGetDto>(PageGetDto, this.getQuery());
+    this.res({
+      data: {
+        list: await this.service.admin.sys.taskLog.page(dto.current - 1, dto.pageSize),
+        pagination: {
+          page: dto.current,
+          size: dto.pageSize,
+          total: await this.service.admin.sys.taskLog.count(),
+        },
+      },
+    });
+  }
+
+}

+ 120 - 0
app/controller/admin/sys/user.ts

@@ -0,0 +1,120 @@
+import BaseController from '../../base';
+import { AdminRoute } from '../../../decorator/router_register';
+import { CreateUserDto, InfoUserDto, DeleteUserDto, QueryUserDto, UpdateUserDto } from '../../../dto/admin/sys/user';
+
+export default class SysUserController extends BaseController {
+
+  /**
+   * @api {post} /admin/sys/user/add 新增系统用户
+   * @apiGroup 系统用户
+   * @apiUse Auth
+   * @apiUse BaseRes
+   * @apiUse Page
+   * @apiParam {Number} departmentId 部门编号
+   * @apiParam {String} name 管理员名称
+   * @apiParam {String} username 用户名
+   * @apiParam {String} nickName 别名
+   * @apiParam {Number[]} roles 关联角色编号列表,最多三个
+   * @apiParam {String} remark 用户备注
+   * @apiParam {String} email 邮箱
+   * @apiParam {String} phone 手机
+   * @apiParam {Number} status 状态
+   */
+  @AdminRoute('/sys/user/add', 'post')
+  async add() {
+    const dto = await this.ctx.validate<CreateUserDto>(CreateUserDto);
+    const result = await this.service.admin.sys.user.add(dto);
+    if (result) {
+      this.res();
+    } else {
+      this.res({ code: 10001 });
+    }
+  }
+
+  /**
+   * @api {post} /admin/sys/user/info 获取系统用户信息
+   * @apiGroup 系统用户
+   * @apiUse Auth
+   * @apiUse BaseRes
+   * @apiUse Page
+   * @apiParam {Number} id 用户编号
+   * @apiSuccess {SysUser} data 用户列表
+   */
+  @AdminRoute('/sys/user/info', 'get')
+  async info() {
+    const dto = await this.ctx.validate<InfoUserDto>(InfoUserDto, this.getQuery());
+    const user = await this.service.admin.sys.user.info(parseInt(dto.userId));
+    this.res({
+      data: user,
+    });
+  }
+
+  /**
+   * @api {post} /admin/sys/user/delete 删除用户列表
+   * @apiGroup 系统用户
+   * @apiUse Auth
+   * @apiUse BaseRes
+   * @apiUse Page
+   * @apiParam {Number[]} userIds 用户编号列表
+   * @apiSuccess {SysUser} data 用户列表
+   */
+  @AdminRoute('/sys/user/delete', 'post')
+  async delete() {
+    const dto = await this.ctx.validate<DeleteUserDto>(DeleteUserDto);
+    await this.service.admin.sys.user.delete(dto.userIds);
+    await this.service.admin.sys.user.multiForbidden(dto.userIds);
+    this.res();
+  }
+
+  /**
+   * @api {get} /admin/sys/user/page 获取系统用户列表
+   * @apiGroup 系统用户
+   * @apiUse Auth
+   * @apiUse BaseRes
+   * @apiUse Page
+   * @apiParam {Number} departmentId 部门编号
+   * @apiSuccess {SysUser[]} data.list 用户列表
+   */
+  @AdminRoute('/sys/user/page', 'get')
+  async page() {
+    // console.log(this.getQuery(), this.getParams(), this.getBody());
+    const dto = await this.ctx.validate<QueryUserDto>(QueryUserDto, this.getQuery());
+    // dto.departmentId=-1;
+    this.res({
+      data: {
+        list: await this.service.admin.sys.user.page(this.ctx.token.uid, -1, dto.current - 1, dto.pageSize),
+        pagination: {
+          total: await this.service.admin.sys.user.count(this.ctx.token.uid, -1),
+          page: dto.current,
+          size: dto.pageSize,
+        },
+      },
+    });
+  }
+
+  /**
+   * @api {post} /admin/sys/user/update 更新系统用户信息
+   * @apiGroup 系统用户
+   * @apiUse Auth
+   * @apiUse BaseRes
+   * @apiUse Page
+   * @apiParam {Number} departmentId 部门编号
+   * @apiParam {String} name 管理员名称
+   * @apiParam {String} username 用户名
+   * @apiParam {String} nickName 别名
+   * @apiParam {Number[]} roles 关联角色编号列表,最多三个
+   * @apiParam {String} remark 用户备注
+   * @apiParam {String} email 邮箱
+   * @apiParam {String} phone 手机
+   * @apiParam {Number} status 状态
+   * @apiParam {Number} id 用户编号
+   */
+  @AdminRoute('/sys/user/update', 'post')
+  async update() {
+    const dto = await this.ctx.validate<UpdateUserDto>(UpdateUserDto);
+    await this.service.admin.sys.user.update(dto);
+    await this.service.admin.sys.menu.refreshPerms(dto.id);
+    this.res();
+  }
+
+}

+ 61 - 0
app/controller/admin/tg/tgbot.ts

@@ -0,0 +1,61 @@
+import BaseController from '../../base';
+import { AdminRoute } from '../../../decorator/router_register';
+import { PageGetDto } from '../../../dto/comm';
+import { CreateBotDto, DeleteBotDto, UpdateBotDto } from '../../../dto/admin/tg/bot';
+
+export default class TgbotController extends BaseController {
+  @AdminRoute('/tg/tgboot/test', 'get')
+  async test() {
+    this.res({
+      data: await this.service.admin.tg.tgbot.tgTest(),
+    });
+  }
+
+  /**
+   * list
+   */
+  @AdminRoute('/tg/bot/list', 'get')
+  async list() {
+    this.res({
+      data: await this.service.admin.tg.tgbot.list(),
+    });
+  }
+
+  /**
+   * page
+   */
+  @AdminRoute('/tg/bot/page', 'get')
+  async page() {
+    const dto = await this.ctx.validate<PageGetDto>(PageGetDto, this.getQuery());
+    this.res({
+      data: {
+        list: await this.service.admin.tg.tgbot.page(dto.current - 1, dto.pageSize),
+        pagination: {
+          page: dto.current,
+          size: dto.pageSize,
+          total: await this.service.admin.tg.tgbot.count(),
+        },
+      },
+    });
+  }
+  @AdminRoute('/tg/bot/add', 'post')
+  async add() {
+    const dto = await this.ctx.validate<CreateBotDto>(CreateBotDto);
+    this.res({
+      data: await this.service.admin.tg.tgbot.add(dto),
+    });
+  }
+  @AdminRoute('/tg/bot/update', 'post')
+  async update() {
+    const dto = await this.ctx.validate<UpdateBotDto>(UpdateBotDto);
+    await this.service.admin.tg.tgbot.update(dto);
+    this.res({});
+  }
+  @AdminRoute('/tg/bot/delete', 'post')
+  async delete() {
+    const dto = await this.ctx.validate<DeleteBotDto>(DeleteBotDto);
+    this.res({
+      data: await this.service.admin.tg.tgbot.delete(dto.id),
+    });
+  }
+}

+ 122 - 0
app/controller/admin/tg/tggroup.ts

@@ -0,0 +1,122 @@
+import BaseController from '../../base';
+import { AdminRoute } from '../../../decorator/router_register';
+import { PageGetDto } from '../../../dto/comm';
+import {
+  CreateGroupDto,
+  DeleteGroupDto,
+  SendGroupsMsgDto,
+  SendMsgDto,
+  UpdateGroupDto,
+} from '../../../dto/admin/tg/group';
+
+export class TggroupController extends BaseController {
+  /**
+   * list
+   */
+  @AdminRoute('/tg/group/list', 'get')
+  async list() {
+    this.res({
+      data: await this.service.admin.tg.tggroup.list(),
+    });
+  }
+  /**
+   * page
+   */
+  @AdminRoute('/tg/group/page', 'get')
+  async page() {
+    const dto = await this.ctx.validate<PageGetDto>(PageGetDto, this.getQuery());
+    this.res({
+      data: {
+        list: await this.service.admin.tg.tggroup.page(dto.current - 1, dto.pageSize),
+        pagination: {
+          page: dto.current,
+          size: dto.pageSize,
+          total: await this.service.admin.tg.tggroup.count(),
+        },
+      },
+    });
+  }
+  /**
+   * add
+   */
+  @AdminRoute('/tg/group/add', 'post')
+  async add() {
+    const dto = await this.ctx.validate<CreateGroupDto>(CreateGroupDto);
+    this.res({
+      data: await this.service.admin.tg.tggroup.add(dto),
+    });
+  }
+  /**
+   * delete
+   */
+  @AdminRoute('/tg/group/delete', 'post')
+  async delete() {
+    const dto = await this.ctx.validate<DeleteGroupDto>(DeleteGroupDto);
+    this.res({
+      data: await this.service.admin.tg.tggroup.delete(dto.ids),
+    });
+  }
+  /**
+   * delete
+   */
+  @AdminRoute('/tg/group/update', 'post')
+  async update() {
+    const dto = await this.ctx.validate<UpdateGroupDto>(UpdateGroupDto);
+    await this.service.admin.tg.tggroup.update(dto);
+    this.res({});
+  }
+  @AdminRoute('/tg/group/send_groups_msg', 'post')
+  async send_groups_msg() {
+    const dto = await this.ctx.validate<SendGroupsMsgDto>(SendGroupsMsgDto);
+    this.res({
+      data: await this.service.admin.tg.tggroup.send_groups_msg(dto.groupIds,dto.message),
+    });
+  }
+  @AdminRoute('/tg/group/send_msg', 'post')
+  async send_msg() {
+    const dto = await this.ctx.validate<SendMsgDto>(SendMsgDto);
+    this.res({
+      data: await this.service.admin.tg.tggroup.send_msg(dto.groupId, dto.botId, dto.message),
+    });
+  }
+  @AdminRoute('/tg/group/msg_groupIds', 'get')
+  async msg_groupIds() {
+    this.res({
+      data: await this.service.admin.tg.tggroup.msg_groupIds(),
+    });
+  }
+  @AdminRoute('/tg/group/all_botlist', 'get')
+  async all_botlist() {
+    this.res({
+      data: await this.service.admin.tg.tggroup.all_botlist(),
+    });
+  }
+  @AdminRoute('/tg/group/receive_page', 'get')
+  async receive_page() {
+    const dto = await this.ctx.validate<PageGetDto>(PageGetDto, this.getQuery());
+    this.res({
+      data: {
+        list: await this.service.admin.tg.tglog.receive_page(dto.current - 1, dto.pageSize),
+        pagination: {
+          page: dto.current,
+          size: dto.pageSize,
+          total: await this.service.admin.tg.tglog.receive_count(),
+        },
+      },
+    });
+  }
+  @AdminRoute('/tg/group/send_page', 'get')
+  async send_page() {
+    const dto = await this.ctx.validate<PageGetDto>(PageGetDto, this.getQuery());
+    this.res({
+      data: {
+        list: await this.service.admin.tg.tglog.send_page(dto.current - 1, dto.pageSize),
+        pagination: {
+          page: dto.current,
+          size: dto.pageSize,
+          total: await this.service.admin.tg.tglog.send_count(),
+        },
+      },
+    });
+  }
+}

+ 74 - 0
app/controller/base.ts

@@ -0,0 +1,74 @@
+import { Controller } from 'egg';
+import { IHelper } from '../../typings/app';
+
+export interface ResOp {
+  data?: any;
+  code?: number;
+  message?: string;
+}
+
+/**
+ * @apiDefine BaseRes
+ * @apiSuccess {Number} code 错误码,成功则返回200
+ * @apiSuccess {String} message 错误信息,成功则返回success
+ * @apiSuccess {Object} data 返回的数据
+ */
+
+/**
+ * @apiDefine Auth
+ * @apiHeader {String} Authorization 管理员登陆Token
+ */
+
+/**
+* @apiDefine Page
+* @apiSuccess {Array} data.list 查询数据列表
+* @apiSuccess {Object} data.pagination 分页信息
+* @apiSuccess {Number} data.pagination.page 当前页数
+* @apiSuccess {Number} data.pagination.size 限制个数
+* @apiSuccess {Number} data.pagination.total 总数量
+*/
+
+export default abstract class BaseController extends Controller {
+
+  /**
+   * 获取Query
+   */
+  protected getQuery(): any {
+    return this.ctx.request.query;
+  }
+
+  /**
+   * 获取Body
+   */
+  protected getBody(): any {
+    return this.ctx.request.body;
+  }
+
+  /**
+   * 获取Router上的参数
+   */
+  protected getParams(): any {
+    return this.ctx.params;
+  }
+
+  /**
+   * 获取Helper
+   */
+  protected getHelper(): IHelper {
+    return this.ctx.helper;
+  }
+
+  /**
+   * 返回数据
+   * @param op 返回配置,返回失败需要单独配置
+  */
+  protected res(op?: ResOp): void {
+    this.ctx.set('Content-Type', 'application/json');
+    this.ctx.body = {
+      data: op?.data ?? null,
+      code: op?.code ?? 200,
+      message: op?.code ? this.ctx.helper.getErrorMessageByCode(`${op!.code}`) || op?.message || 'unknown error' : op?.message || 'success',
+    };
+  }
+
+}

+ 116 - 0
app/decorator/router_register.ts

@@ -0,0 +1,116 @@
+import { Application, Context } from 'egg';
+
+/**
+ * 记录各个routerUrl的路由配置,使用initRouter统一设置
+ */
+const __router__: any = {};
+
+/**
+ * egg suport http method
+ */
+export type HttpMethod = 'get' | 'post' | 'patch' | 'delete' | 'options' | 'put' | 'all';
+
+/**
+ * RouterOption
+ */
+interface RouterOption {
+  httpMethod: HttpMethod;
+  handleName: string;
+  beforeMiddlewares: any[];
+  constructorFn: any;
+  className: string;
+  validator?: Validator | undefined | null;
+  url: string;
+}
+
+interface Validator {
+  params?: any;
+  body?: any;
+}
+
+/**
+ * 推入路由配置
+ */
+function _setRouter(url: string, option: RouterOption) {
+  __router__[url] = __router__[url] || [];
+  __router__[url].push(option);
+}
+
+/**
+ * 注册路由,路由信息是通过装饰器收集的
+ * router.head - HEAD
+ * router.options - OPTIONS
+ * router.get - GET
+ * router.put - PUT
+ * router.post - POST
+ * router.patch - PATCH
+ * router.delete - DELETE
+ * router.del - 由于 delete 是一个保留字,所以提供了一个 delete 方法的别名。
+ * router.redirect - 可以对 URL 进行重定向处理,比如我们最经常使用的可以把用户访问的根目录路由到某个主页。
+ * @param app Application
+ */
+export function initRouter(app: Application) {
+  // eslint-disable-next-line @typescript-eslint/no-unused-vars
+  const { router } = app;
+  Object.keys(__router__).forEach(url => {
+    __router__[url].forEach((opt: RouterOption) => {
+      router[opt.httpMethod](opt.url, ...opt.beforeMiddlewares, async (ctx: Context) => {
+        const ist = new opt.constructorFn(ctx);
+        if (opt.validator) {
+          // 如果存在validator,则先进行参数校验
+          if (opt.httpMethod === 'get') {
+            const query = opt.validator.params ? await ctx.validate(opt.validator.params, ctx.request.query) : null;
+            await ist[opt.handleName].call(ist, { query });
+          } else {
+            const query = opt.validator.params ? await ctx.validate(opt.validator.params, ctx.request.query) : null;
+            const body = opt.validator.body ? await ctx.validate(opt.validator.body, ctx.request.body) : null;
+            await ist[opt.handleName].call(ist, { body, query });
+          }
+        } else {
+          await ist[opt.handleName].call(ist);
+        }
+        // await ist[opt.handleName].call(ist);
+      });
+    });
+  });
+}
+
+/**
+ * 收集路由信息,使用@Route装饰器
+ */
+export function Route(url: string, method: HttpMethod, validator?: Validator, beforeMiddlewares: any[] = []) {
+  // eslint-disable-next-line @typescript-eslint/no-unused-vars
+  return function(target: any, funcName: string, _descriptor: PropertyDescriptor) {
+    _setRouter(url, {
+      httpMethod: method,
+      beforeMiddlewares,
+      handleName: funcName,
+      constructorFn: target.constructor,
+      className: target.constructor.name,
+      validator,
+      url,
+    });
+  };
+}
+
+
+const PREFIX_ADMIN = '/admin';
+/**
+ * 自动添加/admin前缀的Url路由装饰器
+ * 例如 url 为 /sys/user/add, 使用该装饰器可直接变为/admin/sys/user/add
+ */
+export function AdminRoute(url: string, method: HttpMethod, validator?: Validator, beforeMiddlewares: any[] = []) {
+  // eslint-disable-next-line @typescript-eslint/no-unused-vars
+  return function(target: any, funcName: string, _descriptor: PropertyDescriptor) {
+    _setRouter(url, {
+      httpMethod: method,
+      beforeMiddlewares,
+      handleName: funcName,
+      constructorFn: target.constructor,
+      className: target.constructor.name,
+      validator,
+      url: `${PREFIX_ADMIN}${url}`,
+    });
+  };
+}
+

+ 40 - 0
app/dto/admin/space/image.ts

@@ -0,0 +1,40 @@
+import {
+  IsNumberString,
+  IsInt,
+  ArrayNotEmpty,
+  ArrayMinSize,
+  Length,
+} from 'class-validator';
+import { Expose } from 'class-transformer';
+import { PageGetDto } from '../../comm';
+
+export class DeleteTypeDto {
+  @IsInt()
+  @Expose()
+  typeId: number;
+}
+
+export class CreateTypeDto {
+  @Length(2)
+  @Expose()
+  name: string;
+}
+
+export class QueryImageDto extends PageGetDto {
+  @IsNumberString()
+  @Expose()
+  typeId: string;
+}
+
+export class DeleteImageDto {
+  @ArrayNotEmpty()
+  @ArrayMinSize(1)
+  @Expose()
+  imageIds: number[];
+}
+
+export class UploadImageDto {
+  @IsNumberString()
+  @Expose()
+  typeId: string;
+}

+ 68 - 0
app/dto/admin/sys/dept.ts

@@ -0,0 +1,68 @@
+import {
+  IsInt,
+  IsNumberString,
+  ArrayNotEmpty,
+  IsString,
+  Min,
+  IsOptional,
+} from 'class-validator';
+import { Expose } from 'class-transformer';
+
+export class CreateDeptDto {
+
+  @IsString()
+  @Expose()
+  departmentName: string;
+
+  @IsInt()
+  @Expose()
+  parentDepartmentId: number;
+
+  @IsOptional()
+  @IsInt()
+  @Min(0)
+  @Expose()
+  orderNum: number;
+}
+
+export class UpdateDeptDto {
+  @IsInt()
+  @Expose()
+  id: number;
+
+  @IsString()
+  @Expose()
+  name: string;
+
+  @IsInt()
+  @Expose()
+  parentId: number;
+
+  @IsOptional()
+  @IsInt()
+  @Min(0)
+  @Expose()
+  orderNum: number;
+}
+
+export class DeleteDeptDto {
+  @IsInt()
+  @Expose()
+  departmentId: number;
+}
+
+export class InfoDeptDto {
+  @IsNumberString()
+  @Expose()
+  departmentId: string;
+}
+
+export class TransferDeptDto {
+  @ArrayNotEmpty()
+  @Expose()
+  userIds: number[];
+
+  @IsInt()
+  @Expose()
+  departmentId: number;
+}

+ 11 - 0
app/dto/admin/sys/log.ts

@@ -0,0 +1,11 @@
+import {
+  IsString,
+} from 'class-validator';
+import { Expose } from 'class-transformer';
+import { PageGetDto } from '../../comm';
+
+export class SearchLogDto extends PageGetDto {
+  @IsString()
+  @Expose()
+  q: string;
+}

+ 93 - 0
app/dto/admin/sys/menu.ts

@@ -0,0 +1,93 @@
+import {
+  IsInt,
+  Min,
+  Max,
+  MinLength,
+  IsString,
+  IsBoolean,
+  Allow,
+  IsNumberString, IsOptional,
+} from 'class-validator';
+import { Expose } from 'class-transformer';
+
+/**
+ * 增加菜单
+ */
+export class CreateMenuDto {
+
+  @IsInt()
+  @Min(0)
+  @Max(5)
+  @Expose()
+  type: number;
+
+  @IsOptional()
+  @IsInt()
+  @Expose()
+  parentId: number;
+
+  @MinLength(2)
+  @Expose()
+  name: string;
+
+  @IsInt()
+  @Min(0)
+  @Expose()
+  orderNum: number;
+
+  @IsOptional()
+  @IsString()
+  @Expose()
+  router: string;
+
+  @IsBoolean()
+  @Expose()
+  isShow: boolean;
+
+  @IsOptional()
+  @IsBoolean()
+  @Expose()
+  keepalive: boolean;
+
+  @IsOptional()
+  @IsString()
+  @Expose()
+  icon: string;
+
+  @IsOptional()
+  @IsString()
+  @Expose()
+  perms: string;
+
+  @Allow()
+  @Expose()
+  viewPath: string;
+
+  @IsInt()
+  @Expose()
+  status: string;
+}
+
+export class UpdateMenuDto extends CreateMenuDto {
+  @IsInt()
+  @Expose()
+  menuId: number;
+}
+
+/**
+ * 删除菜单
+ */
+export class DeleteMenuDto {
+  @IsInt()
+  @Expose()
+  menuId: number;
+}
+
+/**
+ * 查询菜单
+ */
+export class InfoMenuDto {
+  @IsNumberString()
+  @Expose()
+  menuId: string;
+}

+ 8 - 0
app/dto/admin/sys/online.ts

@@ -0,0 +1,8 @@
+import { IsInt } from 'class-validator';
+import { Expose } from 'class-transformer';
+
+export class KickDto {
+  @IsInt()
+  @Expose()
+  id: number;
+}

+ 53 - 0
app/dto/admin/sys/role.ts

@@ -0,0 +1,53 @@
+import {
+  ArrayNotEmpty,
+  ArrayMinSize,
+  IsOptional,
+  IsNumberString,
+  Length,
+  Matches,
+  IsInt,
+  Allow,
+} from 'class-validator';
+import { Expose } from 'class-transformer';
+
+export class DeleteRoleDto {
+  @ArrayNotEmpty()
+  @Expose()
+  roleIds: number[];
+}
+
+export class CreateRoleDto {
+  @Length(2)
+  @Expose()
+  name: string;
+
+  @Matches(/^[a-z0-9A-Z]+$/)
+  @Expose()
+  label: string;
+
+  @Allow()
+  @Expose()
+  remark: string;
+
+  @IsOptional()
+  @ArrayMinSize(0)
+  @Expose()
+  menus: number[];
+
+  @IsOptional()
+  @ArrayMinSize(0)
+  @Expose()
+  depts: number[];
+}
+
+export class UpdateRoleDto extends CreateRoleDto {
+  @IsInt()
+  @Expose()
+  roleId: number;
+}
+
+export class InfoRoleDto {
+  @IsNumberString()
+  @Expose()
+  roleId: string;
+}

+ 121 - 0
app/dto/admin/sys/task.ts

@@ -0,0 +1,121 @@
+import {
+  Length,
+  IsString,
+  IsIn,
+  IsDate,
+  IsInt,
+  Validate,
+  IsOptional,
+  ValidateIf,
+  ValidatorConstraint,
+  ValidatorConstraintInterface,
+  ValidationArguments,
+} from 'class-validator';
+import { Expose, Transform } from 'class-transformer';
+import * as parser from 'cron-parser';
+
+// cron 表达式验证,bull lib下引用了cron-parser
+@ValidatorConstraint({ name: 'isCronExpression', async: false })
+export class IsCronExpression implements ValidatorConstraintInterface {
+  // eslint-disable-next-line @typescript-eslint/no-unused-vars
+  validate(text: string, args: ValidationArguments) {
+    try {
+      const op: any = { iterator: true };
+      let needOp = false;
+      if ((args.object as any).startTime) {
+        needOp = true;
+        op.startDate = (args.object as any).startTime;
+      }
+      if ((args.object as any).endTime) {
+        needOp = true;
+        op.endDate = (args.object as any).endTime;
+      }
+      if (needOp) {
+        const tmp = parser.parseExpression(text, op);
+        if (!tmp.hasNext()) {
+          throw new Error();
+        }
+      } else {
+        parser.parseExpression(text);
+      }
+      return true;
+    } catch (e) {
+      return false;
+    }
+  }
+
+  // eslint-disable-next-line @typescript-eslint/no-unused-vars
+  defaultMessage(_args: ValidationArguments) {
+    // here you can provide default error message if validation failed
+    return 'this cron expression ($value) invalid';
+  }
+}
+
+export class CreateTaskDto {
+
+  @Length(2, 50)
+  @Expose()
+  name: string;
+
+  @IsString()
+  @Expose()
+  service: string;
+
+  @IsIn([ 0, 1 ])
+  @Expose()
+  type: number;
+
+  @IsIn([ 0, 1, 2 ])
+  @Expose()
+  status: number;
+
+  @ValidateIf((_o, v) => { return !(v === '' || v === undefined || v === null); })
+  @IsDate()
+  @Expose()
+  @Transform(value => { if (value) { return new Date(value as any); } return null; }, { toClassOnly: true })
+  startTime: Date;
+
+  @ValidateIf((_o, v) => { return !(v === '' || v === undefined || v === null); })
+  @IsDate()
+  @Expose()
+  @Transform(value => { if (value) { return new Date(value as any); } return null; }, { toClassOnly: true })
+  endTime: Date;
+
+  @IsInt()
+  @IsOptional()
+  @Expose()
+  limit: number;
+
+  @ValidateIf(o => { return o.type === 0; })
+  @Validate(IsCronExpression)
+  @Expose()
+  cron: string;
+
+  @IsInt()
+  @ValidateIf(o => { return o.type === 1; })
+  @Expose()
+  every: number;
+
+  @IsOptional()
+  @IsString()
+  @Expose()
+  data: string;
+
+  @IsOptional()
+  @IsString()
+  @Expose()
+  remark: string;
+
+}
+
+export class UpdateTaskDto extends CreateTaskDto {
+  @IsInt()
+  @Expose()
+  id: number;
+}
+
+export class CheckIdTaskDto {
+  @IsInt()
+  @Expose()
+  id: number;
+}

+ 80 - 0
app/dto/admin/sys/user.ts

@@ -0,0 +1,80 @@
+import {
+  IsInt,
+  Length,
+  Matches,
+  Allow,
+  IsEmail,
+  ArrayNotEmpty,
+  ValidateIf,
+  IsOptional,
+  IsIn,
+  IsNumberString,
+} from 'class-validator';
+import { Expose } from 'class-transformer';
+import { PageGetDto } from '../../comm';
+
+export class CreateUserDto {
+  @IsInt()
+  @Expose()
+  departmentId: number;
+
+  @Length(2)
+  @Expose()
+  name: string;
+
+  @Matches(/^[a-z0-9A-Z]+$/)
+  @Length(6, 20)
+  @Expose()
+  username: string;
+
+  @Allow()
+  @Expose()
+  nickName: string;
+
+  @ArrayNotEmpty()
+  @Expose()
+  roles: number[];
+
+  @Allow()
+  @Expose()
+  remark: string;
+
+  @ValidateIf((_o, v) => { return !(v === '' || v === undefined || v === null); })
+  @IsEmail()
+  @Expose()
+  email: string;
+
+  @Allow()
+  @Expose()
+  phone: string;
+
+  @IsOptional()
+  @IsIn([ 0, 1 ])
+  @Expose()
+  status: number;
+}
+
+export class UpdateUserDto extends CreateUserDto {
+  @IsInt()
+  @Expose()
+  id: number;
+}
+
+export class InfoUserDto {
+  @IsNumberString()
+  @Expose()
+  userId: string;
+}
+
+export class DeleteUserDto {
+  @ArrayNotEmpty()
+  @Expose()
+  userIds: number[];
+}
+
+export class QueryUserDto extends PageGetDto {
+  @IsOptional()
+  @IsNumberString()
+  @Expose()
+  departmentId: string;
+}

+ 38 - 0
app/dto/admin/tg/bot.ts

@@ -0,0 +1,38 @@
+import { Allow, IsInt, IsString } from 'class-validator';
+import { Expose } from 'class-transformer';
+
+export class CreateBotDto {
+  @IsInt()
+  @Expose()
+  botId:number;
+
+  @Allow()
+  @Expose()
+  username: string;
+
+  @Allow()
+  @Expose()
+  name: string;
+
+  @IsString()
+  @Expose()
+  token: string;
+
+  @IsInt()
+  @Expose()
+  status:number;
+
+  @Allow()
+  @Expose()
+  description:string;
+}
+export class UpdateBotDto extends CreateBotDto {
+  @IsInt()
+  @Expose()
+  id: number;
+}
+export class DeleteBotDto {
+  @IsInt()
+  @Expose()
+  id: number;
+}

+ 71 - 0
app/dto/admin/tg/group.ts

@@ -0,0 +1,71 @@
+import { Allow, ArrayMinSize, ArrayNotEmpty, IsInt, IsOptional, IsString } from 'class-validator';
+import { Expose } from 'class-transformer';
+
+export class CreateGroupDto {
+  @Allow()
+  @Expose()
+  name: string;
+
+  @Allow()
+  @Expose()
+  username: string;
+
+  @Allow()
+  @IsInt()
+  @Expose()
+  groupId:number;
+
+  @Allow()
+  @Expose()
+  description:string;
+
+  @IsInt()
+  @Expose()
+  type:number;
+
+  @Allow()
+  @IsInt()
+  @Expose()
+  defaultBotId:number;
+
+  @IsInt()
+  @Expose()
+  status:number;
+
+  @IsOptional()
+  @ArrayMinSize(0)
+  @Expose()
+  tgGroupBots: number[];
+}
+export class UpdateGroupDto extends CreateGroupDto {
+  @IsInt()
+  @Expose()
+  id: number;
+}
+export class DeleteGroupDto {
+  @ArrayNotEmpty()
+  @Expose()
+  ids: number[];
+}
+
+export class SendGroupsMsgDto {
+  @ArrayNotEmpty()
+  @Expose()
+  groupIds:number[];
+
+  @IsString()
+  @Expose()
+  message:string;
+}
+export class SendMsgDto{
+  @IsInt()
+  @Expose()
+  groupId: number;
+  @IsInt()
+  @Expose()
+  botId: number;
+
+  @IsString()
+  @Expose()
+  message:string;
+}

+ 62 - 0
app/dto/admin/verify.ts

@@ -0,0 +1,62 @@
+import {
+  Length,
+  IsString,
+  IsEmail,
+  ValidateIf,
+  Allow,
+} from 'class-validator';
+import { Expose } from 'class-transformer';
+
+export class LoginInfoDto {
+
+  @IsString()
+  @Expose()
+  username: string;
+
+  @IsString()
+  @Expose()
+  password: string;
+
+  @IsString()
+  @Expose()
+  captchaId: string;
+
+  @Length(4)
+  @Expose()
+  verifyCode: string;
+}
+
+export class UpdatePersonInfoDto {
+  @Length(2, 20)
+  @Expose()
+  name: string;
+
+  @Allow()
+  @Expose()
+  nickName: string;
+
+  @ValidateIf((_o, v) => { return !(v === '' || v === undefined || v === null); })
+  @IsEmail()
+  @Expose()
+  email: string;
+
+  @Allow()
+  @Expose()
+  phone: string;
+
+  @Allow()
+  @Expose()
+  originPassword: string;
+
+  @Allow()
+  @Expose()
+  newPassword: string;
+
+  @Allow()
+  @Expose()
+  remark: string;
+
+  @Allow()
+  @Expose()
+  headImg: string;
+}

+ 45 - 0
app/dto/comm.ts

@@ -0,0 +1,45 @@
+import {
+  IsInt,
+  IsOptional,
+  Min,
+} from 'class-validator';
+import { Expose, Transform } from 'class-transformer';
+
+export class PagePostDto {
+  @IsOptional()
+  @IsInt()
+  @Min(0)
+  @Expose()
+  limit: number;
+
+  @IsOptional()
+  @IsInt()
+  @Min(1)
+  @Expose()
+  page: number;
+}
+
+/**
+ * 由于query获取的参数只能为string,所以要区分开
+ */
+export class PageGetDto {
+  @IsOptional()
+  @IsInt()
+  @Min(1)
+  @Transform(v => {
+    const parsed = parseInt(v.value, 10);
+    return isNaN(parsed) ? 10 : parsed; // 默认值为 10
+  }, { toClassOnly: true })
+  @Expose()
+  pageSize: number;
+
+  @IsOptional()
+  @IsInt()
+  @Min(1)
+  @Transform(v => {
+    const parsed = parseInt(v.value, 10);
+    return isNaN(parsed) ? 1 : parsed; // 默认值为 1
+  }, { toClassOnly: true })
+  @Expose()
+  current: number;
+}

+ 18 - 0
app/entities/admin/image/space/info.ts

@@ -0,0 +1,18 @@
+import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
+import { BaseEntity } from '../../../base';
+
+@Entity({ name: 'image_space_info' })
+export default class ImageSpaceInfo extends BaseEntity {
+  @PrimaryGeneratedColumn()
+  id: number;
+
+  @Column({ name: 'type_id' })
+  typeId: number;
+
+  @Column({ type: 'varchar', length: 500 })
+  url: string;
+
+  @Column()
+  extra: string;
+
+}

+ 12 - 0
app/entities/admin/image/space/type.ts

@@ -0,0 +1,12 @@
+import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
+import { BaseEntity } from '../../../base';
+
+@Entity({ name: 'image_space_type' })
+export default class ImageSpaceType extends BaseEntity {
+  @PrimaryGeneratedColumn()
+  id: number;
+
+  @Column({ type: 'varchar', length: 50, unique: true })
+  name: string;
+
+}

+ 18 - 0
app/entities/admin/sys/department.ts

@@ -0,0 +1,18 @@
+import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
+import { BaseEntity } from '../../base';
+
+@Entity({ name: 'sys_department' })
+export default class SysDepartment extends BaseEntity {
+  @PrimaryGeneratedColumn()
+  id: number;
+
+  @Column({ name: 'parend_id', nullable: true })
+  parentId: number;
+
+  @Column()
+  name: string;
+
+  @Column({ name: 'order_num', type: 'int', nullable: true, default: 0 })
+  orderNum: number;
+
+}

+ 20 - 0
app/entities/admin/sys/login_log.ts

@@ -0,0 +1,20 @@
+import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
+import { BaseEntity } from '../../base';
+
+@Entity({ name: 'sys_login_log' })
+export default class SysLoginLog extends BaseEntity {
+  @PrimaryGeneratedColumn()
+  id: number;
+
+  @Column({ nullable: true, name: 'user_id' })
+  userId: number;
+
+  @Column({ nullable: true })
+  ip: string;
+
+  @Column({ type: 'datetime', nullable: true })
+  time: Date;
+
+  @Column({ length: 500, nullable: true })
+  ua: string;
+}

+ 42 - 0
app/entities/admin/sys/menu.ts

@@ -0,0 +1,42 @@
+import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
+import { BaseEntity } from '../../base';
+
+@Entity({ name: 'sys_menu' })
+export default class SysMenu extends BaseEntity {
+  @PrimaryGeneratedColumn()
+  id: number;
+
+  @Column({ name: 'parent_id', nullable: true })
+  parentId: number;
+
+  @Column()
+  name: string;
+
+  @Column({ nullable: true })
+  router: string;
+
+  @Column({ nullable: true })
+  perms: string;
+
+  @Column({ type: 'tinyint', default: 0 })
+  type: number;
+
+  @Column({ nullable: true })
+  icon: string;
+
+  @Column({ name: 'order_num', type: 'int', default: 0, nullable: true })
+  orderNum: number;
+
+  @Column({ name: 'view_path', nullable: true })
+  viewPath: string;
+
+  @Column({ type: 'boolean', nullable: true, default: true })
+  keepalive: boolean;
+
+  @Column({ type: 'boolean', nullable: true, default: true })
+  isShow: boolean;
+
+  @Column({ type: 'int', default: 1 })
+  status: number;
+
+}

+ 29 - 0
app/entities/admin/sys/req_log.ts

@@ -0,0 +1,29 @@
+import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
+import { BaseEntity } from '../../base';
+
+@Entity({ name: 'sys_req_log' })
+export default class SysReqLog extends BaseEntity {
+  @PrimaryGeneratedColumn()
+  id: number;
+
+  @Column({ nullable: true })
+  ip: string;
+
+  @Column({ nullable: true, name: 'user_id' })
+  userId: number;
+
+  @Column({ type: 'text', nullable: true })
+  params: string;
+
+  @Column({ length: 100, nullable: true })
+  action: string;
+
+  @Column({ length: 15, nullable: true })
+  method: string;
+
+  @Column({ type: 'int', nullable: true })
+  status: number;
+
+  @Column({ type: 'int', nullable: true, name: 'consume_time', default: 0 })
+  consumeTime: number;
+}

+ 23 - 0
app/entities/admin/sys/role.ts

@@ -0,0 +1,23 @@
+import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
+import { BaseEntity } from '../../base';
+
+@Entity({ name: 'sys_role' })
+export default class SysRole extends BaseEntity {
+  @PrimaryGeneratedColumn()
+  id: number;
+
+  @Column()
+  userId: string;
+
+  @Column({ unique: true })
+  name: string;
+
+  @Column({ length: 50, unique: true })
+  label: string;
+
+  @Column({ nullable: true })
+  remark: string;
+
+  @Column()
+  status: number;
+}

+ 14 - 0
app/entities/admin/sys/role_department.ts

@@ -0,0 +1,14 @@
+import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
+import { BaseEntity } from '../../base';
+
+@Entity({ name: 'sys_role_department' })
+export default class SysRoleDepartment extends BaseEntity {
+  @PrimaryGeneratedColumn()
+  id: number;
+
+  @Column({ name: 'role_id' })
+  roleId: number;
+
+  @Column({ name: 'department_id' })
+  departmentId: number;
+}

+ 14 - 0
app/entities/admin/sys/role_menu.ts

@@ -0,0 +1,14 @@
+import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
+import { BaseEntity } from '../../base';
+
+@Entity({ name: 'sys_role_menu' })
+export default class SysRoleMenu extends BaseEntity {
+  @PrimaryGeneratedColumn()
+  id: number;
+
+  @Column({ name: 'role_id' })
+  roleId: number;
+
+  @Column({ name: 'menu_id' })
+  menuId: number;
+}

+ 45 - 0
app/entities/admin/sys/task.ts

@@ -0,0 +1,45 @@
+import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
+import { BaseEntity } from '../../base';
+
+@Entity({ name: 'sys_task' })
+export default class SysTask extends BaseEntity {
+  @PrimaryGeneratedColumn()
+  id: number;
+
+  @Column({ type: 'varchar', length: 50, unique: true })
+  name: string;
+
+  @Column()
+  service: string;
+
+  @Column({ type: 'tinyint', default: 0 })
+  type: number;
+
+  @Column({ type: 'tinyint', default: 1 })
+  status: number;
+
+  @Column({ name: 'start_time', type: 'datetime', nullable: true })
+  startTime: Date;
+
+  @Column({ name: 'end_time', type: 'datetime', nullable: true })
+  endTime: Date;
+
+  @Column({ type: 'int', nullable: true, default: 0 })
+  limit: number;
+
+  @Column({ nullable: true })
+  cron: string;
+
+  @Column({ type: 'int', nullable: true })
+  every: number;
+
+  @Column({ type: 'text', nullable: true })
+  data: string;
+
+  @Column({ name: 'job_opts', type: 'text', nullable: true })
+  jobOpts: string;
+
+  @Column({ nullable: true })
+  remark: string;
+
+}

+ 18 - 0
app/entities/admin/sys/task_log.ts

@@ -0,0 +1,18 @@
+import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
+import { BaseEntity } from '../../base';
+
+@Entity({ name: 'sys_task_log' })
+export default class SysTaskLog extends BaseEntity {
+  @PrimaryGeneratedColumn()
+  id: number;
+
+  @Column({ name: 'task_id' })
+  taskId: number;
+
+  @Column({ type: 'tinyint', default: 0 })
+  status: number;
+
+  @Column({ type: 'text', nullable: true })
+  detail: string;
+
+}

+ 39 - 0
app/entities/admin/sys/user.ts

@@ -0,0 +1,39 @@
+import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
+import { BaseEntity } from '../../base';
+
+@Entity({ name: 'sys_user' })
+export default class SysUser extends BaseEntity {
+  @PrimaryGeneratedColumn()
+  id: number;
+
+  @Column({ name: 'department_id' })
+  departmentId: number;
+
+  @Column()
+  name: string;
+
+  @Column({ unique: true })
+  username: string;
+
+  @Column()
+  password: string;
+
+  @Column({ name: 'nick_name', nullable: true })
+  nickName: string;
+
+  @Column({ name: 'head_img', nullable: true })
+  headImg: string;
+
+  @Column({ nullable: true })
+  email: string;
+
+  @Column({ nullable: true })
+  phone: string;
+
+  @Column({ nullable: true })
+  remark: string;
+
+  @Column({ type: 'tinyint', nullable: true, default: 1 })
+  status: number;
+
+}

+ 14 - 0
app/entities/admin/sys/user_role.ts

@@ -0,0 +1,14 @@
+import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
+import { BaseEntity } from '../../base';
+
+@Entity({ name: 'sys_user_role' })
+export default class SysUserRole extends BaseEntity {
+  @PrimaryGeneratedColumn()
+  id: number;
+
+  @Column({ name: 'user_id' })
+  userId: number;
+
+  @Column({ name: 'role_id' })
+  roleId: number;
+}

+ 66 - 0
app/entities/admin/tg/TgBot.ts

@@ -0,0 +1,66 @@
+import {
+  Column,
+  Entity,
+  Index,
+  OneToMany,
+  PrimaryGeneratedColumn,
+} from 'typeorm';
+import TgGroupBot from './TgGroupBot';
+
+@Index('uk_botId', [ 'botId' ], { unique: true })
+@Index('uk_username', [ 'username' ], { unique: true })
+@Entity('tg_bot', { schema: 'tgbot' })
+export default class TgBot {
+  @PrimaryGeneratedColumn({ type: 'int', name: 'id' })
+  id: number;
+
+  @Column('bigint', { name: 'botId', unique: true, comment: '机器人ID' })
+  botId: number;
+
+  @Column('varchar', {
+    name: 'username',
+    unique: true,
+    comment: '机器人用户名',
+    length: 100,
+  })
+  username: string;
+
+  @Column('varchar', { name: 'name', comment: '机器人名称', length: 100 })
+  name: string;
+
+  @Column('varchar', { name: 'token', comment: '机器人Token', length: 255 })
+  token: string;
+
+  @Column('tinyint', {
+    name: 'status',
+    nullable: true,
+    comment: '机器人状态 0 1',
+    default: () => "'1'",
+  })
+  status: number | null;
+
+  @Column('varchar', {
+    name: 'description',
+    nullable: true,
+    comment: '机器人描述',
+    length: 500,
+  })
+  description: string | null;
+
+  @Column('datetime', {
+    name: 'createTime',
+    comment: '创建时间',
+    default: () => 'CURRENT_TIMESTAMP',
+  })
+  createTime: Date;
+
+  @Column('datetime', {
+    name: 'updateTime',
+    comment: '更新时间',
+    default: () => 'CURRENT_TIMESTAMP',
+  })
+  updateTime: Date;
+
+  @OneToMany(() => TgGroupBot, tgGroupBot => tgGroupBot.bot)
+  tgGroupBots: TgGroupBot[];
+}

+ 30 - 0
app/entities/admin/tg/TgBotSendLog.ts

@@ -0,0 +1,30 @@
+import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm';
+
+@Index('idx_botId', [ 'botId' ], {})
+@Index('idx_groupId', [ 'groupId' ], {})
+@Entity('tg_bot_send_log', { schema: 'tgbot' })
+export default class TgBotSendLog {
+  @PrimaryGeneratedColumn({ type: 'int', name: 'id', unsigned: true })
+  id: number;
+
+  @Column('bigint', { name: 'botId', comment: '机器人Id' })
+  botId: number;
+
+  @Column('bigint', { name: 'groupId', comment: '群id' })
+  groupId: string;
+
+  @Column('text', { name: 'text', nullable: true, comment: '发送消息' })
+  text: string | null;
+
+  @Column('datetime', {
+    name: 'createTime',
+    default: () => 'CURRENT_TIMESTAMP',
+  })
+  createTime: Date;
+
+  @Column('datetime', {
+    name: 'updateTime',
+    default: () => 'CURRENT_TIMESTAMP',
+  })
+  updateTime: Date;
+}

+ 81 - 0
app/entities/admin/tg/TgGroup.ts

@@ -0,0 +1,81 @@
+import {
+  Column,
+  Entity,
+  Index,
+  OneToMany,
+  PrimaryGeneratedColumn,
+} from 'typeorm';
+import TgGroupBot from './TgGroupBot';
+
+@Index('uk_groupId', [ 'groupId' ], { unique: true })
+@Entity('tg_group', { schema: 'tgbot' })
+export default class TgGroup {
+  @PrimaryGeneratedColumn({ type: 'int', name: 'id' })
+  id: number;
+
+  @Column('varchar', { name: 'name', nullable: true, comment: '群组名称', length: 100 })
+  name: string;
+
+  @Column('varchar', {
+    name: 'username',
+    nullable: true,
+    comment: '群组用户名或邀请链接',
+    length: 100,
+  })
+  username: string;
+
+  @Column('bigint', {
+    name: 'groupId',
+    nullable: true,
+    unique: true,
+    comment: '群组ID(加入群组后获取)',
+  })
+  groupId: number;
+
+  @Column('varchar', {
+    name: 'description',
+    nullable: true,
+    comment: '群组描述',
+    length: 500,
+  })
+  description: string | null;
+
+  @Column('int', {
+    name: 'type',
+    comment: '分类ID 0 只听 1只发 2 既听又发',
+    default: () => "'0'",
+  })
+  type: number;
+
+  @Column('bigint', {
+    name: 'defaultBotId',
+    nullable: true,
+    comment: '默认机器人ID',
+  })
+  defaultBotId: number | null;
+
+  @Column('tinyint', {
+    name: 'status',
+    comment: '是否启用',
+    width: 1,
+    default: () => "'1'",
+  })
+  status: boolean;
+
+  @Column('datetime', {
+    name: 'createTime',
+    comment: '创建时间',
+    default: () => 'CURRENT_TIMESTAMP',
+  })
+  createTime: Date;
+
+  @Column('datetime', {
+    name: 'updateTime',
+    comment: '更新时间',
+    default: () => 'CURRENT_TIMESTAMP',
+  })
+  updateTime: Date;
+
+  @OneToMany(() => TgGroupBot, tgGroupBot => tgGroupBot.group)
+  tgGroupBots: TgGroupBot[];
+}

+ 53 - 0
app/entities/admin/tg/TgGroupBot.ts

@@ -0,0 +1,53 @@
+import {
+  Column,
+  Entity,
+  Index,
+  JoinColumn,
+  ManyToOne,
+  PrimaryGeneratedColumn,
+} from 'typeorm';
+import TgBot from './TgBot';
+import TgGroup from './TgGroup';
+
+@Index('idx_bot', [ 'botId' ], {})
+@Index('idx_group', [ 'groupId' ], {})
+@Index('uk_group_bot', [ 'groupId', 'botId' ], { unique: true })
+@Entity('tg_group_bot', { schema: 'tgbot' })
+export default class TgGroupBot {
+  @PrimaryGeneratedColumn({ type: 'int', name: 'id' })
+  id: number;
+
+  @Column('bigint', { name: 'groupId', comment: '群组ID' })
+  groupId: number;
+
+  @Column('bigint', { name: 'botId', comment: '机器人ID' })
+  botId: number;
+
+  @Column('datetime', {
+    name: 'createTime',
+    comment: '创建时间',
+    default: () => 'CURRENT_TIMESTAMP',
+  })
+  createTime: Date;
+
+  @Column('datetime', {
+    name: 'updateTime',
+    comment: '更新时间',
+    default: () => 'CURRENT_TIMESTAMP',
+  })
+  updateTime: Date;
+
+  @ManyToOne(() => TgBot, tgBot => tgBot.tgGroupBots, {
+    onDelete: 'CASCADE',
+    onUpdate: 'NO ACTION',
+  })
+  @JoinColumn([{ name: 'botId', referencedColumnName: 'botId' }])
+  bot: TgBot;
+
+  @ManyToOne(() => TgGroup, tgGroup => tgGroup.tgGroupBots, {
+    onDelete: 'CASCADE',
+    onUpdate: 'NO ACTION',
+  })
+  @JoinColumn([{ name: 'groupId', referencedColumnName: 'groupId' }])
+  group: TgGroup;
+}

+ 98 - 0
app/entities/admin/tg/TgMessage.ts

@@ -0,0 +1,98 @@
+import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm';
+
+@Index('idx_chat_type', [ 'chatType' ], {})
+@Index('idx_chatId', [ 'chatId' ], {})
+@Index('uk_message', [ 'botId', 'messageId', 'chatId' ], { unique: true })
+@Entity('tg_message', { schema: 'tgbot' })
+export default class TgMessage {
+  @PrimaryGeneratedColumn({ type: 'int', name: 'id', unsigned: true })
+  id: number;
+
+  @Column('bigint', { name: 'botId', comment: '哪个机器人监听的' })
+  botId: number;
+
+  @Column('bigint', { name: 'messageId', comment: 'Telegram消息ID' })
+  messageId: number;
+
+  @Column('bigint', { name: 'chatId', comment: '群组ID或个人ID' })
+  chatId: number;
+
+  @Column('varchar', {
+    name: 'chatType',
+    comment: 'group 群组 private个人',
+    length: 50,
+    default: () => "'group'",
+  })
+  chatType: string;
+
+  @Column('varchar', {
+    name: 'chatTitle',
+    nullable: true,
+    comment: '群组标题',
+    length: 255,
+  })
+  chatTitle: string | null;
+
+  @Column('varchar', {
+    name: 'chatName',
+    nullable: true,
+    comment: '个人聊天昵称',
+    length: 255,
+  })
+  chatName: string | null;
+
+  @Column('varchar', {
+    name: 'chatUsername',
+    nullable: true,
+    comment: '个人聊天用户名',
+    length: 255,
+  })
+  chatUsername: string | null;
+
+  @Column('bigint', { name: 'fromUserId', comment: '发送者用户id' })
+  fromUserId: string;
+
+  @Column('varchar', {
+    name: 'fromName',
+    nullable: true,
+    comment: '发送者名字',
+    length: 255,
+  })
+  fromName: string | null;
+
+  @Column('varchar', {
+    name: 'fromUsername',
+    nullable: true,
+    comment: '发送者用户名',
+    length: 255,
+  })
+  fromUsername: string | null;
+
+  @Column('tinyint', {
+    name: 'isBot',
+    comment: '是否来自机器人',
+    unsigned: true,
+    default: () => "'0'",
+  })
+  isBot: number;
+
+  @Column('int', { name: 'sentTime', nullable: true, comment: '发送时间' })
+  sentTime: number | null;
+
+  @Column('text', { name: 'text', nullable: true, comment: '消息内容' })
+  text: string | null;
+
+  @Column('datetime', {
+    name: 'createTime',
+    comment: '创建时间',
+    default: () => 'CURRENT_TIMESTAMP',
+  })
+  createTime: Date;
+
+  @Column('datetime', {
+    name: 'updateTime',
+    comment: '跟新时间',
+    default: () => 'CURRENT_TIMESTAMP',
+  })
+  updateTime: Date;
+}

+ 9 - 0
app/entities/base.ts

@@ -0,0 +1,9 @@
+import { CreateDateColumn, UpdateDateColumn } from 'typeorm';
+
+export abstract class BaseEntity {
+  @CreateDateColumn()
+  createTime: Date;
+
+  @UpdateDateColumn()
+  updateTime: Date;
+}

+ 106 - 0
app/extend/helper.ts

@@ -0,0 +1,106 @@
+import * as yaml from 'js-yaml';
+import * as fs from 'fs';
+import * as path from 'path';
+import * as CryptoJS from 'crypto-js';
+import { IHelper } from 'egg';
+import { customAlphabet } from 'nanoid';
+import * as moment from 'moment';
+import * as jwt from 'jsonwebtoken';
+import { v4 as uuidv4 } from 'uuid';
+
+/**
+ * 格式化时间
+ */
+export const now = function() {
+  return moment(new Date()).format('YYYY-MM-DD HH:mm:ss');
+};
+
+/**
+ * 错误信息map
+ */
+let errorMap = null;
+
+export default {
+
+  /**
+   * 获取请求IP
+   */
+  getReqIP(this: IHelper) {
+    const req: any = this.ctx.req;
+    return (req.headers['x-forwarded-for'] || // 判断是否有反向代理 IP
+            req.connection.remoteAddress || // 判断 connection 的远程 IP
+            req.socket.remoteAddress || // 判断后端的 socket 的 IP
+            req.connection.socket.remoteAddress).replace('::ffff:', '');
+  },
+
+  /**
+   * 根据code获取错误信息
+   */
+  getErrorMessageByCode(code: string) {
+    if (!errorMap) {
+      errorMap = yaml.safeLoad(fs.readFileSync(path.resolve(__dirname, '../../config/error_constants.yaml'), 'utf8'));
+    }
+    return errorMap![code];
+  },
+
+  /**
+   * AES加密
+   */
+  aesEncrypt(this: IHelper, msg: string, secret: string) {
+    return CryptoJS.AES.encrypt(msg, secret).toString();
+  },
+
+  /**
+   * AES解密
+   */
+  aesDecrypt(this: IHelper, encrypted: string, secret: string) {
+    return CryptoJS.AES.decrypt(encrypted, secret).toString(CryptoJS.enc.Utf8);
+  },
+
+  /**
+   * md5加密
+   */
+  md5(msg: string) {
+    return CryptoJS.MD5(msg).toString();
+  },
+
+  /**
+   * 生成一个随机的值
+   */
+  generateRandomValue(length: number, placeholder = '1234567890qwertyuiopasdfghjklzxcvbnm') {
+    const nanoid = customAlphabet(placeholder, length);
+    return nanoid();
+  },
+
+  /**
+   * 生成一个UUID
+   */
+  generateUUID() {
+    return uuidv4();
+  },
+
+  /**
+   * 获取当前时间
+   */
+  getDate() {
+    return now();
+  },
+
+  /**
+   * JsonWebToken Sign
+   * https://github.com/auth0/node-jsonwebtoken
+   */
+  jwtSign(this: IHelper, sign: any, options?: any) {
+    return jwt.sign(sign, this.config.jwt.secret, options);
+  },
+
+  /**
+   * JsonWebToken Verify
+   * https://github.com/auth0/node-jsonwebtoken
+   */
+  jwtVerify(this: IHelper, token: string, options?: any) {
+    return jwt.verify(token, this.config.jwt.secret, options);
+  },
+
+};
+

+ 87 - 0
app/middleware/admin_authority.ts

@@ -0,0 +1,87 @@
+import { Context } from 'egg';
+import * as _ from 'lodash';
+
+// 无需token的地址
+const noTokenUrl = [
+  '/admin/captcha/img',
+  '/admin/login',
+  '/admin/tg/tgboot/test',
+];
+
+const noPermUrl = [
+  '/admin/permmenu',
+  '/admin/person',
+  '/admin/logout',
+  // '/admin/role_menu',
+];
+
+
+/**
+ * Admin权限验证中间件,只检测/admin开头请求
+ * Token验证通过会把当前解析的Token解析把对象挂载到ctx.token上,token对象例如{ uid, pv }
+ */
+export default function AdminAuthority(): any {
+  return async (ctx: Context, next: () => Promise<any>) => {
+    const { url } = ctx;
+    let statusCode = 200;
+    let errorCode = 0;
+    const token = ctx.get('Authorization');
+    if (_.startsWith(url, '/admin')) {
+      if (noTokenUrl.includes(url.split('?')[0])) {
+        await next();
+        return;
+      }
+      try {
+        ctx.token = ctx.helper.jwtVerify(token);
+      } catch (e) {
+        statusCode = 401;
+        errorCode = 11001;
+      }
+      if (ctx.token) {
+        if (noPermUrl.includes(url.split('?')[0])) {
+          await next();
+          return;
+        }
+        const pv = await ctx.service.admin.comm.verify.getRedisPasswordVersionById(ctx.token.uid);
+        if (pv !== `${ctx.token.pv}`) {
+          // 判断密码版本,防止登录时更改密码还在允许使用
+          errorCode = 11002;
+          statusCode = 401;
+        } else {
+          const redisToken = await ctx.service.admin.comm.verify.getRedisTokenById(ctx.token.uid);
+          // 查询token是否一致
+          if (token !== redisToken) {
+            errorCode = 11002;
+            statusCode = 401;
+          } else {
+            // 遍历权限是否包含该url,不包含则无访问权限
+            let perms = await ctx.service.admin.comm.verify.getRedisPermsById(ctx.token.uid);
+            if (_.isEmpty(perms)) {
+              errorCode = 11001;
+              statusCode = 403;
+            } else {
+              // 将sys:admin:user等转换成sys/admin/user
+              perms = JSON.parse(perms!).map(e => {
+                return e.replace(/:/g, '/');
+              });
+              if (!perms!.includes(url.split('?')[0].replace('/admin/', ''))) {
+                errorCode = 11003;
+                statusCode = 403;
+              }
+            }
+          }
+        }
+      }
+      if (statusCode > 200) {
+        ctx.status = 200;
+        ctx.body = {
+          code: errorCode,
+          message: ctx.helper.getErrorMessageByCode(`${errorCode}`),
+        };
+        return;
+      }
+    }
+    // has perms, pass
+    await next();
+  };
+}

+ 19 - 0
app/middleware/admin_req_log.ts

@@ -0,0 +1,19 @@
+import { Context } from 'egg';
+
+/**
+ * 日志中间件,记录请求
+ */
+export default function AdminReqLog(): any {
+  return async (ctx: Context, next: () => Promise<any>) => {
+    const startTime = Date.now();
+    await next();
+    const reportTime = Date.now() - startTime;
+    const { url } = ctx;
+    console.log('url:',url);
+    // 该接口不做记录/admin/sys/log/page
+    if (url.startsWith('/admin') && !url.startsWith('/admin/sys/req-log/page')) {
+      ctx.service.admin.sys.reqLog.save(url.split('?')[0],
+        ctx.req.method === 'GET' ? ctx.request.query : ctx.request.body, ctx.status, reportTime, ctx.req.method, ctx.token ? ctx.token.uid : null);
+    }
+  };
+}

+ 24 - 0
app/middleware/execption.ts

@@ -0,0 +1,24 @@
+import { Context } from 'egg';
+
+/**
+ * 统一异常处理
+ */
+export default function Exception(): any {
+  return async (ctx: Context, next: () => Promise<any>) => {
+    try {
+      await next();
+    } catch (err) {
+      const { errors } = err;
+      ctx.logger.error('[Exception]', err.message, errors);
+      ctx.set('Content-Type', 'application/json');
+      // 生产环境时 500 错误的详细错误内容不返回给客户端,因为可能包含敏感信息
+      const status = err.status || 500;
+      const message = status === 500 && ctx.app.config.env === 'prod' ? '服务器好像出了点问题...稍后再试试' : err.message;
+      ctx.status = status;
+      ctx.body = JSON.stringify({
+        errorCode: err.errorCode || 500,
+        message,
+      });
+    }
+  };
+}

+ 32 - 0
app/queue/sys.ts

@@ -0,0 +1,32 @@
+import * as Queue from 'bull';
+import { Application } from 'egg';
+
+/**
+ * 自定义任务队列
+ */
+export default (app: Application) => {
+  const ctx = app.createAnonymousContext();
+  const tq = new Queue('sys-task', app.config.bull.default);
+
+  // 处理任务
+  tq.process(async function(job) {
+    let id = -1;
+    try {
+      id = await ctx.service.admin.sys.taskLog.record(job.data.id, 0);
+      await ctx.service.admin.sys.task.callService(job.data.service, job.data.args);
+      await ctx.service.admin.sys.taskLog.updateTaskStatus(id, 1);
+    } catch (e) {
+      if (id !== -1) {
+        await ctx.service.admin.sys.taskLog.updateTaskStatus(id, 2, `${e.message}`);
+      }
+    }
+  });
+  // eslint-disable-next-line @typescript-eslint/no-unused-vars
+  tq.on('completed', (job, _result) => {
+    ctx.service.admin.sys.task.updateTaskCompleteStatus(job.data.id);
+  });
+  // tq.on('global:completed', (jobId, _result) => {
+  //   ctx.service.admin.sys.task.updateTaskCompleteStatus(jobId);
+  // });
+  return tq;
+};

+ 9 - 0
app/router.ts

@@ -0,0 +1,9 @@
+import { Application } from 'egg';
+import { initRouter } from './decorator/router_register';
+
+export default (app: Application) => {
+  // const { controller, router } = app;
+  // router.all('/a', controller.welcome.index);
+  // 使用@Route进行注册路由
+  initRouter(app);
+};

+ 32 - 0
app/service/admin/comm/email.ts

@@ -0,0 +1,32 @@
+import BaseService from '../../base';
+import * as nodemailer from 'nodemailer';
+import Mail = require('nodemailer/lib/mailer');
+
+/**
+ * 邮件服务
+ */
+export default class EmailService extends BaseService {
+
+  /**
+   * 发送邮件
+   */
+  async sendEmail(info: Mail.Options) {
+    const { host, port, user, pass, secure } = this.config.mailer;
+    // let testAccount = await nodemailer.createTestAccount();
+    // create reusable transporter object using the default SMTP transport
+    const transporter = nodemailer.createTransport({
+      host,
+      port,
+      secure, // true for 465, false for other ports
+      auth: {
+        user, // generated ethereal user
+        pass, // generated ethereal password
+      },
+    });
+
+    // send mail with defined transport object
+    const result = await transporter.sendMail(info);
+    return result;
+  }
+
+}

+ 78 - 0
app/service/admin/comm/oss.ts

@@ -0,0 +1,78 @@
+import BaseService from '../../base';
+import * as qiniu from 'qiniu';
+import * as path from 'path';
+import * as moment from 'moment';
+import ImageSpaceInfo from '../../../entities/admin/image/space/info';
+
+/**
+ * OSS Service
+ */
+export default class OssService extends BaseService {
+
+  /**
+   * 将文件上传至七牛
+   */
+  async upload(file: any, namespace: string) {
+    const { bucket, accessKey, secretKey, zone } = this.config.qiniu;
+    const key = `${namespace}/${moment().format('YYYYMMDD_hhmmss')}${path.extname(file.filename)}`;
+    const mac = new qiniu.auth.digest.Mac(accessKey, secretKey);
+    const options = {
+      scope: `${bucket}:${key}`,
+    };
+    const putPolicy = new qiniu.rs.PutPolicy(options);
+    const qiniuConfig: any = new qiniu.conf.Config();
+    qiniuConfig.zone = qiniu.zone[zone];
+    const uploadToken = putPolicy.uploadToken(mac);
+    return new Promise((resole, reject) => {
+      const localFile = file.filepath;
+      const formUploader = new qiniu.form_up.FormUploader(qiniuConfig);
+      const putExtra = new qiniu.form_up.PutExtra();
+      // 文件上传
+      formUploader.putFile(uploadToken, key, localFile, putExtra, function(respErr,
+        respBody, respInfo) {
+        if (respErr) {
+          reject(respErr);
+        }
+        if (respInfo.statusCode === 200) {
+          resole(respBody);
+        } else {
+          reject(respErr);
+        }
+      });
+    });
+  }
+
+  /**
+   * 批量删除文件
+   */
+  async delete(images: ImageSpaceInfo[]) {
+    const { bucket, accessKey, secretKey, zone } = this.config.qiniu;
+    if (images && images.length > 0) {
+      const keys = images.map(e => {
+        const { key } = JSON.parse(e.extra);
+        return qiniu.rs.deleteOp(bucket, key);
+      });
+      const mac = new qiniu.auth.digest.Mac(accessKey, secretKey);
+      const qiniuConfig: any = new qiniu.conf.Config();
+      qiniuConfig.zone = qiniu.zone[zone];
+      const bucketManager = new qiniu.rs.BucketManager(mac, qiniuConfig);
+      return new Promise((resolve, reject) => {
+        bucketManager.batch(keys, (err, respBody, respInfo) => {
+          if (err) {
+            reject(err);
+          } else {
+            // 200 is success, 298 is part success
+            if (parseInt(respInfo.statusCode) / 100 === 2) {
+              resolve();
+            } else {
+              reject(respInfo.deleteusCode);
+              console.log(respInfo.deleteusCode);
+              console.log(respBody);
+            }
+          }
+        });
+      });
+    }
+  }
+
+}

+ 105 - 0
app/service/admin/comm/verify.ts

@@ -0,0 +1,105 @@
+import BaseService from '../../base';
+import * as svgCaptcha from 'svg-captcha';
+import * as _ from 'lodash';
+
+/**
+ * 通用功能Servce
+ */
+export default class VerifyService extends BaseService {
+
+  /**
+   * 生成图片验证码
+   */
+  async getImgCaptcha(params) {
+    const svg = svgCaptcha.create({
+      size: 4,
+      color: true,
+      noise: 4,
+      width: params.width ?? 100,
+      height: params.height ?? 50,
+    });
+    const result = {
+      img: `data:image/svg+xml;base64,${new Buffer(svg.data).toString('base64')}`,
+      id: this.getHelper().generateUUID(),
+    };
+    // 10分钟过期时间
+    await this.getAdminRedis().set(`admin:captcha:img:${result.id}`, svg.text, 'EX', 60 * 10);
+    return result;
+  }
+
+  /**
+   * 校验验证码
+   */
+  async checkImgCaptcha(id: string, code: string) {
+    const result = await this.getAdminRedis().get(`admin:captcha:img:${id}`);
+    if (_.isEmpty(result)) {
+      return false;
+    }
+    if (code.toLowerCase() !== result!.toLowerCase()) {
+      return false;
+    }
+    // 校验成功后移除验证码
+    await this.getAdminRedis().del(`admin:captcha:img:${id}`);
+    return true;
+  }
+
+  /**
+   * 获取登录JWT
+   * 返回null则账号密码有误,不存在该用户
+   */
+  async getLoginSign(username: string, password: string) {
+    // const decodeUserName = this.getHelper().aesDecrypt(username, this.config.aesSecret.front);
+    const decodeUserName = username;
+    // const decodePassword = this.getHelper().aesDecrypt(password, this.config.aesSecret.front);
+    const decodePassword = password;
+    const user = await this.getRepo().admin.sys.User.findOne({ username: decodeUserName, status: 1 });
+    if (_.isEmpty(user)) {
+      return null;
+    }
+    if (this.getHelper().aesDecrypt(user!.password, this.config.aesSecret.admin) !== decodePassword) {
+      return null;
+    }
+    const perms = await this.service.admin.sys.menu.getPerms(user!.id);
+    const jwtSign = this.getHelper().jwtSign({
+      uid: parseInt(user!.id.toString()),
+      pv: 1,
+    }, {
+      expiresIn: '24h',
+    });
+    await this.getAdminRedis().set(`admin:passwordVersion:${user!.id}`, 1);
+    await this.getAdminRedis().set(`admin:token:${user!.id}`, jwtSign);
+    await this.getAdminRedis().set(`admin:perms:${user!.id}`, JSON.stringify(perms));
+    // 保存登录日志
+    await this.service.admin.sys.loginLog.save(user!.id);
+    return jwtSign;
+  }
+
+  /**
+   * 清除登录状态信息
+   */
+  async clearLoginStatus(uid: number) {
+    await this.service.admin.sys.user.forbidden(uid);
+  }
+
+  /**
+   * 获取权限菜单
+   */
+  async getPermMenu(uid: number) {
+    const menus = await this.service.admin.sys.menu.getMenus(uid);
+    const perms = await this.service.admin.sys.menu.getPerms(uid);
+    return { menus, perms };
+  }
+
+  async getRedisPasswordVersionById(id: number) {
+    return this.getAdminRedis().get(`admin:passwordVersion:${id}`);
+  }
+
+  async getRedisTokenById(id: number) {
+    return this.getAdminRedis().get(`admin:token:${id}`);
+  }
+
+  async getRedisPermsById(id: number) {
+    return this.getAdminRedis().get(`admin:perms:${id}`);
+  }
+
+}

+ 84 - 0
app/service/admin/space/image.ts

@@ -0,0 +1,84 @@
+import BaseService from '../../base';
+import { In } from 'typeorm';
+
+/**
+ * 图片空间Service
+ */
+export default class ImageSpaceService extends BaseService {
+
+  /**
+   * 查找是否存在当前图片空间类型
+   */
+  async find(typeId: number) {
+    const result = await this.getRepo().admin.image.space.Type.findOne({ id: typeId });
+    return result;
+  }
+
+  /**
+   * 查找所有图片空间类型
+   */
+  async type() {
+    return await this.getRepo().admin.image.space.Type.find();
+  }
+
+  /**
+   * 增加分类
+   */
+  async addType(name: string) {
+    await this.getRepo().admin.image.space.Type.insert({ name });
+  }
+
+  /**
+   * 删除分类
+   */
+  async deleteType(id: number) {
+    await this.getRepo().admin.image.space.Type.delete(id);
+  }
+
+  /**
+   * 查找当前分类下是否有图片
+   */
+  async findCurrentTypeHasImage(id: number) {
+    const count = await this.getRepo().admin.image.space.Info.count({ typeId: id });
+    return count > 0;
+  }
+
+  /**
+   * 获取当前图片空间下的所有图片数量
+   */
+  async count(typeId: number) {
+    return await this.getRepo().admin.image.space.Info.count(typeId === -1 ? {} : { typeId });
+  }
+
+  /**
+   * 根据数据id删除图片
+   */
+  async deleteImageByIds(ids: number[]) {
+    const result = await this.getRepo().admin.image.space.Info.find({ id: In(ids) });
+    this.service.admin.comm.oss.delete(result);
+    await this.getRepo().admin.image.space.Info.delete(ids);
+  }
+
+  /**
+   * 分页获取图片空间下的图片
+   */
+  async page(typeId: number, page: number, count: number) {
+    const result = await this.getRepo().admin.image.space.Info.find({
+      where: typeId === -1 ? {} : { typeId },
+      order: {
+        id: 'DESC',
+      },
+      take: count,
+      skip: page * count,
+    });
+    return result;
+  }
+
+  /**
+   * add info item
+   */
+  async add(typeId: number, url: string, extra?: string) {
+    await this.getRepo().admin.image.space.Info.save({ typeId, url, extra });
+  }
+
+}

+ 92 - 0
app/service/admin/sys/dept.ts

@@ -0,0 +1,92 @@
+import BaseService from '../../base';
+import * as _ from 'lodash';
+import { In } from 'typeorm';
+import { UpdateDeptDto } from '../../../dto/admin/sys/dept';
+
+/**
+ * 系统部门Service
+ */
+export default class SysDeptService extends BaseService {
+
+  /**
+   * 获取所有部门
+   */
+  async list() {
+    return await this.getRepo().admin.sys.Department.find();
+  }
+
+  /**
+   * 根据ID查找部门信息
+   */
+  async info(id: number) {
+    const department = await this.getRepo().admin.sys.Department.findOne({ id });
+    let parentDepartment: any = null;
+    if (department!.parentId) {
+      parentDepartment = await this.getRepo().admin.sys.Department.findOne({ id: department!.parentId });
+    }
+    return { department, parentDepartment };
+  }
+
+  /**
+   * 更新部门信息
+   */
+  async update(param: UpdateDeptDto) {
+    await this.getRepo().admin.sys.Department.update(param.id, { parentId: param.parentId === -1 ? undefined : param.parentId });
+  }
+
+  /**
+   * 转移部门
+   */
+  async transfer(userIds: number[], deptId: number) {
+    await this.getRepo().admin.sys.User.update({ id: In(userIds) }, { departmentId: deptId });
+  }
+
+  /**
+   * 新增部门
+   */
+  async add(deptName: string, parentDeptId: number) {
+    await this.getRepo().admin.sys.Department.insert({ name: deptName, parentId: parentDeptId === -1 ? undefined : parentDeptId });
+  }
+
+  /**
+   * 根据ID删除部门
+   */
+  async delete(departmentId: number) {
+    await this.getRepo().admin.sys.Department.delete(departmentId);
+  }
+
+  /**
+   * 根据部门查询关联的用户ID
+   */
+  async countUserByDeptId(id: number) {
+    return await this.getRepo().admin.sys.User.count({ departmentId: id });
+  }
+
+  /**
+   * 根据部门查询关联的角色
+   */
+  async countRoleByDeptId(id: number) {
+    return await this.getRepo().admin.sys.RoleDepartment.count({ departmentId: id });
+  }
+
+  /**
+   * 根据当前角色id获取部门列表
+   */
+  async getDepts(uid: number) {
+    const roleIds = await this.service.admin.sys.role.getRoleIdByUser(uid);
+    let depts: any = [];
+    if (_.includes(roleIds, this.config.rootRoleId)) {
+      // root find all
+      depts = await this.getRepo().admin.sys.Department.find();
+    } else {
+      // [ 1, 2, 3 ] role find
+      depts = await this.getRepo().admin.sys.Department.createQueryBuilder('dept')
+        .innerJoinAndSelect('sys_role_department', 'role_dept', 'dept.id = role_dept.department_id')
+        .andWhere('role_dept.role_id IN (:...roldIds)', { roldIds: roleIds })
+        .orderBy('dept.order_num', 'ASC')
+        .getMany();
+    }
+    return depts;
+  }
+
+}

+ 65 - 0
app/service/admin/sys/login_log.ts

@@ -0,0 +1,65 @@
+import BaseService from '../../base';
+import { UAParser } from 'ua-parser-js';
+
+/**
+ * 登录日志服务
+ */
+export default class SysLoginLogService extends BaseService {
+
+  /**
+   * 记录登录日志
+   */
+  async save(id: number) {
+    await this.getRepo().admin.sys.LoginLog.save({
+      ip: this.getHelper().getReqIP(),
+      userId: id,
+      ua: this.ctx.get('user-agent'),
+    });
+  }
+
+  /**
+   * 计算日志总数
+   */
+  async count() {
+    return await this.getRepo().admin.sys.LoginLog.count();
+  }
+
+  /**
+   * 分页加载日志信息
+   */
+  async page(page: number, count: number) {
+    // const result = await this.getRepo().admin.sys.LoginLog.find({
+    //   order: {
+    //     id: 'DESC',
+    //   },
+    //   take: count,
+    //   skip: page * count,
+    // });
+    const result = await this.getRepo().admin.sys.LoginLog.createQueryBuilder('login_log')
+      .innerJoinAndSelect('sys_user', 'user', 'login_log.user_id = user.id')
+      .orderBy('login_log.createTime', 'DESC')
+      .offset(page * count)
+      .limit(count)
+      .getRawMany();
+    const parser = new UAParser();
+    return result.map(e => {
+      const u = parser.setUA(e.login_log_ua).getResult();
+      return {
+        id: e.login_log_id,
+        ip: e.login_log_ip,
+        os: `${u.os.name} ${u.os.version}`,
+        browser: `${u.browser.name} ${u.browser.version}`,
+        time: e.login_log_createTime,
+        username: e.user_username,
+      };
+    });
+  }
+
+  /**
+   * 清空表中的所有数据
+   */
+  async clear() {
+    await this.getRepo().admin.sys.LoginLog.clear();
+  }
+
+}

+ 155 - 0
app/service/admin/sys/menu.ts

@@ -0,0 +1,155 @@
+import BaseService from '../../base';
+import * as _ from 'lodash';
+import { IsNull, Not } from 'typeorm';
+
+/**
+ * 系统-菜单
+ */
+export default class SysMenuService extends BaseService {
+
+  /**
+   * 获取所有菜单
+   */
+  async list() {
+    return await this.getRepo().admin.sys.Menu.find();
+  }
+
+  /**
+   * 保存或新增菜单
+   */
+  async save(menu: any) {
+    const result = await this.getRepo().admin.sys.Menu.save(menu);
+    return result;
+  }
+
+  /**
+   * 根据角色获取所有菜单
+   */
+  async getMenus(uid: number) {
+    const roleIds = await this.service.admin.sys.role.getRoleIdByUser(uid);
+    let menus: any = [];
+    if (_.includes(roleIds, this.config.rootRoleId)) {
+      // root find all
+      menus = await this.getRepo().admin.sys.Menu.find();
+    } else {
+      // [ 1, 2, 3 ] role find
+      menus = await this.getRepo().admin.sys.Menu.createQueryBuilder('menu')
+        .innerJoinAndSelect('sys_role_menu', 'role_menu', 'menu.id = role_menu.menu_id')
+        .andWhere('role_menu.role_id IN (:...roldIds)', { roldIds: roleIds })
+        .orderBy('menu.order_num', 'DESC')
+        .getMany();
+    }
+    return menus;
+  }
+
+  /**
+   * 查找当前菜单下的子菜单,目录以及菜单
+   */
+  async findChildMenus(mid: number) {
+    const allMenus: any = [];
+    const menus = await this.getRepo().admin.sys.Menu.find({ parentId: mid });
+    // if (_.isEmpty(menus)) {
+    //   return allMenus;
+    // }
+    // const childMenus: any = [];
+    for (let i = 0; i < menus.length; i++) {
+      if (menus[i].type !== 2) {
+        // 子目录下是菜单或目录,继续往下级查找
+        const c = await this.findChildMenus(menus[i].id);
+        allMenus.push(c);
+      }
+      allMenus.push(menus[i].id);
+    }
+    return allMenus;
+  }
+
+  /**
+   * 获取某个菜单的信息
+   * @param mid menu id
+   */
+  async getMenuItemInfo(mid: number) {
+    const menu = await this.getRepo().admin.sys.Menu.findOne({ id: mid });
+    return menu;
+  }
+
+  /**
+   * 获取某个菜单以及关联的父菜单的信息
+   */
+  async getMenuItemAndParentInfo(mid: number) {
+    const menu = await this.getRepo().admin.sys.Menu.findOne({ id: mid });
+    let parentMenu: any = null;
+    if (menu && menu.parentId) {
+      parentMenu = await this.getRepo().admin.sys.Menu.findOne({ id: menu.parentId });
+    }
+    return { menu, parentMenu };
+  }
+
+  /**
+   * 查找节点路由是否存在
+   */
+  async findRouterExist(router: string) {
+    const menus = await this.getRepo().admin.sys.Menu.findOne({ router });
+    return !_.isEmpty(menus);
+  }
+
+  /**
+   * 获取当前用户的所有权限
+   */
+  async getPerms(uid: number) {
+    const roleIds = await this.service.admin.sys.role.getRoleIdByUser(uid);
+    let perms: any[] = [];
+    let result: any = null;
+    if (_.includes(roleIds, this.config.rootRoleId)) {
+      // root find all perms
+      result = await this.getRepo().admin.sys.Menu.find({ perms: Not(IsNull()), type: 3 });
+    } else {
+      result = await this.getRepo().admin.sys.Menu.createQueryBuilder('menu')
+        .innerJoinAndSelect('sys_role_menu', 'role_menu', 'menu.id = role_menu.menu_id')
+        .andWhere('role_menu.role_id IN (:...roldIds)', { roldIds: roleIds })
+        .andWhere('menu.type = 3')
+        .andWhere('menu.perms IS NOT NULL')
+        .getMany();
+    }
+    if (!_.isEmpty(result)) {
+      result.forEach(e => {
+        perms = _.concat(perms, e.perms.split(','));
+      });
+      perms = _.uniq(perms);
+    }
+    return perms;
+  }
+
+  /**
+   * 删除多项菜单
+   */
+  async deleteMenuItem(mids: number[]) {
+    return await this.getRepo().admin.sys.Menu.delete(mids);
+  }
+
+  /**
+   * 刷新指定用户ID的权限
+   */
+  async refreshPerms(uid: number) {
+    const perms = await this.getPerms(uid);
+    const online = await this.getAdminRedis().get(`admin:token:${uid}`);
+    if (online) {
+      // 判断是否在线
+      await this.getAdminRedis().set(`admin:perms:${uid}`, JSON.stringify(perms));
+    }
+  }
+
+  /**
+   * 刷新所有在线用户的权限
+   */
+  async refreshOnlineUserPerms() {
+    const onlineUserIds: string[] = await this.getAdminRedis().keys('admin:token:*');
+    if (onlineUserIds && onlineUserIds.length > 0) {
+      for (let i = 0; i < onlineUserIds.length; i++) {
+        const uid = onlineUserIds[i].split('admin:token:')[1];
+        const perms = await this.getPerms(parseInt(uid));
+        await this.getAdminRedis().set(`admin:perms:${uid}`, JSON.stringify(perms));
+      }
+    }
+  }
+
+}

+ 52 - 0
app/service/admin/sys/online.ts

@@ -0,0 +1,52 @@
+import BaseService from '../../base';
+import { UAParser } from 'ua-parser-js';
+
+/**
+ * 在线用户Service
+ */
+export default class SysOnlineService extends BaseService {
+
+  async list() {
+    const onlineUserIds: string[] = await this.getAdminRedis().keys('admin:token:*');
+    const formatNumberIds: number[] = onlineUserIds.map(e => {
+      const uid = e.split('admin:token:')[1];
+      return parseInt(uid);
+    });
+    return await this.findLastLoginInfo(formatNumberIds);
+  }
+
+  /**
+   * 根据用户id列表查找最近登录信息和用户信息
+   */
+  async findLastLoginInfo(ids: number[]) {
+    const result = await this.ctx.ormManager.query(`
+    SELECT n.*, u.username
+      FROM sys_login_log n
+      INNER JOIN (
+        SELECT user_id, MAX(createTime) AS createTime
+        FROM sys_login_log GROUP BY user_id
+      ) AS max USING (user_id, createTime)
+      INNER JOIN sys_user u ON n.user_id = u.id
+      WHERE n.user_id IN (?)
+    `, [ ids ]);
+    if (result) {
+      const parser = new UAParser();
+      return result.map(e => {
+        const u = parser.setUA(e.ua).getResult();
+        return {
+          id: e.user_id,
+          ip: e.ip,
+          username: e.username,
+          isCurrent: this.ctx.token.uid === e.user_id,
+          time: e.createTime,
+          status: 1,
+          os: `${u.os.name} ${u.os.version}`,
+          browser: `${u.browser.name} ${u.browser.version}`,
+          disable: this.ctx.token.uid === e.user_id || e.user_id === this.config.rootRoleId,
+        };
+      });
+    }
+    return [];
+  }
+
+}

+ 76 - 0
app/service/admin/sys/req_log.ts

@@ -0,0 +1,76 @@
+import BaseService from '../../base';
+
+/**
+ * 请求追踪服务
+ */
+export default class SysReqLogService extends BaseService {
+
+  /**
+   * 记录日志
+   */
+  async save(url: string, params: string, status: number, consumeTime: number, method: string | undefined, userId: number | null) {
+    const ip = this.getHelper().getReqIP();
+    await this.getRepo().admin.sys.ReqLog.insert({
+      action: url,
+      params: JSON.stringify(params),
+      userId: userId === null ? undefined : userId,
+      ip,
+      method: method ? method.toUpperCase() : undefined,
+      status,
+      consumeTime,
+    });
+  }
+
+  /**
+   * 计算日志总数
+   */
+  async count() {
+    return await this.getRepo().admin.sys.ReqLog.count();
+  }
+
+  /**
+   * 分页加载日志信息
+   */
+  async page(page: number, count: number) {
+    const result = await this.getRepo().admin.sys.ReqLog.find({
+      order: {
+        id: 'DESC',
+      },
+      take: count,
+      skip: page * count,
+    });
+    return result;
+  }
+
+  /**
+   * 分页查询
+   */
+  async search(page: number, count: number, q: string) {
+    const allResult = await this.getRepo().admin.sys.ReqLog.createQueryBuilder('req_log')
+      .where(`req_log.userId LIKE '%${q}%'`)
+      .orWhere(`req_log.ip LIKE '%${q}%'`)
+      .orWhere(`req_log.action LIKE '%${q}%'`)
+      .orWhere(`req_log.params LIKE '%${q}%'`)
+      .getMany();
+    const result = await this.getRepo().admin.sys.ReqLog.createQueryBuilder('req_log')
+      .where(`req_log.userId LIKE '%${q}%'`)
+      .orWhere(`req_log.ip LIKE '%${q}%'`)
+      .orWhere(`req_log.action LIKE '%${q}%'`)
+      .orWhere(`req_log.params LIKE '%${q}%'`)
+      .skip(page * count)
+      .take(count)
+      .getMany();
+    return {
+      count: allResult.length,
+      logs: result,
+    };
+  }
+
+  /**
+   * 清空表中的所有数据
+   */
+  async clear() {
+    await this.getRepo().admin.sys.ReqLog.clear();
+  }
+
+}

+ 205 - 0
app/service/admin/sys/role.ts

@@ -0,0 +1,205 @@
+import BaseService from '../../base';
+import * as _ from 'lodash';
+import { In, Not } from 'typeorm';
+import { CreateRoleDto, UpdateRoleDto } from '../../../dto/admin/sys/role';
+
+/**
+ * 系统-角色
+ */
+export default class SysRoleService extends BaseService {
+
+  /**
+   * 列举所有角色:除去超级管理员
+   */
+  async list() {
+    const result = await this.getRepo().admin.sys.Role.find({ id: Not(this.config.rootRoleId) });
+    return result;
+  }
+
+  /**
+   * 列举所有角色条数:除去超级管理员
+   */
+  async count() {
+    const count = await this.getRepo().admin.sys.Role.count({ id: Not(this.config.rootRoleId) });
+    return count;
+  }
+
+  /**
+   * 根据角色获取角色信息
+   */
+  async info(rid: number) {
+    const roleInfo = await this.getRepo().admin.sys.Role.findOne({ id: rid });
+    const menus = await this.getRepo().admin.sys.RoleMenu.find({ roleId: rid });
+    const depts = await this.getRepo().admin.sys.RoleDepartment.find({ roleId: rid });
+    return { roleInfo, menus, depts };
+  }
+
+  /**
+   * 根据角色Id数组删除
+   */
+  async delete(roleIds: number[]) {
+    if (_.includes(roleIds, this.config.rootRoleId)) {
+      throw new Error('Not Support Delete Root');
+    }
+    await this.ctx.ormManager.transaction(async manager => {
+      await manager.delete(this.getEntity().admin.sys.Role, roleIds);
+      await manager.delete(this.getEntity().admin.sys.RoleMenu, { roleId: In(roleIds) });
+      await manager.delete(this.getEntity().admin.sys.RoleDepartment, { roleId: In(roleIds) });
+      // TODO:需要连同用户一并删除
+    });
+  }
+
+  /**
+   * 增加角色
+   */
+  async add(param: CreateRoleDto, uid: number) {
+    const { name, label, remark, menus, depts } = param;
+    const role = await this.getRepo().admin.sys.Role.insert({ name, label, remark, userId: String(uid) });
+    const { identifiers } = role;
+    const roleId = parseInt(identifiers[0].id);
+    if (menus && menus.length > 0) {
+      // 关联菜单
+      const insertRows = menus.map(m => {
+        return {
+          roleId,
+          menuId: m,
+        };
+      });
+      await this.getRepo().admin.sys.RoleMenu.insert(insertRows);
+    }
+    if (depts && depts.length > 0) {
+      // 关联部门
+      const insertRows = depts.map(d => {
+        return {
+          roleId,
+          departmentId: d,
+        };
+      });
+      await this.getRepo().admin.sys.RoleDepartment.insert(insertRows);
+    }
+    return { roleId };
+  }
+
+  /**
+   * 更新角色信息
+   */
+  async update(param: UpdateRoleDto) {
+    const { roleId, name, label, remark, menus, depts } = param;
+    const role = await this.getRepo().admin.sys.Role.save({ id: roleId, name, label, remark });
+    const originDeptRows = await this.getRepo().admin.sys.RoleDepartment.find({ roleId });
+    const originMenuRows = await this.getRepo().admin.sys.RoleMenu.find({ roleId });
+    const originMenuIds = originMenuRows.map(e => { return e.menuId; });
+    const originDeptIds = originDeptRows.map(e => { return e.departmentId; });
+    // 开始对比差异
+    const insertMenusRowIds = _.difference(menus, originMenuIds);
+    const deleteMenusRowIds = _.difference(originMenuIds, menus);
+    const insertDeptRowIds = _.difference(depts, originDeptIds);
+    const deleteDeptRowIds = _.difference(originDeptIds, depts);
+    // using transaction
+    await this.ctx.ormManager.transaction(async manager => {
+      // 菜单
+      if (insertMenusRowIds.length > 0) {
+        // 有条目更新
+        const insertRows = insertMenusRowIds.map(e => {
+          return {
+            roleId,
+            menuId: e,
+          };
+        });
+        await manager.insert(this.getEntity().admin.sys.RoleMenu, insertRows as any);
+      }
+      if (deleteMenusRowIds.length > 0) {
+        // 有条目需要删除
+        const realDeleteRowIds = _.filter(originMenuRows, e => {
+          return _.includes(deleteMenusRowIds, e.menuId);
+        }).map(e => { return (e as any).id; });
+        await manager.delete(this.getEntity().admin.sys.RoleMenu, realDeleteRowIds);
+      }
+      // 部门
+      if (insertDeptRowIds.length > 0) {
+        // 有条目更新
+        const insertRows = insertDeptRowIds.map(e => {
+          return {
+            roleId,
+            departmentId: e,
+          };
+        });
+        await manager.insert(this.getEntity().admin.sys.RoleDepartment, insertRows as any);
+      }
+      if (deleteDeptRowIds.length > 0) {
+        // 有条目需要删除
+        const realDeleteRowIds = _.filter(originDeptRows, e => {
+          return _.includes(deleteDeptRowIds, e.departmentId);
+        }).map(e => { return (e as any).id; });
+        await manager.delete(this.getEntity().admin.sys.RoleDepartment, realDeleteRowIds);
+      }
+    });
+    return role;
+  }
+
+  /**
+   * 分页加载角色信息
+   */
+  async page(page: number, count: number) {
+    const result = await this.getRepo().admin.sys.Role.find({
+      where: {
+        id: Not(this.config.rootRoleId),
+      },
+      order: {
+        id: 'ASC',
+      },
+      take: count,
+      skip: page * count,
+    });
+    return result;
+  }
+
+  /**
+   * 根据用户id查找角色信息
+   */
+  async getRoleIdByUser(id: number) {
+    const result = await this.getRepo().admin.sys.UserRole.find({
+      where: {
+        userId: id,
+      },
+    });
+    if (!_.isEmpty(result)) {
+      return _.map(result, v => {
+        return v.roleId;
+      });
+    }
+    return [];
+  }
+
+  /**
+   * 根据角色ID列表查找关联用户ID
+   */
+  async countUserIdByRole(ids: number[]) {
+    if (_.includes(ids, this.config.rootRoleId)) {
+      throw new Error('Not Support Delete Root');
+    }
+    return await this.getRepo().admin.sys.UserRole.count({ roleId: In(ids) });
+  }
+
+  /**
+   * 所有按钮
+   */
+  async role_menu() {
+    return await this.getRepo().admin.sys.Menu.find({
+      select: [ 'id', 'parentId', 'name', 'type' ],
+      order: { id: 'ASC' },
+    });
+  }
+
+  /**
+   * 通过roleId获取menu
+   */
+  async menu_by_roleid(roleId: number) {
+    const roleMenus = await this.getRepo().admin.sys.RoleMenu.find({
+      select: [ 'menuId' ],
+      where: { roleId },
+      order: { menuId: 'ASC' },
+    });
+    return roleMenus.map(item => item.menuId);
+  }
+}

+ 211 - 0
app/service/admin/sys/task.ts

@@ -0,0 +1,211 @@
+import BaseService from '../../base';
+import { CreateTaskDto, UpdateTaskDto } from '../../../dto/admin/sys/task';
+import SysTask from '../../../entities/admin/sys/task';
+
+/**
+ * 任务调度服务
+ */
+export default class SysTaskService extends BaseService {
+
+  /**
+   * 初始化任务,系统启动前调用
+   */
+  async initTask() {
+    const jobs = await this.app.queue.sys.getJobs([ 'active', 'delayed', 'failed', 'paused', 'waiting', 'completed' ]);
+    for (let i = 0; i < jobs.length; i++) {
+      // 先移除所有已存在的任务
+      await jobs[i].remove();
+    }
+    // 查找所有需要运行的任务
+    const tasks = await this.getRepo().admin.sys.Task.find({ status: 1 });
+    if (tasks && tasks.length > 0) {
+      for (const t of tasks) {
+        this.start(t);
+      }
+    }
+  }
+
+  /**
+   * 分页查询
+   */
+  async page(page: number, count: number) {
+    const result = await this.getRepo().admin.sys.Task.find({
+      order: {
+        id: 'ASC',
+      },
+      take: count,
+      skip: page * count,
+    });
+    return result;
+  }
+
+  /**
+   * count task
+   */
+  async count() {
+    return await this.getRepo().admin.sys.Task.count();
+  }
+
+  /**
+   * task info
+   */
+  async info(id: number) {
+    return await this.getRepo().admin.sys.Task.findOne({ id });
+  }
+
+  /**
+   * delete task
+   */
+  async delete(task: SysTask) {
+    if (!task) {
+      throw new Error('Task is Empty');
+    }
+    await this.stop(task);
+    await this.getRepo().admin.sys.Task.delete(task.id);
+  }
+
+  /**
+   * 添加任务
+   */
+  async addOrUpdate(param: CreateTaskDto | UpdateTaskDto) {
+    const result = await this.getRepo().admin.sys.Task.save(param);
+    const task = await this.info(result.id);
+    if (result.status === 0) {
+      await this.stop(task!);
+    } else if (result.status === 1) {
+      await this.start(task!);
+    }
+  }
+
+  /**
+   * 手动执行一次
+   */
+  async once(task: SysTask) {
+    if (task) {
+      await this.app.queue.sys.add({ id: task.id, service: task.service, args: task.data },
+        { jobId: task.id, removeOnComplete: true, removeOnFail: true });
+    } else {
+      throw new Error('Task is Empty');
+    }
+  }
+
+  /**
+   * 启动任务
+   */
+  async start(task: SysTask) {
+    if (!task) {
+      throw new Error('Task is Empty');
+    }
+    // 先停掉之前存在的任务
+    await this.stop(task);
+    let repeat: any;
+    if (task.type === 1) {
+      // 间隔 Repeat every millis (cron setting cannot be used together with this setting.)
+      repeat = {
+        every: task.every,
+      };
+    } else {
+      // cron
+      repeat = {
+        cron: task.cron,
+      };
+      // Start date when the repeat job should start repeating (only with cron).
+      if (task.startTime) {
+        repeat.startDate = task.startTime;
+      }
+      if (task.endTime) {
+        repeat.endDate = task.endTime;
+      }
+    }
+    if (task.limit > 0) {
+      repeat.limit = task.limit;
+    }
+    const job = await this.app.queue.sys.add({ id: task.id, service: task.service, args: task.data },
+      { jobId: task.id, removeOnComplete: true, removeOnFail: true, repeat });
+    if (job && job.opts) {
+      await this.getRepo().admin.sys.Task.update(task.id, { jobOpts: JSON.stringify(job.opts.repeat), status: 1 });
+    } else {
+      // update status to 0,标识暂停任务,因为启动失败
+      job && await job.remove();
+      await this.getRepo().admin.sys.Task.update(task.id, { status: 0 });
+      throw new Error('Task Start failed');
+    }
+  }
+
+  /**
+   * 停止任务
+   */
+  async stop(task: SysTask) {
+    if (!task) {
+      throw new Error('Task is Empty');
+    }
+    const exist = await this.existJob(task.id.toString());
+    if (!exist) {
+      await this.getRepo().admin.sys.Task.update(task.id, { status: 0 });
+      return;
+    }
+    const jobs = await this.app.queue.sys.getJobs([ 'active', 'delayed', 'failed', 'paused', 'waiting', 'completed' ]);
+    for (let i = 0; i < jobs.length; i++) {
+      if (jobs[i].data.id === task.id) {
+        await jobs[i].remove();
+      }
+    }
+    await this.getRepo().admin.sys.Task.update(task.id, { status: 0 });
+    // if (task.jobOpts) {
+    //   await this.app.queue.sys.removeRepeatable(JSON.parse(task.jobOpts));
+    //   // update status
+    //   await this.getRepo().admin.sys.Task.update(task.id, { status: 0 });
+    // }
+  }
+
+  /**
+   * 查看队列中任务是否存在
+   */
+  async existJob(jobId: string) {
+    const jobs = await this.app.queue.sys.getRepeatableJobs();
+    const ids = jobs.map(e => {
+      return e.id;
+    });
+    return ids.includes(jobId);
+  }
+
+  /**
+   * 更新任务完成状态
+   */
+  async updateTaskCompleteStatus(tid: number) {
+    const result = await this.app.queue.sys.getRepeatableJobs();
+    const task = await this.getRepo().admin.sys.Task.findOne({ id: tid });
+    const jobOpts = JSON.parse(task!.jobOpts);
+    // 如果下次执行时间小于当前时间,则表示已经执行完成。
+    for (const task of result) {
+      const currentTime = new Date().getTime();
+      if (task.id && task.id === tid.toString()
+        && (jobOpts.cron ? task.cron === jobOpts.cron : task.every === jobOpts.every) && task.next < currentTime) {
+        // 如果下次执行时间小于当前时间,则表示已经执行完成。
+        await this.getRepo().admin.sys.Task.update(tid, { status: 2 });
+      }
+    }
+  }
+
+  /**
+   * 根据serviceName调用service
+   */
+  async callService(serviceName: string, args: string) {
+    if (serviceName) {
+      let serviceTmp = this.service;
+      const arr = serviceName.split('.');
+      for (let i = 0; i < arr.length; i++) {
+        if (i === arr.length - 1) {
+          if (args) {
+            await serviceTmp[arr[arr.length - 1]](JSON.parse(args));
+          } else {
+            await serviceTmp[arr[arr.length - 1]]();
+          }
+        } else {
+          serviceTmp = serviceTmp[arr[i]];
+        }
+      }
+    }
+  }
+
+}

+ 71 - 0
app/service/admin/sys/task_log.ts

@@ -0,0 +1,71 @@
+import BaseService from '../../base';
+
+/**
+ * 任务调度服务
+ */
+export default class SysTaskService extends BaseService {
+
+  /**
+   * 记录任务日志
+   */
+  async record(tid: number, status: number) {
+    const result = await this.getRepo().admin.sys.TaskLog.save({
+      taskId: tid,
+      status,
+    });
+    return result.id;
+  }
+
+  async updateTaskStatus(id: number, status: number, detail?: string) {
+    this.getRepo().admin.sys.TaskLog.update(id, {
+      status,
+      detail,
+    });
+  }
+
+  /**
+   * 计算日志总数
+   */
+  async count() {
+    return await this.getRepo().admin.sys.TaskLog.count();
+  }
+
+  /**
+   * 分页加载日志信息
+   */
+  async page(page: number, count: number) {
+    // const result = await this.getRepo().admin.sys.TaskLog.find({
+    //   order: {
+    //     id: 'DESC',
+    //   },
+    //   take: count,
+    //   skip: page * count,
+    // });
+    // return result;
+    const result = await this.getRepo().admin.sys.TaskLog.createQueryBuilder('task_log')
+      .leftJoinAndSelect('sys_task', 'task', 'task_log.task_id = task.id')
+      .orderBy('task_log.id', 'DESC')
+      .offset(page * count)
+      .limit(count)
+      .getRawMany();
+    return result.map(e => {
+      return {
+        id: e.task_log_id,
+        taskId: e.task_id,
+        name: e.task_name,
+        createTime: e.task_log_createTime,
+        finishTime: e.task_log_updateTime,
+        detail: e.task_log_detail,
+        status: e.task_log_status,
+      };
+    });
+  }
+
+  /**
+   * 清空表中的所有数据
+   */
+  async clear() {
+    await this.getRepo().admin.sys.TaskLog.clear();
+  }
+
+}

+ 296 - 0
app/service/admin/sys/user.ts

@@ -0,0 +1,296 @@
+import * as _ from 'lodash';
+import BaseService from '../../base';
+import { Not, In } from 'typeorm';
+import { UpdatePersonInfoDto } from '../../../dto/admin/verify';
+import { CreateUserDto, UpdateUserDto } from '../../../dto/admin/sys/user';
+import { findIndex } from 'lodash';
+
+/**
+ * 系统-用户
+ */
+export default class SysUserService extends BaseService {
+  /**
+   * 查找超管的用户ID
+   */
+  async findRootUserId(): Promise<number> {
+    const result = await this.getRepo().admin.sys.User.findOne({ id: this.config.rootRoleId });
+    return result ? result.id : 0;
+  }
+  /**
+   * 查询用户个人信息
+   */
+  async person(uid: number) {
+    const user: any = await this.getRepo().admin.sys.User.findOne({ id: uid });
+    if (!_.isEmpty(user)) {
+      delete user.departmentId;
+      delete user.status;
+      // delete user.remark;
+      delete user.password;
+    }
+    return user;
+  }
+
+  /**
+   * 更新个人信息
+   */
+  async personUpdate(uid: number, param: UpdatePersonInfoDto) {
+    const { name, nickName, email, phone, originPassword, newPassword, remark, headImg } = param;
+    let savePassword: string | undefined;
+    if (originPassword && newPassword) {
+      const user = await this.getRepo().admin.sys.User.findOne({ id: uid });
+      const decodePassword = this.getHelper().aesDecrypt(user!.password, this.config.aesSecret.admin);
+      // const decodeOriginPassword = this.getHelper().aesDecrypt(originPassword, this.config.aesSecret.front);
+      // const decodeNewPassword = this.getHelper().aesDecrypt(newPassword, this.config.aesSecret.front);
+      const decodeOriginPassword = originPassword;
+      const decodeNewPassword = newPassword;
+      if (decodePassword === decodeOriginPassword) {
+        // 旧密码不一致
+        savePassword = this.getHelper().aesEncrypt(decodeNewPassword, this.config.aesSecret.admin);
+      } else {
+        return false;
+      }
+    }
+    const obj: any = { name, nickName, email, phone, remark, headImg };
+    if (savePassword) {
+      await this.service.admin.sys.user.upgradePasswordV(uid);
+      obj.password = savePassword;
+    }
+    await this.getRepo().admin.sys.User.update(uid, obj);
+    return true;
+  }
+
+  /**
+   * 增加系统用户,如果返回false则表示已存在该用户
+   * @param param Object 对应SysUser实体类
+   */
+  async add(param: CreateUserDto) {
+    // const insertData: any = { ...CreateUserDto };
+    const exists = await this.getRepo().admin.sys.User.findOne({ username: param.username });
+    if (!_.isEmpty(exists)) {
+      return false;
+    }
+    // 所有用户初始密码为123456
+    await this.ctx.ormManager.transaction(async manager => {
+      const password = this.getHelper().aesEncrypt('123456', this.config.aesSecret.admin);
+      const u = manager.create(this.getEntity().admin.sys.User, {
+        departmentId: param.departmentId,
+        username: param.username,
+        password,
+        name: param.name,
+        nickName: param.nickName,
+        email: param.email,
+        phone: param.phone,
+        remark: param.remark,
+        status: param.status,
+      });
+      const result = await manager.save(u);
+      const { roles } = param;
+      const insertRoles = roles.map(e => {
+        return {
+          roleId: e,
+          userId: result.id,
+        };
+      });
+      // 分配角色
+      await manager.insert(this.getEntity().admin.sys.UserRole, insertRoles);
+    });
+    return true;
+  }
+
+  /**
+   * 更新用户信息
+   */
+  async update(param: UpdateUserDto) {
+    await this.ctx.ormManager.transaction(async manager => {
+      await manager.update(this.getEntity().admin.sys.User, param.id, {
+        departmentId: param.departmentId,
+        username: param.username,
+        name: param.name,
+        nickName: param.nickName,
+        email: param.email,
+        phone: param.phone,
+        remark: param.remark,
+        status: param.status,
+      });
+      // 先删除原来的角色关系
+      await manager.delete(this.getEntity().admin.sys.UserRole, { userId: param.id });
+      const insertRoles = param.roles.map(e => {
+        return {
+          roleId: e,
+          userId: param.id,
+        };
+      });
+      // 重新分配角色
+      await manager.insert(this.getEntity().admin.sys.UserRole, insertRoles);
+      if (param.status === 0) {
+        // 禁用状态
+        await this.forbidden(param.id);
+      }
+    });
+  }
+
+  /**
+   * 查找用户信息
+   * @param id 用户id
+   */
+  async info(id: number) {
+    const user: any = await this.getRepo().admin.sys.User.findOne(id);
+    if (_.isEmpty(user)) {
+      throw new Error('unfind this user info');
+    }
+    const departmentRow = await this.getRepo().admin.sys.Department.findOne({ id: user!.departmentId });
+    if (_.isEmpty(departmentRow)) {
+      throw new Error('unfind this user info');
+    }
+    const roleRows = await this.getRepo().admin.sys.UserRole.find({ userId: user!.id });
+    const roles = roleRows.map(e => {
+      return e.roleId;
+    });
+    delete user!.password;
+    return { ...user, roles, departmentName: departmentRow!.name };
+  }
+
+  /**
+   * 查找列表里的信息
+   */
+  async infoList(ids: number[]) {
+    const users = await this.getRepo().admin.sys.User.findByIds(ids);
+    return users;
+  }
+
+  /**
+   * 根据ID列表删除用户
+   */
+  async delete(userIds: number[]) {
+    await this.getRepo().admin.sys.User.delete(userIds);
+    await this.getRepo().admin.sys.UserRole.delete({ userId: In(userIds) });
+  }
+
+  /**
+   * 根据部门ID列举用户条数:除去超级管理员
+   */
+  async count(uid: number, deptId: number) {
+    const rootUserId = await this.findRootUserId();
+    if (deptId === -1) {
+      return await this.getRepo().admin.sys.User.count({ id: Not(In([ rootUserId, uid ])) });
+    }
+    return await this.getRepo().admin.sys.User.count({ id: Not(In([ rootUserId, uid ])), departmentId: deptId });
+  }
+
+  /**
+   * 根据部门ID进行分页查询用户列表
+   * deptId = -1 时查询全部
+   */
+  async page(uid: number, deptId: number, page: number, count: number) {
+    const rootUserId = await this.findRootUserId();
+    const result = await this.getRepo().admin.sys.User.createQueryBuilder('user')
+      .innerJoinAndSelect('sys_department', 'dept', 'dept.id = user.departmentId')
+      .innerJoinAndSelect(
+        'sys_user_role',
+        'user_role',
+        'user_role.user_id = user.id',
+      )
+      .innerJoinAndSelect('sys_role', 'role', 'role.id = user_role.role_id')
+      .where('user.id NOT IN (:...ids)', { ids: [ rootUserId, uid ] })
+      .andWhere(deptId === -1 ? '1 = 1' : `user.departmentId = '${deptId}'`)
+      .offset(page * count)
+      .limit(count)
+      .getRawMany();
+    // const dealResult = result.map(e => {
+    //   return {
+    //     createTime: e.user_createTime,
+    //     departmentId: e.user_department_id,
+    //     email: e.user_email,
+    //     headImg: e.user_head_img,
+    //     id: e.user_id,
+    //     name: e.user_name,
+    //     nickName: e.user_nick_name,
+    //     phone: e.user_phone,
+    //     remark: e.user_remark,
+    //     status: e.user_status,
+    //     updateTime: e.user_updateTime,
+    //     username: e.user_username,
+    //     departmentName: e.dept_name,
+    //   };
+    // });
+    const dealResult:any[] = [];
+    // 过滤去重
+    result.forEach(e => {
+      const index = findIndex(dealResult, e2 => e2.id === e.user_id);
+      if (index < 0) {
+        // 当前元素不存在则插入
+        dealResult.push({
+          createTime: e.user_createTime,
+          departmentId: e.user_department_id,
+          email: e.user_email,
+          headImg: e.user_head_img,
+          id: e.user_id,
+          name: e.user_name,
+          nickName: e.user_nick_name,
+          phone: e.user_phone,
+          remark: e.user_remark,
+          status: e.user_status,
+          updateTime: e.user_updateTime,
+          username: e.user_username,
+          departmentName: e.dept_name,
+          roleNames: [ e.role_name ],
+        });
+      } else {
+        // 已存在
+        dealResult[index].roleNames.push(e.role_name);
+      }
+    });
+    // const result = await this.getRepo().admin.sys.User.find({
+    //   where: {
+    //     username: Not('root'),
+    //     departmentId: deptId,
+    //   },
+    //   order: {
+    //     id: 'ASC',
+    //   },
+    //   take: count,
+    //   skip: page * count,
+    // });
+    return dealResult;
+  }
+
+  /**
+   * 禁用用户
+   */
+  async forbidden(uid: number) {
+    await this.getAdminRedis().del(`admin:passwordVersion:${uid}`);
+    await this.getAdminRedis().del(`admin:token:${uid}`);
+    await this.getAdminRedis().del(`admin:perms:${uid}`);
+  }
+
+  /**
+   * 禁用多个用户
+   */
+  async multiForbidden(uids: number[]) {
+    if (uids) {
+      const pvs: string[] = [];
+      const ts: string[] = [];
+      const ps: string[] = [];
+      uids.forEach(e => {
+        pvs.push(`admin:passwordVersion:${e}`);
+        ts.push(`admin:token:${e}`);
+        ps.push(`admin:perms:${e}`);
+      });
+      await this.getAdminRedis().del(pvs);
+      await this.getAdminRedis().del(ts);
+      await this.getAdminRedis().del(ps);
+    }
+  }
+
+  /**
+   * 升级用户版本密码
+   */
+  async upgradePasswordV(id: number) {
+    // admin:passwordVersion:${param.id}
+    const v = await this.getAdminRedis().get(`admin:passwordVersion:${id}`);
+    if (!_.isEmpty(v)) {
+      await this.getAdminRedis().set(`admin:passwordVersion:${id}`, parseInt(v!) + 1);
+    }
+  }
+
+}

+ 23 - 0
app/service/admin/tg/tgbase.ts

@@ -0,0 +1,23 @@
+import BaseService from '../../base';
+
+export default class TgBaseService extends BaseService {
+  async reqTgbotApi(cmd:string, data:any) {
+    // 生成一个唯一的请求 ID
+    const timeout = 1000; // 超时时间,单位:毫秒
+    const reqId = Math.random().toString(36).slice(2);
+    this.ctx.app.messenger.sendToApp('req-tgboot-api', { reqId, cmd, data });
+    // 等待 worker 返回结果
+    return await new Promise(resolve => {
+      const timeoutId = setTimeout(() => {
+        console.error('Request timeout: Tgboot did not respond in time');
+        // 挂了 告诉代理重新选一个进程暂时 后边搞到后台
+        this.ctx.app.messenger.sendToAgent('reset-tgboot-api', {});
+        resolve(null);
+      }, timeout);
+      this.ctx.app.messenger.once(`resp-tgboot-api-${reqId}`, response => {
+        clearTimeout(timeoutId); // 清除超时定时器
+        resolve(response);
+      });
+    });
+  }
+}

+ 72 - 0
app/service/admin/tg/tgbot.ts

@@ -0,0 +1,72 @@
+import TgBaseService from './tgbase';
+
+/**
+ * tg机器人
+ */
+export default class TgbotService extends TgBaseService {
+
+  async tgTest() {
+    return await this.reqTgbotApi('test', {});
+  }
+
+  /**
+   * bot list
+   */
+  async list() {
+    return await this.getRepo().admin.tg.TgBot.find();
+  }
+
+  /**
+   * bot count
+   */
+  async count() {
+    return await this.getRepo().admin.tg.TgBot.count();
+  }
+  /**
+   * 分页加载bot
+   */
+  async page(page: number, count: number) {
+    return await this.getRepo().admin.tg.TgBot.find({
+      order: {
+        id: 'ASC',
+      },
+      take: count,
+      skip: page * count,
+    });
+  }
+
+  /**
+   * bot add
+   */
+  async add(param:any) {
+    const { botId, username, name, token, status, description } = param;
+    await this.getRepo().admin.tg.TgBot.insert({ botId, username, name, token, status, description });
+    // 通知api进程创建bot监听
+    if (status) await this.reqTgbotApi('addTgBot', botId);
+    else await this.reqTgbotApi('removeTgBot', botId);
+    return { botId };
+  }
+  /**
+   * bot update
+   */
+  async update(param:any) {
+    const { id, botId, username, name, token, status, description } = param;
+    const bot = await this.getRepo().admin.tg.TgBot.save({ id, botId, username, name, token, status, description });
+    // 通知api进程创建bot监听
+    if (status) await this.reqTgbotApi('addTgBot', botId);
+    else await this.reqTgbotApi('removeTgBot', botId);
+    return bot;
+  }
+
+  /**
+   * bot del
+   */
+  async delete(id:number) {
+    const bot = await this.getRepo().admin.tg.TgBot.findOne({ id });
+    if (bot != null) {
+      await this.getRepo().admin.tg.TgBot.delete(id);
+      await this.reqTgbotApi('removeTgBot', bot.botId);
+    }
+    return id;
+  }
+}

+ 157 - 0
app/service/admin/tg/tgbot_api.ts

@@ -0,0 +1,157 @@
+import BaseService from '../../base';
+import TelegramBot = require('node-telegram-bot-api');
+import TgGroupType from './tggrouptype';
+
+/**
+ * tg机器人 api 服务 只有一个进程可以用
+ */
+const botListMap = new Map<number, TelegramBot>();
+export default class TgbotApiService extends BaseService {
+  /**
+   * 机器人进程初始化
+   */
+  async initTgbot() {
+    await this.initTgBotList();
+    return true;
+  }
+
+  /**
+   * 进程通讯
+   * @param cmd
+   * @param data
+   */
+  async handleReq(cmd:string, data:any) {
+    switch (cmd) {
+      case 'addTgBot':
+        await this.addTgBot(data);
+        break;
+      case 'removeTgBot':
+        await this.removeTgBot(data);
+        break;
+      case 'sendMessage':
+        await this.sendMessage(data);
+        break;
+      default:
+        break;
+    }
+    return true;
+  }
+
+  /**
+   * 初始化机器人监听
+   */
+  async initTgBotList() {
+    const list = await this.getRepo().admin.tg.TgBot.find({ status: 1 });
+    list.forEach(tgbot => {
+      this.addTgBot(tgbot.botId, tgbot);
+    });
+  }
+
+  /**
+   * 添加机器人监听
+   * @param botId
+   * @param botInfo
+   */
+  async addTgBot(botId:number, botInfo:any = null) {
+    if (botListMap.has(botId)) {
+      return false;
+    }
+    if (botInfo == null) {
+      botInfo = await this.getRepo().admin.tg.TgBot.findOne({ botId });
+    }
+    if (botInfo == null || botInfo.status === 0) return false;
+    const bot = new TelegramBot(botInfo.token, { polling: true });
+    botListMap.set(botId, bot);
+    bot.on('message', msg => {
+      this.onReciveMessage(botId, msg);
+    });
+    return true;
+  }
+
+  /**
+   * 移除机器人监听
+   * @param botId
+   */
+  async removeTgBot(botId:number) {
+    if (!botListMap.has(botId)) {
+      return false;
+    }
+    const bot = botListMap.get(botId);
+    await bot.stopPolling();
+    botListMap.delete(botId);
+    return true;
+  }
+
+  /**
+   * 解析msg
+   * @param botId
+   * @param msg
+   */
+  // {"message_id":83,"from":{"id":5034446407,"is_bot":false,"first_name":"xdh","username":"hdx234","language_code":"zh-hans"},"chat":{"id":-4870165498,"title":"z & xdh","type":"group","all_members_are_administrators":true,"accepted_gift_types":{"unlimited_gifts":false,"limited_gifts":false,"unique_gifts":false,"premium_subscription":false}},"date":1752997603,"text":"123452"}
+  // {"message_id":85,"from":{"id":5034446407,"is_bot":false,"first_name":"xdh","username":"hdx234","language_code":"zh-hans"},"chat":{"id":5034446407,"first_name":"xdh","username":"hdx234","type":"private"},"date":1753021095,"text":"11111"}
+  parseMessage(msg:any) {
+    if (msg == null) return null;
+    const { message_id, from, chat, date, text } = msg;
+    return {
+      messageId: message_id,
+      chatId: chat?.id,
+      chatType: chat?.type,
+      chatTitle: chat?.title,
+      chatName: chat?.first_name,
+      chatUsername: chat?.username,
+      fromUserId: from?.id,
+      fromName: from?.first_name,
+      fromUsername: from?.username,
+      isBot: from && from.is_bot ? 1 : 0,
+      sentTime: date,
+      text,
+    };
+  }
+  async recordMessage(botId:number, message:any) {
+    try {
+      const data = Object.assign({}, message, { botId });
+      await this.getRepo().admin.tg.TgMessage.save(data);
+    } catch (error) {
+      console.error(error);
+    }
+  }
+  async recordSendMessage(groupId:any, botId:any, text:any) {
+    try {
+      await this.getRepo().admin.tg.TgBotSendLog.save({ groupId, botId, text });
+    } catch (error) {
+      console.error(error);
+    }
+  }
+  /**
+   * 监听消息处理
+   * @param botId
+   * @param msg
+   */
+  async onReciveMessage(botId:number, msg:any) {
+    const message = this.parseMessage(msg);
+    const group = await this.getRepo().admin.tg.TgGroup.findOne({ groupId: message?.chatId });
+    const groupType = group ? group.type : TgGroupType.LISTEN_ONLY;
+    // 是否记录
+    if (groupType === TgGroupType.LISTEN_ONLY || groupType === TgGroupType.BOTH) {
+      await this.recordMessage(botId, message);
+      await this.service.admin.tg.tgmsghandler.handleMessage(group, message);
+    }
+  }
+  async sendMessage(message:any[]) {
+    if (message == null) return false;
+    try {
+      for (const value of message) {
+        const { groupId, botId, text } = value;
+        if (botListMap.has(botId)) {
+          const bot = botListMap.get(botId);
+          bot.sendMessage(groupId, text);
+          await this.recordSendMessage(groupId, botId, text);
+        }
+      }
+    } catch (error) {
+      console.error(error);
+      return false;
+    }
+    return true;
+  }
+}

+ 159 - 0
app/service/admin/tg/tggroup.ts

@@ -0,0 +1,159 @@
+import TgBaseService from './tgbase';
+import * as _ from 'lodash';
+import { In } from 'typeorm';
+import TgGroupType from './tggrouptype';
+
+export default class TggroupService extends TgBaseService {
+  /**
+   * group list
+   */
+  async list() {
+    return await this.getRepo().admin.tg.TgGroup.find();
+  }
+  /**
+   * bot count
+   */
+  async count() {
+    return await this.getRepo().admin.tg.TgGroup.count();
+  }
+  /**
+   * 分页加载bot
+   */
+  async page(page: number, count: number) {
+    return await this.getRepo().admin.tg.TgGroup.find({
+      relations: [ 'tgGroupBots' ],
+      order: {
+        id: 'ASC',
+      },
+      take: count,
+      skip: page * count,
+    });
+  }
+  /**
+   * bot add
+   */
+  async add(param:any) {
+    const { name, username, groupId, description, type, defaultBotId, status, tgGroupBots } = param;
+    const bots = tgGroupBots;
+    await this.getRepo().admin.tg.TgGroup.insert({ name, username, groupId, description, type, defaultBotId, status });
+    if (bots && bots.length > 0 && groupId != null) {
+      // 关联 机器人
+      const insertRows = bots.map((bid: any) => {
+        return {
+          groupId,
+          botId: bid,
+        };
+      });
+      await this.getRepo().admin.tg.TgGroupBot.insert(insertRows);
+    }
+    return { groupId };
+  }
+  async update(param:any) {
+    const { id, name, username, groupId, description, type, defaultBotId, status, tgGroupBots } = param;
+    const bots = tgGroupBots;
+    const group = await this.getRepo().admin.tg.TgGroup.save({ id, name, username, groupId, description, type, defaultBotId, status });
+    const oribots = await this.getRepo().admin.tg.TgGroupBot.find({ groupId });
+    const oribotIds = oribots.map(e => { return e.botId; });
+    // 开始对比差异
+    const insertIds = _.difference(bots, oribotIds);
+    const deleteIds = _.difference(oribotIds, bots);
+    // using transaction
+    if (insertIds.length || deleteIds.length) {
+      await this.ctx.ormManager.transaction(async manager => {
+        if (insertIds.length > 0) {
+          // 有条目更新
+          const insertRows = insertIds.map(bid => {
+            return {
+              groupId,
+              botId: bid,
+            };
+          });
+          await manager.insert(this.getEntity().admin.tg.TgGroupBot, insertRows as any);
+        }
+        if (oribots.length > 0) {
+          // 有条目需要删除
+          const realDeleteRowIds = _.filter(oribots, e => {
+            return _.includes(deleteIds, e.botId);
+          }).map(e => { return (e as any).id; });
+          await manager.delete(this.getEntity().admin.tg.TgGroupBot, realDeleteRowIds);
+        }
+      });
+    }
+    return group;
+  }
+  /**
+   * 根据数组删除
+   */
+  async delete(ids: number[]) {
+    await this.getRepo().admin.tg.TgGroup.delete(ids);
+    return ids;
+  }
+
+  /**
+   * 获取消息内的所有group信息,因为无法直接获取groupid 所以需要先拉机器人进去监听chatId
+   */
+  async msg_groupIds() {
+    return await this.getRepo().admin.tg.TgMessage.createQueryBuilder('msg')
+      .select([
+        'msg.chatId AS msg_chatId',
+        'ANY_VALUE(msg.chatTitle) AS msg_chatTitle', // 使用 ANY_VALUE() 函数
+      ])
+      .where('msg.chatType = :type', { type: 'group' })
+      .groupBy('msg.chatId') // 只需要按 chatId 分组
+      .getRawMany();
+  }
+
+  /**
+   * 发送多组消息
+   */
+  async send_groups_msg(groupIds:any[], message:string) {
+    const groups = await this.getRepo().admin.tg.TgGroup.find({
+      where: {
+        groupId: In(groupIds),
+      },
+    });
+    const success:any[] = [];
+    const sendList:any[] = [];
+    if (groups != null) {
+      for (const group of groups) {
+        const { groupId, defaultBotId, name , type} = group;
+        if (defaultBotId != null && type !== TgGroupType.LISTEN_ONLY) {
+          sendList.push({ groupId, botId: defaultBotId, name, text: message });
+        }
+      }
+    }
+    const res = await this.reqTgbotApi('sendMessage', sendList);
+    if (res) {
+      sendList.forEach(value => {
+        success.push({
+          groupId: value.groupId,
+          botId: value.botId,
+          name: value.name,
+          success: true,
+        });
+      });
+    }
+    return success;
+  }
+
+  /**
+   * 单群单机器人
+   */
+  async send_msg(groupId:any, botId:any, message:string) {
+    const group = await this.getRepo().admin.tg.TgGroup.findOne({ groupId });
+    let success:any = false;
+    if (group != null && group.type !== TgGroupType.LISTEN_ONLY) {
+      success = await this.reqTgbotApi('sendMessage', [{ groupId, botId, text: message }]);
+    }
+    return { groupId, botId, name: group?.name, success };
+  }
+  async all_botlist() {
+    return await this.getRepo().admin.tg.TgBot.find({
+      select: [ 'id', 'botId', 'name' ],
+      where: { status: 1 },
+      order: {
+        id: 'ASC',
+      },
+    });
+  }
+}

+ 9 - 0
app/service/admin/tg/tggrouptype.ts

@@ -0,0 +1,9 @@
+enum TgGroupType {
+  // 只听
+  LISTEN_ONLY,
+  // 只发
+  SEND_ONLY,
+  // 又听又发
+  BOTH,
+}
+export default TgGroupType;

+ 65 - 0
app/service/admin/tg/tglog.ts

@@ -0,0 +1,65 @@
+import BaseService from '../../base';
+
+export default class Tglog extends BaseService {
+  /**
+   * 分页加载日志信息
+   */
+  async send_page(page: number, count: number) {
+    // return await this.getRepo().admin.tg.TgBotSendLog.find({
+    //   order: {
+    //     id: 'DESC',
+    //   },
+    //   take: count,
+    //   skip: page * count,
+    // });
+    return await this.getRepo().admin.tg.TgBotSendLog.createQueryBuilder('log')
+      .leftJoinAndSelect('tg_bot', 'bot', 'bot.botId = log.botId')
+      .leftJoinAndSelect('tg_group', 'group', 'group.groupId = log.groupId')
+      .select([
+        'log.botId',
+        'log.groupId',
+        'log.text',
+        'log.createTime',
+        'log.updateTime',
+        'bot.name',
+        'group.name',
+      ])
+      .orderBy('log.id', 'DESC')
+      .skip((page) * count)
+      .take(count)
+      .getRawMany();
+  }
+  async receive_page(page: number, count: number) {
+    // return await this.getRepo().admin.tg.TgMessage.find({
+    //   order: {
+    //     id: 'DESC',
+    //   },
+    //   take: count,
+    //   skip: page * count,
+    // });
+    return await this.getRepo().admin.tg.TgMessage.createQueryBuilder('msg')
+      .leftJoinAndSelect('tg_bot', 'bot', 'bot.botId = msg.botId')
+      .select([
+        'msg.botId',
+        'msg.chatId',
+        'msg.chatTitle',
+        'msg.fromUserId',
+        'msg.fromName',
+        'msg.text',
+        'msg.sentTime',
+        'msg.createTime',
+        'msg.updateTime',
+        'bot.name',
+      ])
+      .orderBy('msg.id', 'DESC')
+      .skip((page) * count)
+      .take(count)
+      .getRawMany();
+  }
+  async receive_count() {
+    return await this.getRepo().admin.tg.TgMessage.count();
+  }
+  async send_count() {
+    return await this.getRepo().admin.tg.TgBotSendLog.count();
+  }
+}

+ 12 - 0
app/service/admin/tg/tgmsghandler.ts

@@ -0,0 +1,12 @@
+import BaseService from '../../base';
+
+export default class TgMsgHandler extends BaseService {
+  /**
+   * 消息 处理服务
+   * @param group 没绑定未空
+   * @param message
+   */
+  async handleMessage(group, message) {
+    console.log('handleMessage:', group?.groupId, message?.messageId);
+  }
+}

+ 31 - 0
app/service/base.ts

@@ -0,0 +1,31 @@
+import { Service } from 'egg';
+export default class BaseService extends Service {
+
+  /**
+   * 所有实体会加载在ctx.entities中, 所有仓库会加载到ctx.repo;
+   * 多数据库时加载在对应的ctx.entities[connectName]与ctx.repo[connectionName]上;
+   * 详见typings/typeorm.d.ts文件
+   */
+  getRepo() {
+    return this.ctx.repo;
+  }
+
+  getEntity() {
+    return this.ctx.entity;
+  }
+
+  /**
+   * 获取Helper
+   */
+  getHelper() {
+    return this.ctx.helper;
+  }
+
+  /**
+   * admin redis
+   */
+  getAdminRedis() {
+    return this.app.redis.get('admin') as any;
+  }
+
+}

+ 33 - 0
app/service/reptile/book.ts

@@ -0,0 +1,33 @@
+import BaseService from '../base';
+
+/**
+ * 小说爬虫Service
+ */
+export default class ReptileBookService extends BaseService {
+
+  /**
+   * 获取纵横书籍最新章节
+   */
+  async getZonghengBookLastchapter(param: any) {
+    // eslint-disable-next-line @typescript-eslint/no-var-requires
+    const puppeteer = require('puppeteer-core');
+    // eslint-disable-next-line @typescript-eslint/no-var-requires
+    const cheerio = require('cheerio');
+    const browser = await puppeteer.launch(this.config.puppeteer.launchOptions);
+    const page = await browser.newPage();
+    await page.goto(`http://book.zongheng.com/book/${param.id}.html`, { waitUntil: 'networkidle0' });
+    const content = await page.content();
+    const $ = cheerio.load(content);
+    const chapter = $('.book-html-box .book-top .book-main .book-new-chapter .tit').text();
+    const bookname = $('.book-html-box .book-top .book-main .book-info .book-name').text().trim();
+    await this.service.admin.comm.email.sendEmail({
+      from: 'noreply@mail.si-yee.com',
+      text: `${bookname} 最新章节 : ${chapter}`,
+      to: param.emails,
+      subject: '小说最新章节',
+      html: `<h2>${bookname} 最新章节 : ${chapter}</h2>`,
+    });
+    await browser.close();
+  }
+
+}

+ 14 - 0
appveyor.yml

@@ -0,0 +1,14 @@
+environment:
+  matrix:
+    - nodejs_version: '8'
+
+install:
+  - ps: Install-Product node $env:nodejs_version
+  - npm i npminstall@5 && node_modules\.bin\npminstall
+
+test_script:
+  - node --version
+  - npm --version
+  - npm run test
+
+build: off

+ 117 - 0
config/config.default.ts

@@ -0,0 +1,117 @@
+import { EggAppConfig, EggAppInfo, PowerPartial } from 'egg';
+import * as path from 'path';
+import * as fs from 'fs';
+
+export default (appInfo: EggAppInfo) => {
+  const config = {} as PowerPartial<EggAppConfig>;
+
+  // override config from framework / plugin
+  // use for cookie sign key, should change to your own and keep security
+  config.keys = appInfo.name + '_1597893009804_8539';
+
+  // 配置网站图标 可为网络图标
+  config.siteFile = {
+    '/favicon.ico': fs.readFileSync(path.join(__dirname, '../favicon.ico')),
+  };
+
+  // add your egg config in here
+  config.middleware = [ 'adminReqLog', 'execption', 'adminAuthority' ];
+
+  config.aesSecret = {
+    admin: 'hXuLvp6zmhahtW1kf21DpYxm',
+    front: 'eECRYHR5Er93BijVlkMz9CIn',
+  };
+
+  config.jwt = {
+    secret: 'INnyQ50BEE6AITQraIaDGooJ',
+  };
+
+  // Root角色对应ID
+  config.rootRoleId = 1;
+
+  /**
+   * 框架内置了国际化(I18n)支持,由 egg-i18n 插件提供。
+   * 更多配置参考 @{Link:https://github.com/eggjs/egg-i18n}
+   */
+  config.i18n = {
+    defaultLocale: 'zh-CN',
+  };
+
+  // add your special config in here
+  const bizConfig = {
+    sourceUrl: `https://github.com/eggjs/examples/tree/master/${appInfo.name}`,
+  };
+
+  // https://eggjs.org/zh-cn/core/security.html
+  config.security = {
+    // 配合egg-cors使用
+    // domainWhiteList: [ 'http://localhost:7003' ],
+    csrf: {
+      // 默认为 false,当设置为 true 时,将会放过所有 content-type 为 `application/json` 的请求
+      enable: false,
+      ignoreJSON: true,
+    },
+  };
+
+  /**
+   * egg-global-header
+   * https://github.com/eggjs/egg-global-header
+   */
+  config.globalHeader = {
+    'Powered-by': 'sf-admin',
+  };
+
+  /**
+   * CORS
+   * https://github.com/eggjs/egg-cors
+   */
+  config.cors = {
+    // origin: '*',
+    allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH',
+  };
+
+  // 模板渲染配置
+  config.view = {
+    defaultViewEngine: 'nunjucks',
+  };
+
+  // static config
+  config.static = {
+    prefix: '/static/',
+    dir: path.join(appInfo.baseDir, 'public'),
+    dynamic: true,
+    preload: false,
+    // maxAge: 31536000,
+    maxAge: 0,
+    buffer: false,
+  };
+
+  /**
+   * https://eggjs.org/zh-cn/basics/controller.html#获取上传的文件
+   */
+  config.multipart = {
+    mode: 'file',
+  };
+
+  // https://eggjs.org/zh-cn/core/error-handling.html
+  config.onerror = {
+    // all(err: any, ctx: any) {
+    //   // 在此处定义针对所有响应类型的错误处理方法
+    //   // 注意,定义了 config.all 之后,其他错误处理方法不会再生效
+    //   ctx.set('Content-Type', 'application/json');
+    //   // 生产环境时 500 错误的详细错误内容不返回给客户端,因为可能包含敏感信息
+    //   const status = err.status || 500;
+    //   const message = status === 500 && ctx.app.config.env === 'prod' ? 'Server internal exception, please try again later' : err.message;
+    //   ctx.body = JSON.stringify({
+    //     errorCode: err.errorCode || 500,
+    //     message,
+    //   });
+    // },
+  };
+
+  // the return config will combines to EggAppConfig
+  return {
+    ...config,
+    ...bizConfig,
+  };
+};

+ 28 - 0
config/error_constants.yaml

@@ -0,0 +1,28 @@
+# 业务代码错误信息配置 
+# docs: https://github.com/nodeca/js-yaml
+
+# 10000 - 99999 业务操作错误
+10000: 参数校验异常
+10001: 系统用户已存在
+10002: 填写验证码有误
+10003: 用户名密码有误
+10004: 节点路由已存在
+10005: 权限必须包含父节点
+10006: 非法操作:该节点仅支持目录类型父节点
+10007: 非法操作:节点类型无法直接转换
+10008: 该角色存在关联用户,请先删除关联用户
+10009: 该部门存在关联用户,请先删除关联用户
+10010: 该部门存在关联角色,请先删除关联角色
+10011: 旧密码与原密码不一致
+10012: 如想下线自身可右上角退出
+10013: 不允许下线该用户
+
+# token相关
+11001: 登录无效或无权限访问
+11002: 登录身份已过期
+11003: 无权限,请联系管理员申请权限
+
+# OSS相关
+20001: 文件上传失败
+20002: 请先选择需要上传的图片空间类型
+20003: 当前分类下存在图片,请先删除图片

+ 31 - 0
config/plugin.ts

@@ -0,0 +1,31 @@
+import { EggPlugin } from 'egg';
+
+const plugin: EggPlugin = {
+  // static: true,
+  // nunjucks: {
+  //   enable: true,
+  //   package: 'egg-view-nunjucks',
+  // },
+  typeorm: {
+    enable: true,
+    package: '@hackycy/egg-typeorm',
+  },
+  classValidator: {
+    enable: true,
+    package: '@hackycy/egg-class-validator',
+  },
+  bull: {
+    enable: true,
+    package: '@hackycy/egg-bull',
+  },
+  redis: {
+    enable: true,
+    package: 'egg-redis',
+  },
+  globalHeader: {
+    enable: true,
+    package: 'egg-global-header',
+  },
+};
+
+export default plugin;

+ 685 - 0
db/init.sql

@@ -0,0 +1,685 @@
+# ************************************************************
+# Sequel Ace SQL dump
+# Version 2070
+#
+# https://sequel-ace.com/
+# https://github.com/Sequel-Ace/Sequel-Ace
+#
+# Host: 127.0.0.1 (MySQL 8.0.24)
+# Database: tgbot
+# Generation Time: 2025-07-23 02:32:20 +0000
+# ************************************************************
+
+
+/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
+/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
+/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
+/*!40101 SET NAMES utf8 */;
+SET NAMES utf8mb4;
+/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
+/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
+/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
+
+
+# Dump of table image_space_info
+# ------------------------------------------------------------
+
+DROP TABLE IF EXISTS `image_space_info`;
+
+CREATE TABLE `image_space_info` (
+  `createTime` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+  `updateTime` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+  `id` int NOT NULL AUTO_INCREMENT,
+  `type_id` int NOT NULL,
+  `url` varchar(500) NOT NULL,
+  `extra` varchar(255) NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
+
+LOCK TABLES `image_space_info` WRITE;
+/*!40000 ALTER TABLE `image_space_info` DISABLE KEYS */;
+
+INSERT INTO `image_space_info` (`createTime`, `updateTime`, `id`, `type_id`, `url`, `extra`)
+VALUES
+	('2020-09-24 06:11:00.706996','2020-09-24 06:11:00.706996',2,2,'http://image.si-yee.com/思忆/20200924_021100.png','{\"key\":\"思忆/20200924_021100.png\",\"hash\":\"FlcrAvWiCZQvsBTU7XKaDwpv0sSF\"}'),
+	('2020-09-24 06:20:44.617766','2020-09-24 06:20:44.617766',3,1,'http://image.si-yee.com/头像/20200924_022044.jpeg','{\"key\":\"头像/20200924_022044.jpeg\",\"hash\":\"Fikim5OW0KWgyLqRXktE0FqJQr6Y\"}');
+
+/*!40000 ALTER TABLE `image_space_info` ENABLE KEYS */;
+UNLOCK TABLES;
+
+
+# Dump of table image_space_type
+# ------------------------------------------------------------
+
+DROP TABLE IF EXISTS `image_space_type`;
+
+CREATE TABLE `image_space_type` (
+  `createTime` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+  `updateTime` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+  `id` int NOT NULL AUTO_INCREMENT,
+  `name` varchar(50) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `IDX_91decef4a2b88cb59caf658d44` (`name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
+
+LOCK TABLES `image_space_type` WRITE;
+/*!40000 ALTER TABLE `image_space_type` DISABLE KEYS */;
+
+INSERT INTO `image_space_type` (`createTime`, `updateTime`, `id`, `name`)
+VALUES
+	('2020-09-24 06:04:51.708064','2020-09-24 06:04:51.708064',1,'头像'),
+	('2020-09-24 06:10:36.509527','2020-09-24 06:10:36.509527',2,'思忆');
+
+/*!40000 ALTER TABLE `image_space_type` ENABLE KEYS */;
+UNLOCK TABLES;
+
+
+# Dump of table sys_department
+# ------------------------------------------------------------
+
+DROP TABLE IF EXISTS `sys_department`;
+
+CREATE TABLE `sys_department` (
+  `createTime` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+  `updateTime` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+  `id` int NOT NULL AUTO_INCREMENT,
+  `parend_id` int DEFAULT NULL,
+  `name` varchar(255) NOT NULL,
+  `order_num` int DEFAULT '0',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
+
+LOCK TABLES `sys_department` WRITE;
+/*!40000 ALTER TABLE `sys_department` DISABLE KEYS */;
+
+INSERT INTO `sys_department` (`createTime`, `updateTime`, `id`, `parend_id`, `name`, `order_num`)
+VALUES
+	('2020-08-27 03:33:19.000000','2020-08-27 03:33:19.000000',1,NULL,'公司部门',0),
+	('2020-09-08 05:31:32.426851','2020-10-07 04:25:31.000000',2,1,'管理部门',0);
+
+/*!40000 ALTER TABLE `sys_department` ENABLE KEYS */;
+UNLOCK TABLES;
+
+
+# Dump of table sys_login_log
+# ------------------------------------------------------------
+
+DROP TABLE IF EXISTS `sys_login_log`;
+
+CREATE TABLE `sys_login_log` (
+  `createTime` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+  `updateTime` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+  `id` int NOT NULL AUTO_INCREMENT,
+  `user_id` int DEFAULT NULL,
+  `ip` varchar(255) DEFAULT NULL,
+  `time` datetime DEFAULT NULL,
+  `ua` varchar(500) DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
+
+LOCK TABLES `sys_login_log` WRITE;
+/*!40000 ALTER TABLE `sys_login_log` DISABLE KEYS */;
+
+INSERT INTO `sys_login_log` (`createTime`, `updateTime`, `id`, `user_id`, `ip`, `time`, `ua`)
+VALUES
+	('2025-07-21 18:29:32.786050','2025-07-21 18:29:32.786050',1,1,'::1',NULL,'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36'),
+	('2025-07-22 18:34:04.194115','2025-07-22 18:34:04.194115',2,1,'::1',NULL,'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36'),
+	('2025-07-23 10:26:40.040698','2025-07-23 10:26:40.040698',3,1,'::1',NULL,'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36'),
+	('2025-07-23 10:27:13.466553','2025-07-23 10:27:13.466553',4,1,'::1',NULL,'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36');
+
+/*!40000 ALTER TABLE `sys_login_log` ENABLE KEYS */;
+UNLOCK TABLES;
+
+
+# Dump of table sys_menu
+# ------------------------------------------------------------
+
+DROP TABLE IF EXISTS `sys_menu`;
+
+CREATE TABLE `sys_menu` (
+  `createTime` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+  `updateTime` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+  `id` int NOT NULL AUTO_INCREMENT,
+  `parent_id` int DEFAULT NULL,
+  `name` varchar(255) NOT NULL,
+  `router` varchar(255) DEFAULT NULL,
+  `perms` varchar(255) DEFAULT NULL,
+  `type` tinyint NOT NULL DEFAULT '0' COMMENT '0:菜单 1 iframe 2外链3 按钮',
+  `icon` varchar(255) DEFAULT NULL,
+  `order_num` int DEFAULT '0',
+  `view_path` varchar(255) DEFAULT NULL,
+  `keepalive` tinyint DEFAULT '1',
+  `isShow` tinyint DEFAULT '1',
+  `status` int unsigned NOT NULL DEFAULT '1',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
+
+LOCK TABLES `sys_menu` WRITE;
+/*!40000 ALTER TABLE `sys_menu` DISABLE KEYS */;
+
+INSERT INTO `sys_menu` (`createTime`, `updateTime`, `id`, `parent_id`, `name`, `router`, `perms`, `type`, `icon`, `order_num`, `view_path`, `keepalive`, `isShow`, `status`)
+VALUES
+	('2020-08-28 10:09:26.322745','2020-10-12 06:35:18.000000',1,NULL,'系统','/system',NULL,0,'SettingOutlined',255,NULL,1,1,1),
+	('2020-08-01 00:00:00.000000','2020-09-14 03:53:31.000000',3,1,'权限管理','/system/permission',NULL,0,'SafetyOutlined',0,'',1,1,1),
+	('2020-08-08 00:00:00.000000','2020-09-08 06:54:45.000000',4,3,'用户列表','/system/permission/user',NULL,0,'UserOutlined',0,'/system/permission/user',1,1,1),
+	('2020-08-15 00:00:00.000000','2020-09-11 06:11:52.000000',5,4,'新增',NULL,'sys:user:add',3,NULL,0,NULL,1,1,1),
+	('2020-08-15 00:00:00.000000','2020-09-11 06:13:03.000000',6,4,'删除',NULL,'sys:user:delete',3,NULL,0,NULL,1,1,1),
+	('2020-08-08 00:00:00.000000','2020-09-24 09:51:40.000000',7,3,'菜单列表','/system/permission/menu',NULL,0,'MenuOutlined',0,'/system/permission/menu',1,1,1),
+	('2020-08-15 00:00:00.000000','2020-08-15 00:00:00.000000',8,7,'新增',NULL,'sys:menu:add',3,NULL,0,NULL,1,0,1),
+	('2020-08-15 00:00:00.000000','2020-08-15 00:00:00.000000',9,7,'删除',NULL,'sys:menu:delete',3,NULL,0,NULL,1,1,1),
+	('2020-09-02 08:22:27.548410','2020-09-02 08:22:27.548410',10,7,'查询',NULL,'sys:menu:list,sys:menu:info',3,NULL,0,NULL,1,1,1),
+	('2020-09-04 08:08:53.621419','2020-09-04 08:08:53.621419',19,7,'修改','','sys:menu:update',3,'',0,'',1,1,1),
+	('2020-09-04 09:41:43.133191','2020-09-24 09:16:56.000000',23,3,'角色列表','/system/permission/role','',0,'TeamOutlined',0,'/system/permission/role',1,1,1),
+	('2020-09-07 02:44:27.663925','2020-09-07 08:51:18.000000',25,23,'删除','','sys:role:delete',3,'',0,'',1,1,1),
+	('2020-09-07 02:49:36.058795','2020-09-14 03:56:56.000000',26,44,'饿了么文档','/document/elm','',2,'IeOutlined',0,'http://element-cn.eleme.io/#/zh-CN/component/installation',1,1,1),
+	('2020-09-07 02:50:03.345817','2020-09-14 03:56:47.000000',27,44,'TypeORM中文文档','/document/orm','',1,'FormOutlined',2,'https://www.bookstack.cn/read/TypeORM-0.2.20-zh/README.md',1,1,1),
+	('2020-09-07 07:08:18.106272','2020-09-14 10:26:58.000000',28,23,'新增','','sys:role:add',3,'',0,'',1,1,1),
+	('2020-09-07 08:51:48.319938','2020-09-07 08:51:58.000000',29,23,'修改','','sys:role:update',3,'',0,'',1,1,1),
+	('2020-09-07 10:39:50.396350','2025-07-17 16:22:29.000000',32,23,'查询','','sys:role:list,sys:role:page,sys:role:info,role_menu,menu_by_roleid',3,'',0,'',1,1,1),
+	('2020-09-08 05:29:40.117403','2020-09-11 06:03:43.000000',33,4,'部门查询','','sys:dept:list,sys:dept:info',3,'',0,'',1,1,1),
+	('2020-09-09 07:10:08.435753','2020-09-10 03:41:32.000000',34,4,'查询','','sys:user:page,sys:user:info',3,'',0,'',1,1,1),
+	('2020-09-10 05:09:31.904519','2020-09-10 05:09:31.904519',35,4,'更新','','sys:user:update',3,'',0,'',1,1,1),
+	('2020-09-10 08:02:29.853643','2020-09-10 08:02:40.000000',36,4,'部门转移','','sys:dept:transfer',3,'',0,'',1,1,1),
+	('2020-09-11 04:34:00.379002','2020-09-14 03:29:59.000000',37,1,'系统监控','/system/monitor','',0,'MonitorOutlined',0,'',1,1,1),
+	('2020-09-11 04:34:52.949262','2020-10-12 08:14:58.000000',38,37,'请求追踪','/system/monitor/log','',0,'SnippetsOutlined',0,'/system/monitor/log',1,1,1),
+	('2020-09-11 06:12:14.621531','2020-09-11 06:12:14.621531',39,4,'部门新增','','sys:dept:add',3,'',0,'',1,1,1),
+	('2020-09-11 06:13:23.752133','2020-09-11 06:13:23.752133',40,4,'部门删除','','sys:dept:delete',3,'',0,'',1,1,1),
+	('2020-09-11 06:29:52.437621','2020-09-11 06:29:52.437621',41,4,'部门更新','','sys:dept:update',3,'',0,'',1,1,1),
+	('2020-09-14 03:56:24.740870','2020-10-09 07:47:05.000000',44,NULL,'文档','/document','',0,'ContainerOutlined',0,'',1,1,1),
+	('2020-09-14 06:55:17.544866','2020-09-24 08:00:12.000000',45,NULL,'通用权限','/common','',0,'',0,'',1,0,1),
+	('2020-09-23 07:31:28.700348','2020-09-24 05:22:45.000000',46,45,'图片空间','','space:image:type:list,space:image:type:add,space:image:type:delete,space:image:page,space:image:delete,space:image:upload',3,'',0,'',1,1,1),
+	('2020-10-12 10:00:49.463487','2020-10-12 10:00:49.463487',51,37,'在线用户','/system/monitor/online',NULL,0,'UserOutlined',0,'/system/monitor/online',1,1,1),
+	('2020-10-13 03:01:13.787832','2020-10-13 03:01:13.787832',52,51,'查询','','sys:online:list',3,'',0,'',1,1,1),
+	('2020-10-13 03:01:51.480667','2020-10-13 03:01:51.480667',53,51,'下线','','sys:online:kick',3,'',0,'',1,1,1),
+	('2020-10-13 09:37:29.097828','2020-10-13 09:37:29.097828',54,38,'查询','','sys:req-log:page,sys:req-log:search',3,'',0,'',1,1,1),
+	('2020-10-13 09:52:08.932501','2020-10-13 09:53:44.000000',55,37,'登录日志','/system/monitor/login-log',NULL,0,'LoginOutlined',0,'/system/monitor/login-log',1,1,1),
+	('2020-10-13 09:56:13.285772','2020-10-13 09:56:13.285772',56,55,'查询','','sys:login-log:page',3,'',0,'',1,1,1),
+	('2020-10-19 03:07:18.221647','2025-07-18 20:05:06.000000',57,1,'任务调度','/system/schedule',NULL,0,'ContainerOutlined',0,'',1,1,1),
+	('2020-10-19 03:08:15.925726','2020-10-19 07:21:04.000000',58,57,'定时任务','/system/schedule/task',NULL,0,'schedule',0,'/system/schedule/task',1,1,1),
+	('2020-10-19 03:08:36.247678','2020-10-19 03:08:36.247678',59,58,'查询','','sys:task:page,sys:task:info',3,'',0,'',1,1,1),
+	('2020-10-19 03:09:09.436949','2020-10-19 03:09:09.436949',60,58,'新增','','sys:task:add',3,'',0,'',1,1,1),
+	('2020-10-19 03:09:42.895534','2020-10-19 03:09:42.895534',61,58,'更新','','sys:task:update',3,'',0,'',1,1,1),
+	('2020-10-19 05:45:30.512641','2020-10-19 05:45:30.512641',62,58,'执行一次','','sys:task:once',3,'',0,'',1,1,1),
+	('2020-10-19 05:46:01.910857','2020-10-19 05:46:01.910857',63,58,'运行','','sys:task:start',3,'',0,'',1,1,1),
+	('2020-10-19 05:46:23.694028','2020-10-19 05:46:23.694028',64,58,'暂停','','sys:task:stop',3,'',0,'',1,1,1),
+	('2020-10-19 06:25:52.225518','2020-10-19 06:25:52.225518',65,58,'删除','','sys:task:delete',3,'',0,'',1,1,1),
+	('2020-10-19 07:30:18.456330','2020-10-19 07:30:18.456330',66,57,'任务日志','/system/schedule/log',NULL,0,'schedule-log',0,'/system/schedule/log',1,1,1),
+	('2020-10-19 08:09:49.063343','2020-10-19 08:09:49.063343',67,66,'查询','','sys:task-log:page',3,'',0,'',1,1,1),
+	('2025-07-21 18:34:26.863995','2025-07-21 18:34:26.863995',71,NULL,'Tg机器人','/telegram',NULL,0,'CodepenOutlined',1,NULL,1,1,1),
+	('2025-07-21 18:41:39.497517','2025-07-21 18:41:39.497517',72,71,'机器人','/telegram/tgbot',NULL,0,'RobotOutlined',0,NULL,1,1,1),
+	('2025-07-21 18:42:46.729101','2025-07-21 18:50:03.000000',73,71,'群组','/telegram/tggroup',NULL,0,'ApartmentOutlined',1,NULL,1,1,1),
+	('2025-07-21 18:46:55.443737','2025-07-21 18:46:55.443737',74,71,'消息日志','/telegram/tgmessage',NULL,0,'FileMarkdownOutlined',2,NULL,1,1,1),
+	('2025-07-21 18:49:27.840712','2025-07-21 18:49:27.840712',75,71,'发送日志','/telegram/sendlog',NULL,0,'ContainerOutlined',3,NULL,1,1,1),
+	('2025-07-21 20:19:03.984075','2025-07-21 20:19:03.984075',76,72,'新增',NULL,'tg:bot:add',3,NULL,0,NULL,0,0,1),
+	('2025-07-21 20:20:17.823393','2025-07-21 20:20:17.823393',77,72,'删除',NULL,'tg:bot:delete',3,NULL,0,NULL,0,0,1),
+	('2025-07-21 20:21:38.971429','2025-07-21 21:18:36.000000',78,72,'查询',NULL,'tg:bot:list,tg:bot:page',3,NULL,0,NULL,0,0,1),
+	('2025-07-21 20:23:05.435367','2025-07-21 20:23:05.435367',79,72,'修改',NULL,'tg:bot:update',3,NULL,0,NULL,0,0,1),
+	('2025-07-21 20:24:24.819122','2025-07-21 20:24:24.819122',80,73,'增加',NULL,'tg:group:add',3,NULL,0,NULL,0,0,1),
+	('2025-07-21 20:25:20.016999','2025-07-21 20:25:20.016999',81,73,'删除',NULL,'tg:group:delete',3,NULL,0,NULL,0,0,1),
+	('2025-07-21 20:26:18.521517','2025-07-21 20:26:18.521517',82,73,'修改',NULL,'tg:group:update',3,NULL,0,NULL,0,0,1),
+	('2025-07-21 20:27:32.885849','2025-07-22 11:02:01.000000',83,73,'查询',NULL,'tg:group:list,tg:group:page,tg:group:send_groups_msg,tg:group:send_msg,tg:group:msg_groupIds,tg:group:all_botlist',3,NULL,0,NULL,0,0,1),
+	('2025-07-22 22:37:12.158408','2025-07-22 22:37:12.158408',84,74,'查询',NULL,'tg:group:receive_page',3,NULL,0,NULL,0,0,1),
+	('2025-07-22 22:37:54.017434','2025-07-22 22:37:54.017434',85,75,'查询',NULL,'tg:group:send_page',3,NULL,0,NULL,0,0,1);
+
+/*!40000 ALTER TABLE `sys_menu` ENABLE KEYS */;
+UNLOCK TABLES;
+
+
+# Dump of table sys_req_log
+# ------------------------------------------------------------
+
+DROP TABLE IF EXISTS `sys_req_log`;
+
+CREATE TABLE `sys_req_log` (
+  `createTime` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+  `updateTime` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+  `id` int NOT NULL AUTO_INCREMENT,
+  `ip` varchar(255) DEFAULT NULL,
+  `user_id` int DEFAULT NULL,
+  `params` text,
+  `action` varchar(100) DEFAULT NULL,
+  `method` varchar(15) DEFAULT NULL,
+  `status` int DEFAULT NULL,
+  `consume_time` int DEFAULT '0',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
+
+LOCK TABLES `sys_req_log` WRITE;
+/*!40000 ALTER TABLE `sys_req_log` DISABLE KEYS */;
+
+INSERT INTO `sys_req_log` (`createTime`, `updateTime`, `id`, `ip`, `user_id`, `params`, `action`, `method`, `status`, `consume_time`)
+VALUES
+	('2025-07-23 10:29:32.249229','2025-07-23 10:29:32.249229',1,'::1',1,'{}','/admin/person','GET',200,2),
+	('2025-07-23 10:29:32.255129','2025-07-23 10:29:32.255129',2,'::1',1,'{}','/admin/permmenu','GET',200,11),
+	('2025-07-23 10:29:32.670456','2025-07-23 10:29:32.670456',3,'::1',1,'{}','/admin/notifications','GET',200,1),
+	('2025-07-23 10:29:32.978647','2025-07-23 10:29:32.978647',4,'::1',1,'{}','/admin/tg/group/all_botlist','GET',200,6),
+	('2025-07-23 10:29:32.980942','2025-07-23 10:29:32.980942',5,'::1',1,'{}','/admin/tg/group/msg_groupIds','GET',200,6),
+	('2025-07-23 10:29:33.021813','2025-07-23 10:29:33.021813',6,'::1',1,'{\"current\":\"1\",\"pageSize\":\"10\"}','/admin/tg/group/page','GET',200,5),
+	('2025-07-23 10:29:38.663969','2025-07-23 10:29:38.663969',7,'::1',1,'{\"current\":\"1\",\"pageSize\":\"10\"}','/admin/tg/group/receive_page','GET',200,7),
+	('2025-07-23 10:29:47.210745','2025-07-23 10:29:47.210745',8,'::1',1,'{\"groupId\":-4870165498,\"botId\":7865568785,\"message\":\"123567\"}','/admin/tg/group/send_msg','POST',200,18),
+	('2025-07-23 10:29:55.236154','2025-07-23 10:29:55.236154',9,'::1',1,'{\"current\":\"1\",\"pageSize\":\"10\"}','/admin/tg/group/receive_page','GET',200,6),
+	('2025-07-23 10:30:00.516536','2025-07-23 10:30:00.516536',10,'::1',1,'{\"current\":\"1\",\"pageSize\":\"10\"}','/admin/tg/group/send_page','GET',200,8),
+	('2025-07-23 10:30:30.511129','2025-07-23 10:30:30.511129',11,'::1',1,'{\"current\":\"1\",\"pageSize\":\"10\"}','/admin/tg/group/receive_page','GET',200,7),
+	('2025-07-23 10:30:36.518270','2025-07-23 10:30:36.518270',12,'::1',1,'{\"current\":\"1\",\"pageSize\":\"10\"}','/admin/tg/bot/page','GET',200,6);
+
+/*!40000 ALTER TABLE `sys_req_log` ENABLE KEYS */;
+UNLOCK TABLES;
+
+
+# Dump of table sys_role
+# ------------------------------------------------------------
+
+DROP TABLE IF EXISTS `sys_role`;
+
+CREATE TABLE `sys_role` (
+  `createTime` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+  `updateTime` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+  `id` int NOT NULL AUTO_INCREMENT,
+  `userId` varchar(255) NOT NULL,
+  `name` varchar(255) NOT NULL,
+  `label` varchar(50) NOT NULL,
+  `remark` varchar(255) DEFAULT NULL,
+  `status` int unsigned NOT NULL DEFAULT '1',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `IDX_223de54d6badbe43a5490450c3` (`name`),
+  UNIQUE KEY `IDX_f2d07943355da93c3a8a1c411a` (`label`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
+
+LOCK TABLES `sys_role` WRITE;
+/*!40000 ALTER TABLE `sys_role` DISABLE KEYS */;
+
+INSERT INTO `sys_role` (`createTime`, `updateTime`, `id`, `userId`, `name`, `label`, `remark`, `status`)
+VALUES
+	('2020-08-27 03:35:05.000000','2020-08-27 03:35:05.000000',1,'root','root','系统管理员',NULL,1),
+	('2020-09-14 07:39:02.423786','2025-07-17 20:41:14.000000',2,'1','超级管理员','superadmin','11111',1),
+	('2025-07-17 23:50:42.494553','2025-07-17 23:50:42.494553',5,'1','test','test','test',1);
+
+/*!40000 ALTER TABLE `sys_role` ENABLE KEYS */;
+UNLOCK TABLES;
+
+
+# Dump of table sys_role_department
+# ------------------------------------------------------------
+
+DROP TABLE IF EXISTS `sys_role_department`;
+
+CREATE TABLE `sys_role_department` (
+  `createTime` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+  `updateTime` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+  `id` int NOT NULL AUTO_INCREMENT,
+  `role_id` int NOT NULL,
+  `department_id` int NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
+
+
+
+# Dump of table sys_role_menu
+# ------------------------------------------------------------
+
+DROP TABLE IF EXISTS `sys_role_menu`;
+
+CREATE TABLE `sys_role_menu` (
+  `createTime` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+  `updateTime` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+  `id` int NOT NULL AUTO_INCREMENT,
+  `role_id` int NOT NULL,
+  `menu_id` int NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
+
+LOCK TABLES `sys_role_menu` WRITE;
+/*!40000 ALTER TABLE `sys_role_menu` DISABLE KEYS */;
+
+INSERT INTO `sys_role_menu` (`createTime`, `updateTime`, `id`, `role_id`, `menu_id`)
+VALUES
+	('2020-09-14 07:39:02.432650','2020-09-14 07:39:02.432650',1,2,44),
+	('2020-09-14 07:39:02.432650','2020-09-14 07:39:02.432650',2,2,26),
+	('2020-09-14 07:39:02.432650','2020-09-14 07:39:02.432650',3,2,27),
+	('2020-09-15 08:05:45.986899','2020-09-15 08:05:45.986899',4,2,4),
+	('2020-09-15 08:05:45.986899','2020-09-15 08:05:45.986899',7,2,33),
+	('2020-09-15 08:05:45.986899','2020-09-15 08:05:45.986899',8,2,34),
+	('2020-09-15 08:05:45.986899','2020-09-15 08:05:45.986899',10,2,36),
+	('2020-09-15 08:05:45.986899','2020-09-15 08:05:45.986899',11,2,39),
+	('2020-09-15 08:05:45.986899','2020-09-15 08:05:45.986899',13,2,41),
+	('2020-09-15 08:05:45.986899','2020-09-15 08:05:45.986899',14,2,37),
+	('2020-09-15 08:05:45.986899','2020-09-15 08:05:45.986899',15,2,38),
+	('2020-09-15 08:05:45.986899','2020-09-15 08:05:45.986899',17,2,1),
+	('2020-09-15 08:05:45.986899','2020-09-15 08:05:45.986899',18,2,3),
+	('2020-09-21 06:29:29.232489','2020-09-21 06:29:29.232489',19,2,8),
+	('2020-09-21 06:29:29.232489','2020-09-21 06:29:29.232489',20,2,10),
+	('2020-09-21 06:29:29.232489','2020-09-21 06:29:29.232489',21,2,7),
+	('2020-10-12 03:22:35.019793','2020-10-12 03:22:35.019793',22,2,45),
+	('2020-10-12 03:22:35.019793','2020-10-12 03:22:35.019793',23,2,46),
+	('2025-07-17 20:41:57.689694','2025-07-17 20:41:57.689694',30,2,5),
+	('2025-07-17 20:41:57.689694','2025-07-17 20:41:57.689694',31,2,6),
+	('2025-07-17 20:41:57.689694','2025-07-17 20:41:57.689694',32,2,35),
+	('2025-07-17 20:41:57.689694','2025-07-17 20:41:57.689694',33,2,40),
+	('2025-07-17 20:41:57.689694','2025-07-17 20:41:57.689694',34,2,9),
+	('2025-07-17 20:41:57.689694','2025-07-17 20:41:57.689694',35,2,19),
+	('2025-07-17 20:41:57.689694','2025-07-17 20:41:57.689694',36,2,23),
+	('2025-07-17 20:41:57.689694','2025-07-17 20:41:57.689694',37,2,25),
+	('2025-07-17 20:41:57.689694','2025-07-17 20:41:57.689694',38,2,28),
+	('2025-07-17 20:41:57.689694','2025-07-17 20:41:57.689694',39,2,29),
+	('2025-07-17 20:41:57.689694','2025-07-17 20:41:57.689694',40,2,32),
+	('2025-07-17 20:41:57.689694','2025-07-17 20:41:57.689694',41,2,54),
+	('2025-07-17 20:41:57.689694','2025-07-17 20:41:57.689694',42,2,51),
+	('2025-07-17 20:41:57.689694','2025-07-17 20:41:57.689694',43,2,52),
+	('2025-07-17 20:41:57.689694','2025-07-17 20:41:57.689694',44,2,53),
+	('2025-07-17 20:41:57.689694','2025-07-17 20:41:57.689694',45,2,55),
+	('2025-07-17 20:41:57.689694','2025-07-17 20:41:57.689694',46,2,56),
+	('2025-07-17 20:41:57.689694','2025-07-17 20:41:57.689694',47,2,57),
+	('2025-07-17 20:41:57.689694','2025-07-17 20:41:57.689694',48,2,58),
+	('2025-07-17 20:41:57.689694','2025-07-17 20:41:57.689694',49,2,59),
+	('2025-07-17 20:41:57.689694','2025-07-17 20:41:57.689694',50,2,60),
+	('2025-07-17 20:41:57.689694','2025-07-17 20:41:57.689694',51,2,61),
+	('2025-07-17 20:41:57.689694','2025-07-17 20:41:57.689694',52,2,62),
+	('2025-07-17 20:41:57.689694','2025-07-17 20:41:57.689694',53,2,63),
+	('2025-07-17 20:41:57.689694','2025-07-17 20:41:57.689694',54,2,64),
+	('2025-07-17 20:41:57.689694','2025-07-17 20:41:57.689694',55,2,65),
+	('2025-07-17 20:41:57.689694','2025-07-17 20:41:57.689694',56,2,66),
+	('2025-07-17 20:41:57.689694','2025-07-17 20:41:57.689694',57,2,67),
+	('2025-07-21 21:17:35.865321','2025-07-21 21:17:35.865321',58,2,71),
+	('2025-07-21 21:17:35.865321','2025-07-21 21:17:35.865321',59,2,72),
+	('2025-07-21 21:17:35.865321','2025-07-21 21:17:35.865321',60,2,73),
+	('2025-07-21 21:17:35.865321','2025-07-21 21:17:35.865321',61,2,74),
+	('2025-07-21 21:17:35.865321','2025-07-21 21:17:35.865321',62,2,75),
+	('2025-07-21 21:17:35.865321','2025-07-21 21:17:35.865321',63,2,76),
+	('2025-07-21 21:17:35.865321','2025-07-21 21:17:35.865321',64,2,77),
+	('2025-07-21 21:17:35.865321','2025-07-21 21:17:35.865321',65,2,78),
+	('2025-07-21 21:17:35.865321','2025-07-21 21:17:35.865321',66,2,79),
+	('2025-07-21 21:17:35.865321','2025-07-21 21:17:35.865321',67,2,80),
+	('2025-07-21 21:17:35.865321','2025-07-21 21:17:35.865321',68,2,81),
+	('2025-07-21 21:17:35.865321','2025-07-21 21:17:35.865321',69,2,82),
+	('2025-07-21 21:17:35.865321','2025-07-21 21:17:35.865321',70,2,83);
+
+/*!40000 ALTER TABLE `sys_role_menu` ENABLE KEYS */;
+UNLOCK TABLES;
+
+
+# Dump of table sys_task
+# ------------------------------------------------------------
+
+DROP TABLE IF EXISTS `sys_task`;
+
+CREATE TABLE `sys_task` (
+  `createTime` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+  `updateTime` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+  `id` int NOT NULL AUTO_INCREMENT,
+  `name` varchar(50) NOT NULL,
+  `service` varchar(255) NOT NULL,
+  `type` tinyint NOT NULL DEFAULT '0',
+  `status` tinyint NOT NULL DEFAULT '1',
+  `start_time` datetime DEFAULT NULL,
+  `end_time` datetime DEFAULT NULL,
+  `limit` int DEFAULT '0',
+  `cron` varchar(255) DEFAULT NULL,
+  `every` int DEFAULT NULL,
+  `data` text,
+  `job_opts` text,
+  `remark` varchar(255) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `IDX_ef8e5ab5ef2fe0ddb1428439ef` (`name`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
+
+LOCK TABLES `sys_task` WRITE;
+/*!40000 ALTER TABLE `sys_task` DISABLE KEYS */;
+
+INSERT INTO `sys_task` (`createTime`, `updateTime`, `id`, `name`, `service`, `type`, `status`, `start_time`, `end_time`, `limit`, `cron`, `every`, `data`, `job_opts`, `remark`)
+VALUES
+	('2020-10-19 08:53:44.732338','2025-07-23 09:19:37.000000',1,'定时清空请求追踪日志','admin.sys.reqLog.clear',0,1,NULL,NULL,0,'0 0 3 ? * 1',1000,'','{\"count\":1,\"key\":\"__default__:1:::0 0 3 ? * 1\",\"cron\":\"0 0 3 ? * 1\",\"jobId\":1}',''),
+	('2020-10-19 08:54:42.760785','2025-07-23 09:19:37.000000',2,'定时清空登录日志','admin.sys.loginLog.clear',0,1,NULL,NULL,0,'0 0 3 ? * 1',0,'','{\"count\":1,\"key\":\"__default__:2:::0 0 3 ? * 1\",\"cron\":\"0 0 3 ? * 1\",\"jobId\":2}',''),
+	('2020-10-19 08:55:06.050711','2025-07-23 09:19:37.000000',3,'定时清空任务日志','admin.sys.taskLog.clear',0,1,NULL,NULL,0,'0 0 3 ? * 1',0,'','{\"count\":1,\"key\":\"__default__:3:::0 0 3 ? * 1\",\"cron\":\"0 0 3 ? * 1\",\"jobId\":3}',''),
+	('2020-10-22 07:48:10.089919','2020-10-23 04:21:10.000000',4,'获取逆天邪神最新章节','reptile.book.getZonghengBookLastchapter',1,0,NULL,NULL,3,'',20000,'{\"id\":\"408586\",\"emails\":\"qa894178522@qq.com\"}','','');
+
+/*!40000 ALTER TABLE `sys_task` ENABLE KEYS */;
+UNLOCK TABLES;
+
+
+# Dump of table sys_task_log
+# ------------------------------------------------------------
+
+DROP TABLE IF EXISTS `sys_task_log`;
+
+CREATE TABLE `sys_task_log` (
+  `createTime` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+  `updateTime` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+  `id` int NOT NULL AUTO_INCREMENT,
+  `task_id` int NOT NULL,
+  `status` tinyint NOT NULL DEFAULT '0',
+  `detail` text,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
+
+
+
+# Dump of table sys_user
+# ------------------------------------------------------------
+
+DROP TABLE IF EXISTS `sys_user`;
+
+CREATE TABLE `sys_user` (
+  `createTime` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+  `updateTime` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+  `id` int NOT NULL AUTO_INCREMENT,
+  `department_id` int NOT NULL,
+  `name` varchar(255) NOT NULL,
+  `username` varchar(255) NOT NULL,
+  `password` varchar(255) NOT NULL,
+  `nick_name` varchar(255) DEFAULT NULL,
+  `head_img` varchar(255) DEFAULT NULL,
+  `email` varchar(255) DEFAULT NULL,
+  `phone` varchar(255) DEFAULT NULL,
+  `remark` varchar(255) DEFAULT NULL,
+  `status` tinyint DEFAULT '1',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `IDX_9e7164b2f1ea1348bc0eb0a7da` (`username`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
+
+LOCK TABLES `sys_user` WRITE;
+/*!40000 ALTER TABLE `sys_user` DISABLE KEYS */;
+
+INSERT INTO `sys_user` (`createTime`, `updateTime`, `id`, `department_id`, `name`, `username`, `password`, `nick_name`, `head_img`, `email`, `phone`, `remark`, `status`)
+VALUES
+	('2020-08-27 03:38:30.000000','2025-07-23 10:26:53.000000',1,1,'xdh','rootadmin','U2FsdGVkX1/ShdM7AyvIfQJCMxMu37a76Qqb8sV6UkA=',NULL,'https://avatar.vercel.sh/blur.svg?text=2','qa894178522@qq.com','15622472425',NULL,1),
+	('2020-09-14 07:41:33.732000','2025-07-18 16:16:51.000000',2,2,'hackycy','test001','U2FsdGVkX1+iVR+kHHErkPNInZiV27Mpk4pFL8uhtTQ=','TESTE','','qa894178522@qq.com','1223412','',0),
+	('2020-10-12 03:23:14.611864','2020-10-12 06:12:20.000000',3,2,'hackycy','openadmin','U2FsdGVkX18tvghStliPvPopFZQiILUrZhNk3mUs2jA=','',NULL,'','','',1);
+
+/*!40000 ALTER TABLE `sys_user` ENABLE KEYS */;
+UNLOCK TABLES;
+
+
+# Dump of table sys_user_role
+# ------------------------------------------------------------
+
+DROP TABLE IF EXISTS `sys_user_role`;
+
+CREATE TABLE `sys_user_role` (
+  `createTime` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+  `updateTime` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+  `id` int NOT NULL AUTO_INCREMENT,
+  `user_id` int NOT NULL,
+  `role_id` int NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
+
+LOCK TABLES `sys_user_role` WRITE;
+/*!40000 ALTER TABLE `sys_user_role` DISABLE KEYS */;
+
+INSERT INTO `sys_user_role` (`createTime`, `updateTime`, `id`, `user_id`, `role_id`)
+VALUES
+	('2020-09-14 04:10:34.371646','2020-09-14 04:10:34.371646',1,1,1),
+	('2020-10-12 06:12:20.149960','2020-10-12 06:12:20.149960',18,3,2),
+	('2025-07-18 16:16:51.761627','2025-07-18 16:16:51.761627',26,2,2),
+	('2025-07-18 16:16:51.761627','2025-07-18 16:16:51.761627',27,2,5);
+
+/*!40000 ALTER TABLE `sys_user_role` ENABLE KEYS */;
+UNLOCK TABLES;
+
+
+# Dump of table tg_bot
+# ------------------------------------------------------------
+
+DROP TABLE IF EXISTS `tg_bot`;
+
+CREATE TABLE `tg_bot` (
+  `id` int NOT NULL AUTO_INCREMENT,
+  `botId` bigint NOT NULL COMMENT '机器人ID',
+  `username` varchar(100) NOT NULL COMMENT '机器人用户名',
+  `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '机器人名称',
+  `token` varchar(255) NOT NULL COMMENT '机器人Token',
+  `status` tinyint DEFAULT '1' COMMENT '机器人状态 0 1',
+  `description` varchar(500) DEFAULT NULL COMMENT '机器人描述',
+  `createTime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `updateTime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `uk_username` (`username`),
+  UNIQUE KEY `uk_botId` (`botId`) USING BTREE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='Telegram机器人表';
+
+LOCK TABLES `tg_bot` WRITE;
+/*!40000 ALTER TABLE `tg_bot` DISABLE KEYS */;
+
+INSERT INTO `tg_bot` (`id`, `botId`, `username`, `name`, `token`, `status`, `description`, `createTime`, `updateTime`)
+VALUES
+	(1,7865568785,'hdx234_bot','hdx_bot','7865568785:AAHjZATUdJWYAC3eXY08C_bQQi4z7psQ9g8',1,'test','2025-07-21 01:31:47','2025-07-21 23:04:19');
+
+/*!40000 ALTER TABLE `tg_bot` ENABLE KEYS */;
+UNLOCK TABLES;
+
+
+# Dump of table tg_bot_send_log
+# ------------------------------------------------------------
+
+DROP TABLE IF EXISTS `tg_bot_send_log`;
+
+CREATE TABLE `tg_bot_send_log` (
+  `id` int unsigned NOT NULL AUTO_INCREMENT,
+  `botId` bigint NOT NULL COMMENT '机器人Id',
+  `groupId` bigint NOT NULL COMMENT '群id',
+  `text` text COMMENT '发送消息',
+  `createTime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
+  `updateTime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+  PRIMARY KEY (`id`),
+  KEY `idx_botId` (`botId`),
+  KEY `idx_groupId` (`groupId`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
+
+LOCK TABLES `tg_bot_send_log` WRITE;
+/*!40000 ALTER TABLE `tg_bot_send_log` DISABLE KEYS */;
+
+INSERT INTO `tg_bot_send_log` (`id`, `botId`, `groupId`, `text`, `createTime`, `updateTime`)
+VALUES
+	(1,7865568785,-4870165498,'123567','2025-07-23 10:29:47','2025-07-23 10:29:47');
+
+/*!40000 ALTER TABLE `tg_bot_send_log` ENABLE KEYS */;
+UNLOCK TABLES;
+
+
+# Dump of table tg_group
+# ------------------------------------------------------------
+
+DROP TABLE IF EXISTS `tg_group`;
+
+CREATE TABLE `tg_group` (
+  `id` int NOT NULL AUTO_INCREMENT,
+  `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '群组名称',
+  `username` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT '' COMMENT '群组用户名或邀请链接',
+  `groupId` bigint DEFAULT NULL COMMENT '群组ID(加入群组后获取)',
+  `description` varchar(500) DEFAULT NULL COMMENT '群组描述',
+  `type` int NOT NULL DEFAULT '0' COMMENT '分类ID 0 只听 1只发 2 既听又发',
+  `defaultBotId` bigint DEFAULT NULL COMMENT '默认机器人ID',
+  `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否启用',
+  `createTime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `updateTime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `uk_groupId` (`groupId`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='Telegram群组表';
+
+LOCK TABLES `tg_group` WRITE;
+/*!40000 ALTER TABLE `tg_group` DISABLE KEYS */;
+
+INSERT INTO `tg_group` (`id`, `name`, `username`, `groupId`, `description`, `type`, `defaultBotId`, `status`, `createTime`, `updateTime`)
+VALUES
+	(2,'z & xdh','test',-4870165498,'kjlsdlfa',2,7865568785,1,'2025-07-22 15:33:35','2025-07-22 21:19:29');
+
+/*!40000 ALTER TABLE `tg_group` ENABLE KEYS */;
+UNLOCK TABLES;
+
+
+# Dump of table tg_group_bot
+# ------------------------------------------------------------
+
+DROP TABLE IF EXISTS `tg_group_bot`;
+
+CREATE TABLE `tg_group_bot` (
+  `id` int NOT NULL AUTO_INCREMENT,
+  `groupId` bigint NOT NULL COMMENT '群组ID',
+  `botId` bigint NOT NULL COMMENT '机器人ID',
+  `createTime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `updateTime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `uk_group_bot` (`groupId`,`botId`),
+  KEY `idx_group` (`groupId`),
+  KEY `idx_bot` (`botId`),
+  CONSTRAINT `fk_bot_id` FOREIGN KEY (`botId`) REFERENCES `tg_bot` (`botId`) ON DELETE CASCADE ON UPDATE CASCADE,
+  CONSTRAINT `fk_group_id` FOREIGN KEY (`groupId`) REFERENCES `tg_group` (`groupId`) ON DELETE CASCADE ON UPDATE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='Telegram群组机器人绑定表';
+
+LOCK TABLES `tg_group_bot` WRITE;
+/*!40000 ALTER TABLE `tg_group_bot` DISABLE KEYS */;
+
+INSERT INTO `tg_group_bot` (`id`, `groupId`, `botId`, `createTime`, `updateTime`)
+VALUES
+	(9,-4870165498,7865568785,'2025-07-22 15:33:35','2025-07-22 15:33:35');
+
+/*!40000 ALTER TABLE `tg_group_bot` ENABLE KEYS */;
+UNLOCK TABLES;
+
+
+# Dump of table tg_message
+# ------------------------------------------------------------
+
+DROP TABLE IF EXISTS `tg_message`;
+
+CREATE TABLE `tg_message` (
+  `id` int unsigned NOT NULL AUTO_INCREMENT,
+  `botId` bigint NOT NULL COMMENT '哪个机器人监听的',
+  `messageId` bigint NOT NULL COMMENT 'Telegram消息ID',
+  `chatId` bigint NOT NULL COMMENT '群组ID或个人ID',
+  `chatType` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT 'group' COMMENT 'group 群组 private个人',
+  `chatTitle` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '群组标题',
+  `chatName` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '个人聊天昵称',
+  `chatUsername` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '个人聊天用户名',
+  `fromUserId` bigint NOT NULL COMMENT '发送者用户id',
+  `fromName` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '发送者名字',
+  `fromUsername` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '发送者用户名',
+  `isBot` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '是否来自机器人',
+  `sentTime` int DEFAULT NULL COMMENT '发送时间',
+  `text` text COMMENT '消息内容',
+  `createTime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+  `updateTime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '跟新时间',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `uk_message` (`messageId`,`chatId`,`botId`) USING BTREE,
+  KEY `idx_chatId` (`chatId`),
+  KEY `idx_chat_type` (`chatType`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='Telegram消息表';
+
+LOCK TABLES `tg_message` WRITE;
+/*!40000 ALTER TABLE `tg_message` DISABLE KEYS */;
+
+INSERT INTO `tg_message` (`id`, `botId`, `messageId`, `chatId`, `chatType`, `chatTitle`, `chatName`, `chatUsername`, `fromUserId`, `fromName`, `fromUsername`, `isBot`, `sentTime`, `text`, `createTime`, `updateTime`)
+VALUES
+	(1,7865568785,118,-4870165498,'group','z & xdh',NULL,NULL,5034446407,'xdh','hdx234',0,1753237823,'hello hello','2025-07-23 10:30:23','2025-07-23 10:30:23');
+
+/*!40000 ALTER TABLE `tg_message` ENABLE KEYS */;
+UNLOCK TABLES;
+
+
+
+/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
+/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
+/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
+/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
+/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
+/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;

+ 398 - 0
db/oldinit.sql

@@ -0,0 +1,398 @@
+/*
+ Navicat Premium Data Transfer
+
+ Source Server         : Mac
+ Source Server Type    : MySQL
+ Source Server Version : 50728
+ Source Host           : localhost:3306
+ Source Schema         : sf-admin
+
+ Target Server Type    : MySQL
+ Target Server Version : 50728
+ File Encoding         : 65001
+
+ Date: 26/10/2020 17:30:38
+*/
+
+SET NAMES utf8mb4;
+SET FOREIGN_KEY_CHECKS = 0;
+
+-- ----------------------------
+-- Table structure for image_space_info
+-- ----------------------------
+DROP TABLE IF EXISTS `image_space_info`;
+CREATE TABLE `image_space_info` (
+  `createTime` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+  `updateTime` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `type_id` int(11) NOT NULL,
+  `url` varchar(500) NOT NULL,
+  `extra` varchar(255) NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4;
+
+-- ----------------------------
+-- Records of image_space_info
+-- ----------------------------
+BEGIN;
+INSERT INTO `image_space_info` VALUES ('2020-09-24 06:11:00.706996', '2020-09-24 06:11:00.706996', 2, 2, 'http://image.si-yee.com/思忆/20200924_021100.png', '{\"key\":\"思忆/20200924_021100.png\",\"hash\":\"FlcrAvWiCZQvsBTU7XKaDwpv0sSF\"}');
+INSERT INTO `image_space_info` VALUES ('2020-09-24 06:20:44.617766', '2020-09-24 06:20:44.617766', 3, 1, 'http://image.si-yee.com/头像/20200924_022044.jpeg', '{\"key\":\"头像/20200924_022044.jpeg\",\"hash\":\"Fikim5OW0KWgyLqRXktE0FqJQr6Y\"}');
+COMMIT;
+
+-- ----------------------------
+-- Table structure for image_space_type
+-- ----------------------------
+DROP TABLE IF EXISTS `image_space_type`;
+CREATE TABLE `image_space_type` (
+  `createTime` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+  `updateTime` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `name` varchar(50) NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `IDX_91decef4a2b88cb59caf658d44` (`name`)
+) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4;
+
+-- ----------------------------
+-- Records of image_space_type
+-- ----------------------------
+BEGIN;
+INSERT INTO `image_space_type` VALUES ('2020-09-24 06:04:51.708064', '2020-09-24 06:04:51.708064', 1, '头像');
+INSERT INTO `image_space_type` VALUES ('2020-09-24 06:10:36.509527', '2020-09-24 06:10:36.509527', 2, '思忆');
+COMMIT;
+
+-- ----------------------------
+-- Table structure for sys_department
+-- ----------------------------
+DROP TABLE IF EXISTS `sys_department`;
+CREATE TABLE `sys_department` (
+  `createTime` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+  `updateTime` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `parend_id` int(11) DEFAULT NULL,
+  `name` varchar(255) NOT NULL,
+  `order_num` int(11) DEFAULT '0',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4;
+
+-- ----------------------------
+-- Records of sys_department
+-- ----------------------------
+BEGIN;
+INSERT INTO `sys_department` VALUES ('2020-08-27 03:33:19.000000', '2020-08-27 03:33:19.000000', 1, NULL, '思忆技术', 0);
+INSERT INTO `sys_department` VALUES ('2020-09-08 05:31:32.426851', '2020-10-07 04:25:31.000000', 2, 1, '管理部门', 0);
+COMMIT;
+
+-- ----------------------------
+-- Table structure for sys_login_log
+-- ----------------------------
+DROP TABLE IF EXISTS `sys_login_log`;
+CREATE TABLE `sys_login_log` (
+  `createTime` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+  `updateTime` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `user_id` int(20) DEFAULT NULL,
+  `ip` varchar(255) DEFAULT NULL,
+  `time` datetime DEFAULT NULL,
+  `ua` varchar(500) DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4;
+
+-- ----------------------------
+-- Records of sys_login_log
+-- ----------------------------
+BEGIN;
+INSERT INTO `sys_login_log` VALUES ('2020-10-20 05:52:09.267588', '2020-10-20 05:52:09.267588', 1, 1, '127.0.0.1', '2020-10-20 05:52:09', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.80 Safari/537.36');
+INSERT INTO `sys_login_log` VALUES ('2020-10-22 07:43:12.063370', '2020-10-22 07:43:12.063370', 2, 1, '127.0.0.1', '2020-10-22 07:43:12', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36');
+INSERT INTO `sys_login_log` VALUES ('2020-10-22 07:45:31.359047', '2020-10-22 07:45:31.359047', 3, 1, '127.0.0.1', '2020-10-22 07:45:31', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36');
+INSERT INTO `sys_login_log` VALUES ('2020-10-23 02:39:50.682860', '2020-10-23 02:39:50.682860', 4, 1, '127.0.0.1', '2020-10-23 02:39:51', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36');
+INSERT INTO `sys_login_log` VALUES ('2020-10-26 08:51:05.538341', '2020-10-26 08:51:05.538341', 5, 1, '127.0.0.1', '2020-10-26 08:51:06', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36');
+INSERT INTO `sys_login_log` VALUES ('2020-10-26 09:24:23.451278', '2020-10-26 09:24:23.451278', 6, 3, '127.0.0.1', '2020-10-26 09:24:23', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36');
+INSERT INTO `sys_login_log` VALUES ('2020-10-26 09:24:59.196689', '2020-10-26 09:24:59.196689', 7, 1, '127.0.0.1', '2020-10-26 09:24:59', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36');
+INSERT INTO `sys_login_log` VALUES ('2020-10-26 09:29:59.322557', '2020-10-26 09:29:59.322557', 8, 1, '127.0.0.1', '2020-10-26 09:29:59', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36');
+COMMIT;
+
+-- ----------------------------
+-- Table structure for sys_menu
+-- ----------------------------
+DROP TABLE IF EXISTS `sys_menu`;
+CREATE TABLE `sys_menu` (
+  `createTime` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+  `updateTime` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `parent_id` int(11) DEFAULT NULL,
+  `name` varchar(255) NOT NULL,
+  `router` varchar(255) DEFAULT NULL,
+  `perms` varchar(255) DEFAULT NULL,
+  `type` tinyint(4) NOT NULL DEFAULT '0',
+  `icon` varchar(255) DEFAULT NULL,
+  `order_num` int(11) DEFAULT '0',
+  `view_path` varchar(255) DEFAULT NULL,
+  `keepalive` tinyint(4) DEFAULT '1',
+  `isShow` tinyint(4) DEFAULT '1',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=68 DEFAULT CHARSET=utf8mb4;
+
+-- ----------------------------
+-- Records of sys_menu
+-- ----------------------------
+BEGIN;
+INSERT INTO `sys_menu` VALUES ('2020-08-28 10:09:26.322745', '2020-10-12 06:35:18.000000', 1, NULL, '系统', '/sys', NULL, 0, 'system', 255, NULL, 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-08-01 00:00:00.000000', '2020-09-14 03:53:31.000000', 3, 1, '权限管理', '/sys/permssion', NULL, 0, 'permission', 0, '', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-08-08 00:00:00.000000', '2020-09-08 06:54:45.000000', 4, 3, '用户列表', '/sys/permssion/user', NULL, 1, 'peoples', 0, 'views/system/permission/user', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-08-15 00:00:00.000000', '2020-09-11 06:11:52.000000', 5, 4, '新增', NULL, 'sys:user:add', 2, NULL, 0, NULL, 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-08-15 00:00:00.000000', '2020-09-11 06:13:03.000000', 6, 4, '删除', NULL, 'sys:user:delete', 2, NULL, 0, NULL, 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-08-08 00:00:00.000000', '2020-09-24 09:51:40.000000', 7, 3, '菜单列表', '/sys/permssion/menu', NULL, 1, 'menu', 0, 'views/system/permission/menu', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-08-15 00:00:00.000000', '2020-08-15 00:00:00.000000', 8, 7, '新增', NULL, 'sys:menu:add', 2, NULL, 0, NULL, 1, 0);
+INSERT INTO `sys_menu` VALUES ('2020-08-15 00:00:00.000000', '2020-08-15 00:00:00.000000', 9, 7, '删除', NULL, 'sys:menu:delete', 2, NULL, 0, NULL, 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-09-02 08:22:27.548410', '2020-09-02 08:22:27.548410', 10, 7, '查询', NULL, 'sys:menu:list,sys:menu:info', 2, NULL, 0, NULL, 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-09-04 06:26:36.408290', '2020-09-04 07:13:30.000000', 17, 16, '测试', '', 'sys:menu:list,sys:menu:update,sys:menu:info,sys:menu:add', 2, '', 0, '', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-09-04 08:08:53.621419', '2020-09-04 08:08:53.621419', 19, 7, '修改', '', 'sys:menu:update', 2, '', 0, '', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-09-04 09:41:43.133191', '2020-09-24 09:16:56.000000', 23, 3, '角色列表', '/sys/permission/role', '', 1, 'role', 0, 'views/system/permission/role', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-09-07 02:44:27.663925', '2020-09-07 08:51:18.000000', 25, 23, '删除', '', 'sys:role:delete', 2, '', 0, '', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-09-07 02:49:36.058795', '2020-09-14 03:56:56.000000', 26, 44, '饿了么文档', 'http://element-cn.eleme.io/#/zh-CN/component/installation', '', 1, 'international', 0, 'views/charts/keyboard', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-09-07 02:50:03.345817', '2020-09-14 03:56:47.000000', 27, 44, 'TypeORM中文文档', 'https://www.bookstack.cn/read/TypeORM-0.2.20-zh/README.md', '', 1, 'international', 2, 'views/error-log/components/ErrorTestB', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-09-07 07:08:18.106272', '2020-09-14 10:26:58.000000', 28, 23, '新增', '', 'sys:role:add', 2, '', 0, '', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-09-07 08:51:48.319938', '2020-09-07 08:51:58.000000', 29, 23, '修改', '', 'sys:role:update', 2, '', 0, '', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-09-07 10:39:50.396350', '2020-09-09 06:34:13.000000', 32, 23, '查询', '', 'sys:role:list,sys:role:page,sys:role:info', 2, '', 0, '', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-09-08 05:29:40.117403', '2020-09-11 06:03:43.000000', 33, 4, '部门查询', '', 'sys:dept:list,sys:dept:info', 2, '', 0, '', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-09-09 07:10:08.435753', '2020-09-10 03:41:32.000000', 34, 4, '查询', '', 'sys:user:page,sys:user:info', 2, '', 0, '', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-09-10 05:09:31.904519', '2020-09-10 05:09:31.904519', 35, 4, '更新', '', 'sys:user:update', 2, '', 0, '', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-09-10 08:02:29.853643', '2020-09-10 08:02:40.000000', 36, 4, '部门转移', '', 'sys:dept:transfer', 2, '', 0, '', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-09-11 04:34:00.379002', '2020-09-14 03:29:59.000000', 37, 1, '系统监控', '/sys/monitor', '', 0, 'monitor', 0, '', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-09-11 04:34:52.949262', '2020-10-12 08:14:58.000000', 38, 37, '请求追踪', '/sys/monitor/log', '', 1, 'log', 0, 'views/system/monitor/req-log', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-09-11 06:12:14.621531', '2020-09-11 06:12:14.621531', 39, 4, '部门新增', '', 'sys:dept:add', 2, '', 0, '', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-09-11 06:13:23.752133', '2020-09-11 06:13:23.752133', 40, 4, '部门删除', '', 'sys:dept:delete', 2, '', 0, '', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-09-11 06:29:52.437621', '2020-09-11 06:29:52.437621', 41, 4, '部门更新', '', 'sys:dept:update', 2, '', 0, '', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-09-14 03:56:24.740870', '2020-10-09 07:47:05.000000', 44, NULL, '文档', '/document', '', 0, 'documentation', 0, '', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-09-14 06:55:17.544866', '2020-09-24 08:00:12.000000', 45, NULL, '通用权限', '/common', '', 0, '', 0, '', 1, 0);
+INSERT INTO `sys_menu` VALUES ('2020-09-23 07:31:28.700348', '2020-09-24 05:22:45.000000', 46, 45, '图片空间', '', 'space:image:type:list,space:image:type:add,space:image:type:delete,space:image:page,space:image:delete,space:image:upload', 2, '', 0, '', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-10-12 10:00:49.463487', '2020-10-12 10:00:49.463487', 51, 37, '在线用户', '/sys/monitor/online', NULL, 1, 'people', 0, 'views/system/monitor/online', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-10-13 03:01:13.787832', '2020-10-13 03:01:13.787832', 52, 51, '查询', '', 'sys:online:list', 2, '', 0, '', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-10-13 03:01:51.480667', '2020-10-13 03:01:51.480667', 53, 51, '下线', '', 'sys:online:kick', 2, '', 0, '', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-10-13 09:37:29.097828', '2020-10-13 09:37:29.097828', 54, 38, '查询', '', 'sys:req-log:page,sys:req-log:search', 2, '', 0, '', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-10-13 09:52:08.932501', '2020-10-13 09:53:44.000000', 55, 37, '登录日志', '/sys/monitor/login-log', NULL, 1, 'guide', 0, 'views/system/monitor/login-log', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-10-13 09:56:13.285772', '2020-10-13 09:56:13.285772', 56, 55, '查询', '', 'sys:login-log:page', 2, '', 0, '', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-10-19 03:07:18.221647', '2020-10-19 07:26:37.000000', 57, 1, '任务调度', '/sys/schedule', NULL, 0, 'task', 0, '', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-10-19 03:08:15.925726', '2020-10-19 07:21:04.000000', 58, 57, '定时任务', '/sys/schedule/task', NULL, 1, 'schedule', 0, 'views/system/schedule/task', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-10-19 03:08:36.247678', '2020-10-19 03:08:36.247678', 59, 58, '查询', '', 'sys:task:page,sys:task:info', 2, '', 0, '', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-10-19 03:09:09.436949', '2020-10-19 03:09:09.436949', 60, 58, '新增', '', 'sys:task:add', 2, '', 0, '', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-10-19 03:09:42.895534', '2020-10-19 03:09:42.895534', 61, 58, '更新', '', 'sys:task:update', 2, '', 0, '', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-10-19 05:45:30.512641', '2020-10-19 05:45:30.512641', 62, 58, '执行一次', '', 'sys:task:once', 2, '', 0, '', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-10-19 05:46:01.910857', '2020-10-19 05:46:01.910857', 63, 58, '运行', '', 'sys:task:start', 2, '', 0, '', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-10-19 05:46:23.694028', '2020-10-19 05:46:23.694028', 64, 58, '暂停', '', 'sys:task:stop', 2, '', 0, '', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-10-19 06:25:52.225518', '2020-10-19 06:25:52.225518', 65, 58, '删除', '', 'sys:task:delete', 2, '', 0, '', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-10-19 07:30:18.456330', '2020-10-19 07:30:18.456330', 66, 57, '任务日志', '/sys/schedule/log', NULL, 1, 'schedule-log', 0, 'views/system/schedule/log', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-10-19 08:09:49.063343', '2020-10-19 08:09:49.063343', 67, 66, '查询', '', 'sys:task-log:page', 2, '', 0, '', 1, 1);
+COMMIT;
+
+-- ----------------------------
+-- Table structure for sys_req_log
+-- ----------------------------
+DROP TABLE IF EXISTS `sys_req_log`;
+CREATE TABLE `sys_req_log` (
+  `createTime` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+  `updateTime` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `ip` varchar(255) DEFAULT NULL,
+  `user_id` int(11) DEFAULT NULL,
+  `params` text,
+  `action` varchar(100) DEFAULT NULL,
+  `method` varchar(15) DEFAULT NULL,
+  `status` int(11) DEFAULT NULL,
+  `consume_time` int(11) DEFAULT '0',
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=137 DEFAULT CHARSET=utf8mb4;
+
+-- ----------------------------
+-- Table structure for sys_role
+-- ----------------------------
+DROP TABLE IF EXISTS `sys_role`;
+CREATE TABLE `sys_role` (
+  `createTime` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+  `updateTime` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `userId` varchar(255) NOT NULL,
+  `name` varchar(255) NOT NULL,
+  `label` varchar(50) NOT NULL,
+  `remark` varchar(255) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `IDX_223de54d6badbe43a5490450c3` (`name`),
+  UNIQUE KEY `IDX_f2d07943355da93c3a8a1c411a` (`label`)
+) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4;
+
+-- ----------------------------
+-- Records of sys_role
+-- ----------------------------
+BEGIN;
+INSERT INTO `sys_role` VALUES ('2020-08-27 03:35:05.000000', '2020-08-27 03:35:05.000000', 1, 'root', 'root', '超级管理员', NULL);
+INSERT INTO `sys_role` VALUES ('2020-09-14 07:39:02.423786', '2020-10-13 07:29:33.000000', 2, '1', '测试角色', 'testrole', '');
+COMMIT;
+
+-- ----------------------------
+-- Table structure for sys_role_department
+-- ----------------------------
+DROP TABLE IF EXISTS `sys_role_department`;
+CREATE TABLE `sys_role_department` (
+  `createTime` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+  `updateTime` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `role_id` int(11) NOT NULL,
+  `department_id` int(11) NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4;
+
+-- ----------------------------
+-- Records of sys_role_department
+-- ----------------------------
+BEGIN;
+INSERT INTO `sys_role_department` VALUES ('2020-09-14 07:39:02.445216', '2020-09-14 07:39:02.445216', 1, 2, 1);
+INSERT INTO `sys_role_department` VALUES ('2020-09-14 07:39:02.445216', '2020-09-14 07:39:02.445216', 2, 2, 2);
+COMMIT;
+
+-- ----------------------------
+-- Table structure for sys_role_menu
+-- ----------------------------
+DROP TABLE IF EXISTS `sys_role_menu`;
+CREATE TABLE `sys_role_menu` (
+  `createTime` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+  `updateTime` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `role_id` int(11) NOT NULL,
+  `menu_id` int(11) NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=24 DEFAULT CHARSET=utf8mb4;
+
+-- ----------------------------
+-- Records of sys_role_menu
+-- ----------------------------
+BEGIN;
+INSERT INTO `sys_role_menu` VALUES ('2020-09-14 07:39:02.432650', '2020-09-14 07:39:02.432650', 1, 2, 44);
+INSERT INTO `sys_role_menu` VALUES ('2020-09-14 07:39:02.432650', '2020-09-14 07:39:02.432650', 2, 2, 26);
+INSERT INTO `sys_role_menu` VALUES ('2020-09-14 07:39:02.432650', '2020-09-14 07:39:02.432650', 3, 2, 27);
+INSERT INTO `sys_role_menu` VALUES ('2020-09-15 08:05:45.986899', '2020-09-15 08:05:45.986899', 4, 2, 4);
+INSERT INTO `sys_role_menu` VALUES ('2020-09-15 08:05:45.986899', '2020-09-15 08:05:45.986899', 7, 2, 33);
+INSERT INTO `sys_role_menu` VALUES ('2020-09-15 08:05:45.986899', '2020-09-15 08:05:45.986899', 8, 2, 34);
+INSERT INTO `sys_role_menu` VALUES ('2020-09-15 08:05:45.986899', '2020-09-15 08:05:45.986899', 10, 2, 36);
+INSERT INTO `sys_role_menu` VALUES ('2020-09-15 08:05:45.986899', '2020-09-15 08:05:45.986899', 11, 2, 39);
+INSERT INTO `sys_role_menu` VALUES ('2020-09-15 08:05:45.986899', '2020-09-15 08:05:45.986899', 13, 2, 41);
+INSERT INTO `sys_role_menu` VALUES ('2020-09-15 08:05:45.986899', '2020-09-15 08:05:45.986899', 14, 2, 37);
+INSERT INTO `sys_role_menu` VALUES ('2020-09-15 08:05:45.986899', '2020-09-15 08:05:45.986899', 15, 2, 38);
+INSERT INTO `sys_role_menu` VALUES ('2020-09-15 08:05:45.986899', '2020-09-15 08:05:45.986899', 16, 2, 43);
+INSERT INTO `sys_role_menu` VALUES ('2020-09-15 08:05:45.986899', '2020-09-15 08:05:45.986899', 17, 2, 1);
+INSERT INTO `sys_role_menu` VALUES ('2020-09-15 08:05:45.986899', '2020-09-15 08:05:45.986899', 18, 2, 3);
+INSERT INTO `sys_role_menu` VALUES ('2020-09-21 06:29:29.232489', '2020-09-21 06:29:29.232489', 19, 2, 8);
+INSERT INTO `sys_role_menu` VALUES ('2020-09-21 06:29:29.232489', '2020-09-21 06:29:29.232489', 20, 2, 10);
+INSERT INTO `sys_role_menu` VALUES ('2020-09-21 06:29:29.232489', '2020-09-21 06:29:29.232489', 21, 2, 7);
+INSERT INTO `sys_role_menu` VALUES ('2020-10-12 03:22:35.019793', '2020-10-12 03:22:35.019793', 22, 2, 45);
+INSERT INTO `sys_role_menu` VALUES ('2020-10-12 03:22:35.019793', '2020-10-12 03:22:35.019793', 23, 2, 46);
+COMMIT;
+
+-- ----------------------------
+-- Table structure for sys_task
+-- ----------------------------
+DROP TABLE IF EXISTS `sys_task`;
+CREATE TABLE `sys_task` (
+  `createTime` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+  `updateTime` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `name` varchar(50) NOT NULL,
+  `service` varchar(255) NOT NULL,
+  `type` tinyint(4) NOT NULL DEFAULT '0',
+  `status` tinyint(4) NOT NULL DEFAULT '1',
+  `start_time` datetime DEFAULT NULL,
+  `end_time` datetime DEFAULT NULL,
+  `limit` int(11) DEFAULT '0',
+  `cron` varchar(255) DEFAULT NULL,
+  `every` int(11) DEFAULT NULL,
+  `data` text,
+  `job_opts` text,
+  `remark` varchar(255) DEFAULT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `IDX_ef8e5ab5ef2fe0ddb1428439ef` (`name`)
+) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4;
+
+-- ----------------------------
+-- Records of sys_task
+-- ----------------------------
+BEGIN;
+INSERT INTO `sys_task` VALUES ('2020-10-19 08:53:44.732338', '2020-10-26 09:28:23.000000', 1, '定时清空请求追踪日志', 'admin.sys.reqLog.clear', 0, 1, NULL, NULL, 0, '0 0 3 ? * 1', 1000, '', '{\"count\":1,\"cron\":\"0 0 3 ? * 1\",\"jobId\":1}', '');
+INSERT INTO `sys_task` VALUES ('2020-10-19 08:54:42.760785', '2020-10-26 09:28:23.000000', 2, '定时清空登录日志', 'admin.sys.loginLog.clear', 0, 1, NULL, NULL, 0, '0 0 3 ? * 1', 0, '', '{\"count\":1,\"cron\":\"0 0 3 ? * 1\",\"jobId\":2}', '');
+INSERT INTO `sys_task` VALUES ('2020-10-19 08:55:06.050711', '2020-10-26 09:28:23.000000', 3, '定时清空任务日志', 'admin.sys.taskLog.clear', 0, 1, NULL, NULL, 0, '0 0 3 ? * 1', 0, '', '{\"count\":1,\"cron\":\"0 0 3 ? * 1\",\"jobId\":3}', '');
+INSERT INTO `sys_task` VALUES ('2020-10-22 07:48:10.089919', '2020-10-23 04:21:10.000000', 4, '获取逆天邪神最新章节', 'reptile.book.getZonghengBookLastchapter', 1, 0, NULL, NULL, 3, '', 20000, '{\"id\":\"408586\",\"emails\":\"qa894178522@qq.com\"}', '', '');
+COMMIT;
+
+-- ----------------------------
+-- Table structure for sys_task_log
+-- ----------------------------
+DROP TABLE IF EXISTS `sys_task_log`;
+CREATE TABLE `sys_task_log` (
+  `createTime` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+  `updateTime` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `task_id` int(11) NOT NULL,
+  `status` tinyint(4) NOT NULL DEFAULT '0',
+  `detail` text,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4;
+
+-- ----------------------------
+-- Records of sys_task_log
+-- ----------------------------
+BEGIN;
+INSERT INTO `sys_task_log` VALUES ('2020-10-26 08:52:49.759752', '2020-10-26 08:52:49.759752', 1, 4, 1, NULL);
+COMMIT;
+
+-- ----------------------------
+-- Table structure for sys_user
+-- ----------------------------
+DROP TABLE IF EXISTS `sys_user`;
+CREATE TABLE `sys_user` (
+  `createTime` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+  `updateTime` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `department_id` int(11) NOT NULL,
+  `name` varchar(255) NOT NULL,
+  `username` varchar(255) NOT NULL,
+  `password` varchar(255) NOT NULL,
+  `nick_name` varchar(255) DEFAULT NULL,
+  `head_img` varchar(255) DEFAULT NULL,
+  `email` varchar(255) DEFAULT NULL,
+  `phone` varchar(255) DEFAULT NULL,
+  `remark` varchar(255) DEFAULT NULL,
+  `status` tinyint(4) DEFAULT '1',
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `IDX_9e7164b2f1ea1348bc0eb0a7da` (`username`)
+) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4;
+
+-- ----------------------------
+-- Records of sys_user
+-- ----------------------------
+BEGIN;
+INSERT INTO `sys_user` VALUES ('2020-08-27 03:38:30.000000', '2020-10-07 07:17:14.000000', 1, 1, 'hackycy', 'rootadmin', 'U2FsdGVkX18tvghStliPvPopFZQiILUrZhNk3mUs2jA=', '', 'http://image.si-yee.com/思忆/20200924_021100.png', 'qa894178522@qq.com', '15622472425', NULL, 1);
+INSERT INTO `sys_user` VALUES ('2020-09-14 07:41:33.732000', '2020-10-12 06:04:19.000000', 2, 2, 'hackycy', 'test001', 'U2FsdGVkX1+iVR+kHHErkPNInZiV27Mpk4pFL8uhtTQ=', '', '', 'qa894178522@qq.com', '', '', 0);
+INSERT INTO `sys_user` VALUES ('2020-10-12 03:23:14.611864', '2020-10-12 06:12:20.000000', 3, 2, 'hackycy', 'openadmin', 'U2FsdGVkX18tvghStliPvPopFZQiILUrZhNk3mUs2jA=', '', NULL, '', '', '', 1);
+COMMIT;
+
+-- ----------------------------
+-- Table structure for sys_user_role
+-- ----------------------------
+DROP TABLE IF EXISTS `sys_user_role`;
+CREATE TABLE `sys_user_role` (
+  `createTime` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+  `updateTime` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `user_id` int(11) NOT NULL,
+  `role_id` int(11) NOT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB AUTO_INCREMENT=19 DEFAULT CHARSET=utf8mb4;
+
+-- ----------------------------
+-- Records of sys_user_role
+-- ----------------------------
+BEGIN;
+INSERT INTO `sys_user_role` VALUES ('2020-09-14 04:10:34.371646', '2020-09-14 04:10:34.371646', 1, 1, 1);
+INSERT INTO `sys_user_role` VALUES ('2020-10-12 06:04:19.072876', '2020-10-12 06:04:19.072876', 17, 2, 2);
+INSERT INTO `sys_user_role` VALUES ('2020-10-12 06:12:20.149960', '2020-10-12 06:12:20.149960', 18, 3, 2);
+COMMIT;
+
+SET FOREIGN_KEY_CHECKS = 1;

+ 2 - 0
db/seed/image_space_info.sql

@@ -0,0 +1,2 @@
+INSERT INTO `image_space_info` VALUES ('2020-09-24 06:11:00.706996', '2020-09-24 06:11:00.706996', 2, 2, 'http://image.si-yee.com/思忆/20200924_021100.png', '{\"key\":\"思忆/20200924_021100.png\",\"hash\":\"FlcrAvWiCZQvsBTU7XKaDwpv0sSF\"}');
+INSERT INTO `image_space_info` VALUES ('2020-09-24 06:20:44.617766', '2020-09-24 06:20:44.617766', 3, 1, 'http://image.si-yee.com/头像/20200924_022044.jpeg', '{\"key\":\"头像/20200924_022044.jpeg\",\"hash\":\"Fikim5OW0KWgyLqRXktE0FqJQr6Y\"}');

+ 2 - 0
db/seed/image_space_type.sql

@@ -0,0 +1,2 @@
+INSERT INTO `image_space_type` VALUES ('2020-09-24 06:04:51.708064', '2020-09-24 06:04:51.708064', 1, '头像');
+INSERT INTO `image_space_type` VALUES ('2020-09-24 06:10:36.509527', '2020-09-24 06:10:36.509527', 2, '思忆');

+ 2 - 0
db/seed/sys_department.sql

@@ -0,0 +1,2 @@
+INSERT INTO `sys_department` VALUES ('2020-08-27 03:33:19.000000', '2020-08-27 03:33:19.000000', 1, NULL, '思忆技术', 0);
+INSERT INTO `sys_department` VALUES ('2020-09-08 05:31:32.426851', '2020-10-07 04:25:31.000000', 2, 1, '管理部门', 0);

+ 4 - 0
db/seed/sys_login_log.sql

@@ -0,0 +1,4 @@
+INSERT INTO `sys_login_log` VALUES ('2020-10-20 05:52:09.267588', '2020-10-20 05:52:09.267588', 1, 1, '127.0.0.1', '2020-10-20 05:52:09', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.80 Safari/537.36');
+INSERT INTO `sys_login_log` VALUES ('2020-10-22 07:43:12.063370', '2020-10-22 07:43:12.063370', 2, 1, '127.0.0.1', '2020-10-22 07:43:12', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36');
+INSERT INTO `sys_login_log` VALUES ('2020-10-22 07:45:31.359047', '2020-10-22 07:45:31.359047', 3, 1, '127.0.0.1', '2020-10-22 07:45:31', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36');
+INSERT INTO `sys_login_log` VALUES ('2020-10-23 02:39:50.682860', '2020-10-23 02:39:50.682860', 4, 1, '127.0.0.1', '2020-10-23 02:39:51', 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36');

+ 47 - 0
db/seed/sys_menu.sql

@@ -0,0 +1,47 @@
+INSERT INTO `sys_menu` VALUES ('2020-08-28 10:09:26.322745', '2020-10-12 06:35:18.000000', 1, NULL, '系统', '/sys', NULL, 0, 'system', 255, NULL, 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-08-01 00:00:00.000000', '2020-09-14 03:53:31.000000', 3, 1, '权限管理', '/sys/permssion', NULL, 0, 'permission', 0, '', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-08-08 00:00:00.000000', '2020-09-08 06:54:45.000000', 4, 3, '用户列表', '/sys/permssion/user', NULL, 1, 'peoples', 0, 'views/system/permission/user', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-08-15 00:00:00.000000', '2020-09-11 06:11:52.000000', 5, 4, '新增', NULL, 'sys:user:add', 2, NULL, 0, NULL, 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-08-15 00:00:00.000000', '2020-09-11 06:13:03.000000', 6, 4, '删除', NULL, 'sys:user:delete', 2, NULL, 0, NULL, 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-08-08 00:00:00.000000', '2020-09-24 09:51:40.000000', 7, 3, '菜单列表', '/sys/permssion/menu', NULL, 1, 'menu', 0, 'views/system/permission/menu', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-08-15 00:00:00.000000', '2020-08-15 00:00:00.000000', 8, 7, '新增', NULL, 'sys:menu:add', 2, NULL, 0, NULL, 1, 0);
+INSERT INTO `sys_menu` VALUES ('2020-08-15 00:00:00.000000', '2020-08-15 00:00:00.000000', 9, 7, '删除', NULL, 'sys:menu:delete', 2, NULL, 0, NULL, 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-09-02 08:22:27.548410', '2020-09-02 08:22:27.548410', 10, 7, '查询', NULL, 'sys:menu:list,sys:menu:info', 2, NULL, 0, NULL, 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-09-04 06:26:36.408290', '2020-09-04 07:13:30.000000', 17, 16, '测试', '', 'sys:menu:list,sys:menu:update,sys:menu:info,sys:menu:add', 2, '', 0, '', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-09-04 08:08:53.621419', '2020-09-04 08:08:53.621419', 19, 7, '修改', '', 'sys:menu:update', 2, '', 0, '', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-09-04 09:41:43.133191', '2020-09-24 09:16:56.000000', 23, 3, '角色列表', '/sys/permission/role', '', 1, 'role', 0, 'views/system/permission/role', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-09-07 02:44:27.663925', '2020-09-07 08:51:18.000000', 25, 23, '删除', '', 'sys:role:delete', 2, '', 0, '', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-09-07 02:49:36.058795', '2020-09-14 03:56:56.000000', 26, 44, '饿了么文档', 'http://element-cn.eleme.io/#/zh-CN/component/installation', '', 1, 'international', 0, 'views/charts/keyboard', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-09-07 02:50:03.345817', '2020-09-14 03:56:47.000000', 27, 44, 'TypeORM中文文档', 'https://www.bookstack.cn/read/TypeORM-0.2.20-zh/README.md', '', 1, 'international', 2, 'views/error-log/components/ErrorTestB', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-09-07 07:08:18.106272', '2020-09-14 10:26:58.000000', 28, 23, '新增', '', 'sys:role:add', 2, '', 0, '', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-09-07 08:51:48.319938', '2020-09-07 08:51:58.000000', 29, 23, '修改', '', 'sys:role:update', 2, '', 0, '', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-09-07 10:39:50.396350', '2020-09-09 06:34:13.000000', 32, 23, '查询', '', 'sys:role:list,sys:role:page,sys:role:info', 2, '', 0, '', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-09-08 05:29:40.117403', '2020-09-11 06:03:43.000000', 33, 4, '部门查询', '', 'sys:dept:list,sys:dept:info', 2, '', 0, '', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-09-09 07:10:08.435753', '2020-09-10 03:41:32.000000', 34, 4, '查询', '', 'sys:user:page,sys:user:info', 2, '', 0, '', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-09-10 05:09:31.904519', '2020-09-10 05:09:31.904519', 35, 4, '更新', '', 'sys:user:update', 2, '', 0, '', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-09-10 08:02:29.853643', '2020-09-10 08:02:40.000000', 36, 4, '部门转移', '', 'sys:dept:transfer', 2, '', 0, '', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-09-11 04:34:00.379002', '2020-09-14 03:29:59.000000', 37, 1, '系统监控', '/sys/monitor', '', 0, 'monitor', 0, '', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-09-11 04:34:52.949262', '2020-10-12 08:14:58.000000', 38, 37, '请求追踪', '/sys/monitor/log', '', 1, 'log', 0, 'views/system/monitor/req-log', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-09-11 06:12:14.621531', '2020-09-11 06:12:14.621531', 39, 4, '部门新增', '', 'sys:dept:add', 2, '', 0, '', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-09-11 06:13:23.752133', '2020-09-11 06:13:23.752133', 40, 4, '部门删除', '', 'sys:dept:delete', 2, '', 0, '', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-09-11 06:29:52.437621', '2020-09-11 06:29:52.437621', 41, 4, '部门更新', '', 'sys:dept:update', 2, '', 0, '', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-09-14 03:56:24.740870', '2020-10-09 07:47:05.000000', 44, NULL, '文档', '/document', '', 0, 'documentation', 0, '', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-09-14 06:55:17.544866', '2020-09-24 08:00:12.000000', 45, NULL, '通用权限', '/common', '', 0, '', 0, '', 1, 0);
+INSERT INTO `sys_menu` VALUES ('2020-09-23 07:31:28.700348', '2020-09-24 05:22:45.000000', 46, 45, '图片空间', '', 'space:image:type:list,space:image:type:add,space:image:type:delete,space:image:page,space:image:delete,space:image:upload', 2, '', 0, '', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-10-12 10:00:49.463487', '2020-10-12 10:00:49.463487', 51, 37, '在线用户', '/sys/monitor/online', NULL, 1, 'people', 0, 'views/system/monitor/online', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-10-13 03:01:13.787832', '2020-10-13 03:01:13.787832', 52, 51, '查询', '', 'sys:online:list', 2, '', 0, '', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-10-13 03:01:51.480667', '2020-10-13 03:01:51.480667', 53, 51, '下线', '', 'sys:online:kick', 2, '', 0, '', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-10-13 09:37:29.097828', '2020-10-13 09:37:29.097828', 54, 38, '查询', '', 'sys:req-log:page,sys:req-log:search', 2, '', 0, '', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-10-13 09:52:08.932501', '2020-10-13 09:53:44.000000', 55, 37, '登录日志', '/sys/monitor/login-log', NULL, 1, 'guide', 0, 'views/system/monitor/login-log', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-10-13 09:56:13.285772', '2020-10-13 09:56:13.285772', 56, 55, '查询', '', 'sys:login-log:page', 2, '', 0, '', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-10-19 03:07:18.221647', '2020-10-19 07:26:37.000000', 57, 1, '任务调度', '/sys/schedule', NULL, 0, 'task', 0, '', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-10-19 03:08:15.925726', '2020-10-19 07:21:04.000000', 58, 57, '定时任务', '/sys/schedule/task', NULL, 1, 'schedule', 0, 'views/system/schedule/task', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-10-19 03:08:36.247678', '2020-10-19 03:08:36.247678', 59, 58, '查询', '', 'sys:task:page,sys:task:info', 2, '', 0, '', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-10-19 03:09:09.436949', '2020-10-19 03:09:09.436949', 60, 58, '新增', '', 'sys:task:add', 2, '', 0, '', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-10-19 03:09:42.895534', '2020-10-19 03:09:42.895534', 61, 58, '更新', '', 'sys:task:update', 2, '', 0, '', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-10-19 05:45:30.512641', '2020-10-19 05:45:30.512641', 62, 58, '执行一次', '', 'sys:task:once', 2, '', 0, '', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-10-19 05:46:01.910857', '2020-10-19 05:46:01.910857', 63, 58, '运行', '', 'sys:task:start', 2, '', 0, '', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-10-19 05:46:23.694028', '2020-10-19 05:46:23.694028', 64, 58, '暂停', '', 'sys:task:stop', 2, '', 0, '', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-10-19 06:25:52.225518', '2020-10-19 06:25:52.225518', 65, 58, '删除', '', 'sys:task:delete', 2, '', 0, '', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-10-19 07:30:18.456330', '2020-10-19 07:30:18.456330', 66, 57, '任务日志', '/sys/schedule/log', NULL, 1, 'schedule-log', 0, 'views/system/schedule/log', 1, 1);
+INSERT INTO `sys_menu` VALUES ('2020-10-19 08:09:49.063343', '2020-10-19 08:09:49.063343', 67, 66, '查询', '', 'sys:task-log:page', 2, '', 0, '', 1, 1);

+ 0 - 0
db/seed/sys_req_log.sql


+ 2 - 0
db/seed/sys_role.sql

@@ -0,0 +1,2 @@
+INSERT INTO `sys_role` VALUES ('2020-08-27 03:35:05.000000', '2020-08-27 03:35:05.000000', 1, 'root', 'root', '超级管理员', NULL);
+INSERT INTO `sys_role` VALUES ('2020-09-14 07:39:02.423786', '2020-10-13 07:29:33.000000', 2, '1', '测试角色', 'testrole', '');

+ 2 - 0
db/seed/sys_role_department.sql

@@ -0,0 +1,2 @@
+INSERT INTO `sys_role_department` VALUES ('2020-09-14 07:39:02.445216', '2020-09-14 07:39:02.445216', 1, 2, 1);
+INSERT INTO `sys_role_department` VALUES ('2020-09-14 07:39:02.445216', '2020-09-14 07:39:02.445216', 2, 2, 2);

+ 19 - 0
db/seed/sys_role_menu.sql

@@ -0,0 +1,19 @@
+INSERT INTO `sys_role_menu` VALUES ('2020-09-14 07:39:02.432650', '2020-09-14 07:39:02.432650', 1, 2, 44);
+INSERT INTO `sys_role_menu` VALUES ('2020-09-14 07:39:02.432650', '2020-09-14 07:39:02.432650', 2, 2, 26);
+INSERT INTO `sys_role_menu` VALUES ('2020-09-14 07:39:02.432650', '2020-09-14 07:39:02.432650', 3, 2, 27);
+INSERT INTO `sys_role_menu` VALUES ('2020-09-15 08:05:45.986899', '2020-09-15 08:05:45.986899', 4, 2, 4);
+INSERT INTO `sys_role_menu` VALUES ('2020-09-15 08:05:45.986899', '2020-09-15 08:05:45.986899', 7, 2, 33);
+INSERT INTO `sys_role_menu` VALUES ('2020-09-15 08:05:45.986899', '2020-09-15 08:05:45.986899', 8, 2, 34);
+INSERT INTO `sys_role_menu` VALUES ('2020-09-15 08:05:45.986899', '2020-09-15 08:05:45.986899', 10, 2, 36);
+INSERT INTO `sys_role_menu` VALUES ('2020-09-15 08:05:45.986899', '2020-09-15 08:05:45.986899', 11, 2, 39);
+INSERT INTO `sys_role_menu` VALUES ('2020-09-15 08:05:45.986899', '2020-09-15 08:05:45.986899', 13, 2, 41);
+INSERT INTO `sys_role_menu` VALUES ('2020-09-15 08:05:45.986899', '2020-09-15 08:05:45.986899', 14, 2, 37);
+INSERT INTO `sys_role_menu` VALUES ('2020-09-15 08:05:45.986899', '2020-09-15 08:05:45.986899', 15, 2, 38);
+INSERT INTO `sys_role_menu` VALUES ('2020-09-15 08:05:45.986899', '2020-09-15 08:05:45.986899', 16, 2, 43);
+INSERT INTO `sys_role_menu` VALUES ('2020-09-15 08:05:45.986899', '2020-09-15 08:05:45.986899', 17, 2, 1);
+INSERT INTO `sys_role_menu` VALUES ('2020-09-15 08:05:45.986899', '2020-09-15 08:05:45.986899', 18, 2, 3);
+INSERT INTO `sys_role_menu` VALUES ('2020-09-21 06:29:29.232489', '2020-09-21 06:29:29.232489', 19, 2, 8);
+INSERT INTO `sys_role_menu` VALUES ('2020-09-21 06:29:29.232489', '2020-09-21 06:29:29.232489', 20, 2, 10);
+INSERT INTO `sys_role_menu` VALUES ('2020-09-21 06:29:29.232489', '2020-09-21 06:29:29.232489', 21, 2, 7);
+INSERT INTO `sys_role_menu` VALUES ('2020-10-12 03:22:35.019793', '2020-10-12 03:22:35.019793', 22, 2, 45);
+INSERT INTO `sys_role_menu` VALUES ('2020-10-12 03:22:35.019793', '2020-10-12 03:22:35.019793', 23, 2, 46);

+ 4 - 0
db/seed/sys_task.sql

@@ -0,0 +1,4 @@
+INSERT INTO `sys_task` VALUES ('2020-10-19 08:53:44.732338', '2020-10-23 04:27:47.000000', 1, '定时清空请求追踪日志', 'admin.sys.reqLog.clear', 0, 1, NULL, NULL, 0, '0 0 3 ? * 1', 1000, '', '{\"count\":1,\"cron\":\"0 0 3 ? * 1\",\"jobId\":1}', '');
+INSERT INTO `sys_task` VALUES ('2020-10-19 08:54:42.760785', '2020-10-23 04:27:47.000000', 2, '定时清空登录日志', 'admin.sys.loginLog.clear', 0, 1, NULL, NULL, 0, '0 0 3 ? * 1', 0, '', '{\"count\":1,\"cron\":\"0 0 3 ? * 1\",\"jobId\":2}', '');
+INSERT INTO `sys_task` VALUES ('2020-10-19 08:55:06.050711', '2020-10-23 04:27:47.000000', 3, '定时清空任务日志', 'admin.sys.taskLog.clear', 0, 1, NULL, NULL, 0, '0 0 3 ? * 1', 0, '', '{\"count\":1,\"cron\":\"0 0 3 ? * 1\",\"jobId\":3}', '');
+INSERT INTO `sys_task` VALUES ('2020-10-22 07:48:10.089919', '2020-10-23 04:21:10.000000', 4, '获取逆天邪神最新章节', 'reptile.book.getZonghengBookLastchapter', 1, 0, NULL, NULL, 3, '', 20000, '{\"id\":\"408586\",\"emails\":\"qa894178522@qq.com\"}', '', '');

Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor