支持打包成二进制

This commit is contained in:
COOL 2025-01-11 22:50:43 +08:00
parent a737d24d0e
commit d6ba2d897b
59 changed files with 6806 additions and 56 deletions

View File

@ -1,7 +1,30 @@
{
"extends": "./node_modules/mwts/",
"ignorePatterns": ["node_modules", "dist", "test", "jest.config.js", "typings"],
"ignorePatterns": [
"node_modules",
"dist",
"test",
"jest.config.js",
"typings",
"public/**/**",
"view/**/**",
"packages"
],
"env": {
"jest": true
"jest": true
},
"rules": {
"@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/no-unused-vars": "off",
"@typescript-eslint/ban-ts-comment": "off",
"node/no-extraneous-import": "off",
"no-empty": "off",
"node/no-extraneous-require": "off",
"node/no-unpublished-import": "off",
"eqeqeq": "off",
"node/no-unsupported-features/node-builtins": "off",
"@typescript-eslint/ban-types": "off",
"no-control-regex": "off",
"prefer-const": "off"
}
}
}

4
.gitattributes vendored Normal file
View File

@ -0,0 +1,4 @@
*.js text eol=lf
*.json text eol=lf
*.ts text eol=lf
*.code-snippets text eol=lf

8
.gitignore vendored
View File

@ -1,13 +1,21 @@
logs/
cache/
npm-debug.log
yarn-error.log
node_modules/
package-lock.json
yarn.lock
coverage/
dist/
.idea/
run/
build/
.DS_Store
launch.json
*.sw*
*.un~
.tsbuildinfo
.tsbuildinfo.*
data/*
pnpm-lock.yaml
public/uploads/*

33
Dockerfile Normal file
View File

@ -0,0 +1,33 @@
FROM node:lts-alpine
WORKDIR /app
# 配置alpine国内镜像加速
RUN sed -i "s@http://dl-cdn.alpinelinux.org/@https://repo.huaweicloud.com/@g" /etc/apk/repositories
# 安装tzdata,默认的alpine基础镜像不包含时区组件安装后可通过TZ环境变量配置时区
RUN apk add --no-cache tzdata
# 设置时区为中国东八区这里的配置可以被docker-compose.yml或docker run时指定的时区覆盖
ENV TZ="Asia/Shanghai"
# 如果各公司有自己的私有源可以替换registry地址,如使用官方源注释下一行
RUN npm config set registry https://registry.npmmirror.com
# 复制package.json
COPY package.json ./package.json
# 安装依赖
RUN npm install
# 构建项目
COPY . .
RUN npm run build
# 删除开发期依赖
RUN rm -rf node_modules && rm package-lock.json
# 安装生产环境依赖
RUN npm install
# 如果端口更换,这边可以更新一下
EXPOSE 8001
CMD ["npm", "run", "start"]

View File

@ -1,2 +0,0 @@
> pkg@5.8.1
> Fetching base Node.js binaries to PKG_CACHE_PATH

40
docker-compose.yml Normal file
View File

@ -0,0 +1,40 @@
# 本地数据库环境
# 数据存放在当前目录下的 data里
# 推荐使用安装了docker扩展的vscode打开目录 在本文件上右键可以快速启动,停止
# 如不需要相关容器开机自启动,可注释掉 restart: always
# 如遇端口冲突 可调整ports下 :前面的端口号
version: "3.1"
services:
coolDB:
image: mysql
command:
--default-authentication-plugin=mysql_native_password
--sql_mode=STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION
--group_concat_max_len=102400
restart: always
volumes:
- ./data/mysql/:/var/lib/mysql/
environment:
TZ: Asia/Shanghai # 指定时区
MYSQL_ROOT_PASSWORD: "123456" # 配置root用户密码
MYSQL_DATABASE: "cool" # 业务库名
MYSQL_USER: "root" # 业务库用户名
MYSQL_PASSWORD: "123456" # 业务库密码
networks:
- cool
ports:
- 3306:3306
coolRedis:
image: redis
#command: --requirepass "12345678" # redis库密码,不需要密码注释本行
restart: always
environment:
TZ: Asia/Shanghai # 指定时区
volumes:
- ./data/redis/:/data/
networks:
- cool
ports:
- 6379:6379

View File

@ -1,23 +1,39 @@
{
"name": "cool-admin-midway",
"version": "8.0.0",
"description": "一个很酷的Ai开发快速框架",
"description": "一个很酷的Ai快速开发框架",
"private": true,
"dependencies": {
"@cool-midway/core": "file:/Users/ap/Documents/src/admin/midway-packages/core",
"@midwayjs/bootstrap": "^3.19.3",
"@midwayjs/cache-manager": "^3.19.3",
"@midwayjs/core": "^3.19.0",
"@midwayjs/cron": "^3.19.2",
"@midwayjs/cross-domain": "^3.19.3",
"@midwayjs/info": "^3.19.2",
"@midwayjs/koa": "^3.19.2",
"@midwayjs/logger": "^3.4.2",
"@midwayjs/validate": "^3.19.2"
"@midwayjs/static-file": "^3.19.3",
"@midwayjs/typeorm": "^3.19.2",
"@midwayjs/upload": "^3.19.3",
"@midwayjs/validate": "^3.19.2",
"@midwayjs/view-ejs": "^3.19.2",
"jsonwebtoken": "^9.0.2",
"lodash": "^4.17.21",
"md5": "^2.3.0",
"moment": "^2.30.1",
"mysql2": "^3.12.0",
"sharp": "0.32.6",
"svg-captcha": "^1.4.0",
"typeorm": "^0.3.20",
"uuid": "^11.0.5",
"ws": "^8.18.0"
},
"devDependencies": {
"@midwayjs/bundle-helper": "^1.3.0",
"@midwayjs/mock": "^3.19.2",
"@types/jest": "^29.5.14",
"@types/node": "22",
"@vercel/ncc": "^0.38.3",
"cross-env": "^7.0.3",
"jest": "^29.7.0",
"mwts": "^1.3.0",
@ -31,23 +47,28 @@
},
"scripts": {
"start": "NODE_ENV=production node ./bootstrap.js",
"dev": "cool check && cross-env NODE_ENV=local mwtsc --watch --run @midwayjs/mock/app.js",
"entity": "cool entity",
"dev": "cool check entity && bundle && cross-env NODE_ENV=local mwtsc --watch --run @midwayjs/mock/app.js",
"test": "cross-env NODE_ENV=unittest jest",
"cov": "jest --coverage",
"lint": "mwts check",
"lint:fix": "mwts fix",
"ci": "npm run cov",
"build": "mwtsc --cleanOutDir",
"bundle": "bundle && npm run build && ncc build bootstrap.js -o build",
"bundle_start": "NODE_ENV=production node ./build/index.js",
"pkg": "pkg . -d > build/pkg.log"
"build": "cool entity && bundle && mwtsc --cleanOutDir",
"build:obfuscate": "cool entity && bundle && mwtsc --cleanOutDir && cool obfuscate",
"pkg": "npm run build && pkg . -d > build/pkg.log",
"pm2:start": "pm2 start ./bootstrap.js -i 1 --name cool-admin",
"pm2:stop": "pm2 stop cool-admin & pm2 delete cool-admin"
},
"bin": "./bootstrap.js",
"pkg": {
"scripts": "dist/**/*.js",
"assets": [],
"scripts": "dist/**/*",
"assets": [
"public/**/*"
],
"targets": [
"node18-macos-x64"
"node18-macos-x64",
"node18-win-x64"
],
"outputPath": "build"
},

2585
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

92
public/css/welcome.css Normal file
View File

@ -0,0 +1,92 @@
body {
display: flex;
min-height: 100vh;
margin: 0;
justify-content: center;
align-items: center;
text-align: center;
background: #222;
overflow-y: hidden;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.footer-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
color: #6ee1f5;
padding: 10px 0 20px 0;
text-align: center;
opacity: 0;
animation: fadeIn 5s forwards;
background: #222;
}
.link {
color: #6ee1f5;
}
.reveal {
position: relative;
display: flex;
color: #6ee1f5;
font-size: 2em;
font-family: Raleway, sans-serif;
letter-spacing: 3px;
text-transform: uppercase;
white-space: pre;
}
.reveal span {
opacity: 0;
transform: scale(0);
animation: fadeIn 2.4s forwards;
}
.reveal::before, .reveal::after {
position: absolute;
content: "";
top: 0;
bottom: 0;
width: 2px;
height: 100%;
background: white;
opacity: 0;
transform: scale(0);
}
.reveal::before {
left: 50%;
animation: slideLeft 1.5s cubic-bezier(0.7, -0.6, 0.3, 1.5) forwards;
}
.reveal::after {
right: 50%;
animation: slideRight 1.5s cubic-bezier(0.7, -0.6, 0.3, 1.5) forwards;
}
@keyframes fadeIn {
to {
opacity: 1;
transform: scale(1);
}
}
@keyframes slideLeft {
to {
left: -6%;
opacity: 1;
transform: scale(0.9);
}
}
@keyframes slideRight {
to {
right: -6%;
opacity: 1;
transform: scale(0.9);
}
}

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

14
public/js/welcome.js Normal file
View File

@ -0,0 +1,14 @@
const duration = 0.8;
const delay = 0.3;
// eslint-disable-next-line no-undef
const revealText = document.querySelector('.reveal');
const letters = revealText.textContent.split('');
revealText.textContent = '';
const middle = letters.filter(e => e !== ' ').length / 2;
letters.forEach((letter, i) => {
// eslint-disable-next-line no-undef
const span = document.createElement('span');
span.textContent = letter;
span.style.animationDelay = `${delay + Math.abs(i - middle) * 0.1}s`;
revealText.append(span);
});

30
public/welcome.html Normal file
View File

@ -0,0 +1,30 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>COOL-AMIND 一个很酷的后台权限管理系统</title>
<meta name="keywords" content="cool-admin后台管理系统vueelement-uinodejs" />
<meta name="description" content="element-ui、midway.js、mysql、redis、node.js、前后端分离、权限管理、快速开发 COOL-AMIND 一个很酷的后台权限管理系统" />
<link rel="stylesheet" href="public/css/welcome.css">
<link rel="shortcut icon" href="public/favicon.ico" type="image/x-icon" />
<body>
<div class="reveal">HELLO COOL-ADMIN AI快速开发框架</div>
<!-- 添加底部说明 -->
<div class="footer-bar">
<span>当前版本v8.x</span>
<div class="notice">
<span>本项目采用前后端分离架构,这是后端服务。</span>
<span>前端项目请访问:</span>
<a class="link" target="_blank" href="https://vue.cool-admin.com/">COOL-ADMIN 前端</a>
</div>
</div>
<script src="public/js/welcome.js"></script>
</body>
</html>

View File

@ -1,6 +1,5 @@
import { Inject, Provide } from '@midwayjs/decorator';
import { Inject, Provide } from '@midwayjs/core';
import { Context } from '@midwayjs/koa';
import * as _ from 'lodash';
import * as moment from 'moment';
/**

View File

@ -1,9 +1,73 @@
import { CoolConfig } from '@cool-midway/core';
import { MidwayConfig } from '@midwayjs/core';
import { CoolCacheStore } from '@cool-midway/core';
import * as path from 'path';
// redis缓存
// import { redisStore } from 'cache-manager-ioredis-yet';
export default {
// use for cookie sign key, should change to your own and keep security
keys: '1736423310302_5051',
keys: '673dcd50-f95d-4109-b69d-aa80df64098e',
koa: {
port: 7001,
port: 8001,
},
// 静态文件配置
staticFile: {
buffer: true,
dirs: {
default: {
prefix: '/public',
dir: path.join(__dirname, '..', '..', 'public'),
},
welcome: {
prefix: '/',
dir: path.join(__dirname, '..', '..', 'public'),
alias: {
'/': '/welcome.html',
},
},
},
},
// 文件上传
upload: {
fileSize: '200mb',
whitelist: null,
},
// 缓存 可切换成其他缓存如redis http://www.midwayjs.org/docs/extensions/caching
cacheManager: {
clients: {
default: {
store: CoolCacheStore,
options: {
path: 'cache',
ttl: 0,
},
},
},
},
// cacheManager: {
// clients: {
// default: {
// store: redisStore,
// options: {
// port: 6379,
// host: '127.0.0.1',
// password: '',
// ttl: 0,
// db: 0,
// },
// },
// },
// },
cool: {
// 已经插件化,本地文件上传查看 plugin/config.ts其他云存储查看对应插件的使用
file: {},
// crud配置
crud: {
// 插入模式save不会校验字段(允许传入不存在的字段)insert会校验字段
upsert: 'save',
// 软删除
softDelete: true,
},
} as CoolConfig,
} as MidwayConfig;

View File

@ -1,3 +1,45 @@
import { CoolConfig } from '@cool-midway/core';
import { MidwayConfig } from '@midwayjs/core';
import { entities } from '../entities';
export default {} as MidwayConfig;
/**
* npm run dev
*/
export default {
typeorm: {
dataSource: {
default: {
type: 'mysql',
host: '192.168.0.119',
port: 3306,
username: 'root',
password: '123456',
database: 'cool',
// 自动建表 注意:线上部署的时候不要使用,有可能导致数据丢失
synchronize: true,
// 打印日志
logging: false,
// 字符集
charset: 'utf8mb4',
// 是否开启缓存
cache: true,
// 实体路径
entities,
// 扩展配置
extra: {
keepAliveInitialDelay: 10000,
},
},
},
},
cool: {
// 实体与路径跟生成代码、前端请求、swagger文档相关 注意:线上不建议开启,以免暴露敏感信息
eps: true,
// 是否自动导入模块数据库
initDB: true,
// 判断是否初始化的方式
initJudge: 'db',
// 是否自动导入模块菜单
initMenu: true,
} as CoolConfig,
} as MidwayConfig;

38
src/config/config.prod.ts Normal file
View File

@ -0,0 +1,38 @@
import { CoolConfig } from '@cool-midway/core';
import { MidwayConfig } from '@midwayjs/core';
import { entities } from '../entities';
/**
* npm run prod
*/
export default {
typeorm: {
dataSource: {
default: {
type: 'mysql',
host: '192.168.0.119',
port: 3306,
username: 'root',
password: '123456',
database: 'cool',
// 自动建表 注意:线上部署的时候不要使用,有可能导致数据丢失
synchronize: false,
// 打印日志
logging: false,
// 字符集
charset: 'utf8mb4',
// 是否开启缓存
cache: true,
// 实体路径
entities,
// 扩展配置
extra: {
keepAliveInitialDelay: 10000,
},
},
},
},
cool: {
// 是否自动导入数据库,生产环境不建议开,用本地的数据库手动初始化
initDB: false,
} as CoolConfig,
} as MidwayConfig;

View File

@ -1,38 +1,60 @@
import { Configuration, App } from '@midwayjs/core';
import * as orm from '@midwayjs/typeorm';
import {
Configuration,
App,
IMidwayApplication,
Inject,
ILogger,
} from '@midwayjs/core';
import * as koa from '@midwayjs/koa';
// import * as crossDomain from '@midwayjs/cross-domain';
import * as validate from '@midwayjs/validate';
import * as info from '@midwayjs/info';
import { ReportMiddleware } from './middleware/report.middleware';
import * as staticFile from '@midwayjs/static-file';
import * as cron from '@midwayjs/cron';
import * as DefaultConfig from './config/config.default';
import * as LocalConfig from './config/config.local';
import * as ProdConfig from './config/config.prod';
import * as cool from '@cool-midway/core';
import * as upload from '@midwayjs/upload';
@Configuration({
imports: [
// https://koajs.com/
koa,
// 是否开启跨域(注:顺序不能乱放!!!) http://www.midwayjs.org/docs/extensions/cross_domain
// crossDomain,
// 静态文件托管 https://midwayjs.org/docs/extensions/static_file
staticFile,
// orm https://midwayjs.org/docs/extensions/orm
orm,
// 参数验证 https://midwayjs.org/docs/extensions/validate
validate,
// 本地任务 http://www.midwayjs.org/docs/extensions/cron
cron,
// 文件上传
upload,
// cool-admin 官方组件 https://cool-js.com
cool,
{
component: info,
enabledEnvironment: ['local'],
enabledEnvironment: ['local', 'prod'],
},
],
importConfigs: [
{
default: DefaultConfig,
local: LocalConfig,
prod: ProdConfig,
},
],
})
export class MainConfiguration {
@App('koa')
app: koa.Application;
@App()
app: IMidwayApplication;
async onReady() {
// add middleware
this.app.useMiddleware([ReportMiddleware]);
// add filter
// this.app.useFilter([NotFoundFilter, DefaultErrorFilter]);
}
@Inject()
logger: ILogger;
async onReady() {}
}

23
src/entities.ts Normal file
View File

@ -0,0 +1,23 @@
// 自动生成的文件,请勿手动修改
import * as entity0 from './modules/base/entity/sys/user_role';
import * as entity1 from './modules/base/entity/sys/user';
import * as entity2 from './modules/base/entity/sys/role_menu';
import * as entity3 from './modules/base/entity/sys/role_department';
import * as entity4 from './modules/base/entity/sys/role';
import * as entity5 from './modules/base/entity/sys/param';
import * as entity6 from './modules/base/entity/sys/menu';
import * as entity7 from './modules/base/entity/sys/log';
import * as entity8 from './modules/base/entity/sys/department';
import * as entity9 from './modules/base/entity/sys/conf';
export const entities = [
...Object.values(entity0),
...Object.values(entity1),
...Object.values(entity2),
...Object.values(entity3),
...Object.values(entity4),
...Object.values(entity5),
...Object.values(entity6),
...Object.values(entity7),
...Object.values(entity8),
...Object.values(entity9),
];

View File

@ -1,6 +1,44 @@
/** This file generated by @midwayjs/bundle-helper */
export { MainConfiguration as Configuration } from './configuration';
export { MainConfiguration as Configuration } from './configuration';
export * from './comm/utils';
export * from './config/config.default';
export * from './modules/base/entity/sys/user_role';
export * from './modules/base/entity/sys/user';
export * from './modules/base/entity/sys/role_menu';
export * from './modules/base/entity/sys/role_department';
export * from './modules/base/entity/sys/role';
export * from './modules/base/entity/sys/param';
export * from './modules/base/entity/sys/menu';
export * from './modules/base/entity/sys/log';
export * from './modules/base/entity/sys/department';
export * from './modules/base/entity/sys/conf';
export * from './entities';
export * from './config/config.local';
export * from './config/config.prod';
export * from './interface';
export * from './modules/base/service/sys/conf';
export * from './modules/base/service/sys/log';
export * from './modules/base/middleware/log';
export * from './modules/base/middleware/authority';
export * from './modules/base/config';
export * from './modules/base/dto/login';
export * from './modules/base/service/sys/data';
export * from './modules/base/service/sys/menu';
export * from './modules/base/service/sys/department';
export * from './modules/base/service/sys/perms';
export * from './modules/base/service/sys/role';
export * from './modules/base/service/sys/login';
export * from './modules/base/service/sys/user';
export * from './modules/base/controller/admin/comm';
export * from './modules/base/service/sys/param';
export * from './modules/base/controller/admin/open';
export * from './modules/base/controller/admin/sys/department';
export * from './modules/base/controller/admin/sys/log';
export * from './modules/base/controller/admin/sys/menu';
export * from './modules/base/controller/admin/sys/param';
export * from './modules/base/controller/admin/sys/role';
export * from './modules/base/controller/admin/sys/user';
export * from './modules/base/controller/app/comm';
export * from './modules/base/event/app';
export * from './modules/base/event/menu';
export * from './modules/base/job/log';

View File

@ -0,0 +1,35 @@
import { BaseLogMiddleware } from './middleware/log';
import { BaseAuthorityMiddleware } from './middleware/authority';
import { ModuleConfig } from '@cool-midway/core';
/**
*
*/
export default () => {
return {
// 模块名称
name: '权限管理',
// 模块描述
description: '基础的权限管理功能,包括登录,权限校验',
// 中间件
globalMiddlewares: [BaseAuthorityMiddleware, BaseLogMiddleware],
// 模块加载顺序默认为0值越大越优先加载
order: 10,
// app参数配置允许读取的key
allowKeys: [],
// jwt 生成解密token的
jwt: {
// 单点登录
sso: false,
// 注意: 最好重新修改,防止破解
secret: 'e43675a9-0ce5-4d50-8dd0-549dff19334c',
// token
token: {
// 2小时过期需要用刷新token
expire: 2 * 3600,
// 15天内如果没操作过就需要重新登录
refreshExpire: 24 * 3600 * 15,
},
},
} as ModuleConfig;
};

View File

@ -0,0 +1,99 @@
import {
BaseController,
CoolController,
CoolTag,
CoolUrlTag,
TagTypes,
} from '@cool-midway/core';
import { ALL, Body, Get, Inject, Post, Provide } from '@midwayjs/core';
import { Context } from '@midwayjs/koa';
// import { PluginService } from '../../../plugin/service/info';
import { BaseSysUserEntity } from '../../entity/sys/user';
import { BaseSysLoginService } from '../../service/sys/login';
import { BaseSysPermsService } from '../../service/sys/perms';
import { BaseSysUserService } from '../../service/sys/user';
/**
* Base
*/
@CoolUrlTag()
@Provide()
@CoolController()
export class BaseCommController extends BaseController {
@Inject()
baseSysUserService: BaseSysUserService;
@Inject()
baseSysPermsService: BaseSysPermsService;
@Inject()
baseSysLoginService: BaseSysLoginService;
@Inject()
ctx: Context;
// @Inject()
// pluginService: PluginService;
/**
*
*/
@Get('/person', { summary: '个人信息' })
async person() {
return this.ok(
await this.baseSysUserService.person(this.ctx.admin?.userId)
);
}
/**
*
*/
@Post('/personUpdate', { summary: '修改个人信息' })
async personUpdate(@Body(ALL) user: BaseSysUserEntity) {
await this.baseSysUserService.personUpdate(user);
return this.ok();
}
/**
*
*/
@Get('/permmenu', { summary: '权限与菜单' })
async permmenu() {
return this.ok(
await this.baseSysPermsService.permmenu(this.ctx.admin.roleIds)
);
}
/**
*
*/
@Post('/upload', { summary: '文件上传' })
async upload() {
// const file = await this.pluginService.getInstance('upload');
// return this.ok(await file.upload(this.ctx));
}
/**
*
*/
@Get('/uploadMode', { summary: '文件上传模式' })
async uploadMode() {
// const file = await this.pluginService.getInstance('upload');
// return this.ok(await file.getMode());
}
/**
* 退
*/
@Post('/logout', { summary: '退出' })
async logout() {
await this.baseSysLoginService.logout();
return this.ok();
}
@CoolTag(TagTypes.IGNORE_TOKEN)
@Get('/program', { summary: '编程' })
async program() {
return this.ok('Node');
}
}

View File

@ -0,0 +1,99 @@
import { Provide, Body, Inject, Post, Get, Query } from '@midwayjs/core';
import {
CoolController,
BaseController,
CoolEps,
CoolUrlTag,
CoolTag,
TagTypes,
RESCODE,
} from '@cool-midway/core';
import { LoginDTO } from '../../dto/login';
import { BaseSysLoginService } from '../../service/sys/login';
import { BaseSysParamService } from '../../service/sys/param';
import { Context } from '@midwayjs/koa';
import { Validate } from '@midwayjs/validate';
/**
*
*/
@Provide()
@CoolController({ description: '开放接口' })
@CoolUrlTag()
export class BaseOpenController extends BaseController {
@Inject()
baseSysLoginService: BaseSysLoginService;
@Inject()
baseSysParamService: BaseSysParamService;
@Inject()
ctx: Context;
@Inject()
eps: CoolEps;
/**
*
* @returns
*/
@CoolTag(TagTypes.IGNORE_TOKEN)
@Get('/eps', { summary: '实体信息与路径' })
public async getEps() {
return this.ok(this.eps.admin);
}
/**
* key获得网页内容()
*/
@CoolTag(TagTypes.IGNORE_TOKEN)
@Get('/html', { summary: '获得网页内容的参数值' })
async htmlByKey(@Query('key') key: string) {
this.ctx.body = await this.baseSysParamService.htmlByKey(key);
}
/**
*
* @param login
*/
@CoolTag(TagTypes.IGNORE_TOKEN)
@Post('/login', { summary: '登录' })
@Validate()
async login(@Body() login: LoginDTO) {
return this.ok(await this.baseSysLoginService.login(login));
}
/**
*
*/
@CoolTag(TagTypes.IGNORE_TOKEN)
@Get('/captcha', { summary: '验证码' })
async captcha(
@Query('type') type: string,
@Query('width') width: number,
@Query('height') height: number,
@Query('color') color: string
) {
return this.ok(
await this.baseSysLoginService.captcha(type, width, height, color)
);
}
/**
* token
*/
@CoolTag(TagTypes.IGNORE_TOKEN)
@Get('/refreshToken', { summary: '刷新token' })
async refreshToken(@Query('refreshToken') refreshToken: string) {
try {
const token = await this.baseSysLoginService.refreshToken(refreshToken);
return this.ok(token);
} catch (e) {
this.ctx.status = 401;
this.ctx.body = {
code: RESCODE.COMMFAIL,
message: '登录失效~',
};
}
}
}

View File

@ -0,0 +1,27 @@
import { ALL, Body, Inject, Post, Provide } from '@midwayjs/core';
import { CoolController, BaseController } from '@cool-midway/core';
import { BaseSysDepartmentEntity } from '../../../entity/sys/department';
import { BaseSysDepartmentService } from '../../../service/sys/department';
/**
*
*/
@Provide()
@CoolController({
api: ['add', 'delete', 'update', 'list'],
entity: BaseSysDepartmentEntity,
service: BaseSysDepartmentService,
})
export class BaseDepartmentController extends BaseController {
@Inject()
baseDepartmentService: BaseSysDepartmentService;
/**
*
*/
@Post('/order', { summary: '排序' })
async order(@Body(ALL) params: any) {
await this.baseDepartmentService.order(params);
return this.ok();
}
}

View File

@ -0,0 +1,64 @@
import { Provide, Post, Inject, Body, Get } from '@midwayjs/core';
import { CoolController, BaseController } from '@cool-midway/core';
import { BaseSysLogEntity } from '../../../entity/sys/log';
import { BaseSysUserEntity } from '../../../entity/sys/user';
import { BaseSysConfService } from '../../../service/sys/conf';
import { BaseSysLogService } from '../../../service/sys/log';
/**
*
*/
@Provide()
@CoolController({
api: ['page'],
entity: BaseSysLogEntity,
urlTag: {
name: 'a',
url: ['add'],
},
pageQueryOp: {
keyWordLikeFields: ['b.name', 'a.action', 'a.ip'],
select: ['a.*', 'b.name'],
join: [
{
entity: BaseSysUserEntity,
alias: 'b',
condition: 'a.userId = b.id',
type: 'leftJoin',
},
],
},
})
export class BaseSysLogController extends BaseController {
@Inject()
baseSysLogService: BaseSysLogService;
@Inject()
baseSysConfService: BaseSysConfService;
/**
*
*/
@Post('/clear', { summary: '清理' })
public async clear() {
await this.baseSysLogService.clear(true);
return this.ok();
}
/**
*
*/
@Post('/setKeep', { summary: '日志保存时间' })
public async setKeep(@Body('value') value: number) {
await this.baseSysConfService.updateVaule('logKeep', value);
return this.ok();
}
/**
*
*/
@Get('/getKeep', { summary: '获得日志保存时间' })
public async getKeep() {
return this.ok(await this.baseSysConfService.getValue('logKeep'));
}
}

View File

@ -0,0 +1,46 @@
import { Body, Inject, Post, Provide } from '@midwayjs/core';
import { CoolController, BaseController } from '@cool-midway/core';
import { BaseSysMenuEntity } from '../../../entity/sys/menu';
import { BaseSysMenuService } from '../../../service/sys/menu';
/**
*
*/
@Provide()
@CoolController({
api: ['add', 'delete', 'update', 'info', 'list', 'page'],
entity: BaseSysMenuEntity,
service: BaseSysMenuService,
})
export class BaseSysMenuController extends BaseController {
@Inject()
baseSysMenuService: BaseSysMenuService;
@Post('/parse', { summary: '解析' })
async parse(
@Body('entity') entity: string,
@Body('controller') controller: string,
@Body('module') module: string
) {
return this.ok(
await this.baseSysMenuService.parse(entity, controller, module)
);
}
@Post('/create', { summary: '创建代码' })
async create(@Body() body) {
await this.baseSysMenuService.create(body);
return this.ok();
}
@Post('/export', { summary: '导出' })
async export(@Body('ids') ids: number[]) {
return this.ok(await this.baseSysMenuService.export(ids));
}
@Post('/import', { summary: '导入' })
async import(@Body('menus') menus: any[]) {
await this.baseSysMenuService.import(menus);
return this.ok();
}
}

View File

@ -0,0 +1,34 @@
import { Get, Inject, Provide, Query } from '@midwayjs/core';
import { CoolController, BaseController } from '@cool-midway/core';
import { BaseSysParamEntity } from '../../../entity/sys/param';
import { BaseSysParamService } from '../../../service/sys/param';
import { Context } from '@midwayjs/koa';
/**
*
*/
@Provide()
@CoolController({
api: ['add', 'delete', 'update', 'info', 'page'],
entity: BaseSysParamEntity,
service: BaseSysParamService,
pageQueryOp: {
keyWordLikeFields: ['name', 'keyName'],
fieldEq: ['dataType'],
},
})
export class BaseSysParamController extends BaseController {
@Inject()
baseSysParamService: BaseSysParamService;
@Inject()
ctx: Context;
/**
* key获得网页内容()
*/
@Get('/html', { summary: '获得网页内容的参数值' })
async htmlByKey(@Query('key') key: string) {
this.ctx.body = await this.baseSysParamService.htmlByKey(key);
}
}

View File

@ -0,0 +1,38 @@
import { Provide } from '@midwayjs/core';
import { CoolController, BaseController } from '@cool-midway/core';
import { Context } from 'vm';
import { BaseSysRoleEntity } from '../../../entity/sys/role';
import { BaseSysRoleService } from '../../../service/sys/role';
/**
*
*/
@Provide()
@CoolController({
api: ['add', 'delete', 'update', 'info', 'list', 'page'],
entity: BaseSysRoleEntity,
service: BaseSysRoleService,
// 新增的时候插入当前用户ID
insertParam: async (ctx: Context) => {
return {
userId: ctx.admin.userId,
};
},
pageQueryOp: {
keyWordLikeFields: ['a.name', 'a.label'],
where: async (ctx: Context) => {
const { userId, roleIds, username } = ctx.admin;
return [
// 超级管理员的角色不展示
['label != :label', { label: 'admin' }],
// 如果不是超管,只能看到自己新建的或者自己有的角色
[
`(userId=:userId or id in (${roleIds.join(',')}))`,
{ userId },
username !== 'admin',
],
];
},
},
})
export class BaseSysRoleController extends BaseController {}

View File

@ -0,0 +1,30 @@
import { Body, Inject, Post, Provide } from '@midwayjs/core';
import { CoolController, BaseController } from '@cool-midway/core';
import { BaseSysUserEntity } from '../../../entity/sys/user';
import { BaseSysUserService } from '../../../service/sys/user';
/**
*
*/
@Provide()
@CoolController({
api: ['add', 'delete', 'update', 'info', 'list', 'page'],
entity: BaseSysUserEntity,
service: BaseSysUserService,
})
export class BaseSysUserController extends BaseController {
@Inject()
baseSysUserService: BaseSysUserService;
/**
*
*/
@Post('/move', { summary: '移动部门' })
async move(
@Body('departmentId') departmentId: number,
@Body('userIds') userIds: []
) {
await this.baseSysUserService.move(departmentId, userIds);
return this.ok();
}
}

View File

@ -0,0 +1 @@
这里写对外的api接口

View File

@ -0,0 +1,72 @@
import { Provide, Inject, Get, Post, Query, Config } from '@midwayjs/core';
import {
CoolController,
BaseController,
CoolEps,
TagTypes,
CoolUrlTag,
CoolTag,
} from '@cool-midway/core';
import { Context } from '@midwayjs/koa';
import { BaseSysParamService } from '../../service/sys/param';
// import { PluginService } from '../../../plugin/service/info';
/**
*
*/
@CoolUrlTag()
@Provide()
@CoolController()
export class BaseAppCommController extends BaseController {
// @Inject()
// pluginService: PluginService;
@Inject()
ctx: Context;
@Config('module.base.allowKeys')
allowKeys: string[];
@Inject()
eps: CoolEps;
@Inject()
baseSysParamService: BaseSysParamService;
@CoolTag(TagTypes.IGNORE_TOKEN)
@Get('/param', { summary: '参数配置' })
async param(@Query('key') key: string) {
if (!this.allowKeys.includes(key)) {
return this.fail('非法操作');
}
return this.ok(await this.baseSysParamService.dataByKey(key));
}
/**
*
* @returns
*/
@CoolTag(TagTypes.IGNORE_TOKEN)
@Get('/eps', { summary: '实体信息与路径' })
public async getEps() {
return this.ok(this.eps.app);
}
/**
*
*/
@Post('/upload', { summary: '文件上传' })
async upload() {
// const file = await this.pluginService.getInstance('upload');
// return this.ok(await file.upload(this.ctx));
}
/**
*
*/
@Get('/uploadMode', { summary: '文件上传模式' })
async uploadMode() {
// const file = await this.pluginService.getInstance('upload');
// return this.ok(await file.getMode());
}
}

103
src/modules/base/db.json Normal file
View File

@ -0,0 +1,103 @@
{
"base_sys_param": [
{
"keyName": "rich",
"name": "富文本参数",
"data": "<h3><strong>这是一个富文本</strong></h3><p>xxx</p><p>xxxxxxxxxx</p><p><br></p>",
"dataType": 1,
"remark": null
},
{
"keyName": "json",
"name": "JSON参数",
"data": "{\n \"code\": 111233\n}",
"dataType": 0,
"remark": null
},
{
"keyName": "file",
"name": "文件",
"data": "",
"dataType": 2,
"remark": null
},
{
"keyName": "text",
"name": "测试",
"data": "这是一段字符串",
"dataType": 0,
"remark": null
}
],
"base_sys_conf": [
{
"cKey": "logKeep",
"cValue": "31"
},
{
"cKey": "recycleKeep",
"cValue": "31"
}
],
"base_sys_department": [
{
"id": 1,
"name": "COOL",
"parentId": null,
"orderNum": 0
},
{
"id": 11,
"name": "开发",
"parentId": 12,
"orderNum": 2
},
{
"id": 12,
"name": "测试",
"parentId": 1,
"orderNum": 1
},
{
"id": 13,
"name": "游客",
"parentId": 1,
"orderNum": 3
}
],
"base_sys_role": [
{
"id": 1,
"userId": "1",
"name": "超管",
"label": "admin",
"remark": "最高权限的角色",
"relevance": 1,
"menuIdList": "null",
"departmentIdList": "null"
}
],
"base_sys_user": [
{
"id": 1,
"departmentId": 1,
"name": "超级管理员",
"username": "admin",
"password": "e10adc3949ba59abbe56e057f20f883e",
"passwordV": 7,
"nickName": "管理员",
"headImg": "https://cool-js.com/admin/headimg.jpg",
"phone": "18000000000",
"email": "team@cool-js.com",
"status": 1,
"remark": "拥有最高权限的用户",
"socketId": null
}
],
"base_sys_user_role": [
{
"userId": 1,
"roleId": 1
}
]
}

View File

@ -0,0 +1,21 @@
import { Rule, RuleType } from '@midwayjs/validate';
/**
*
*/
export class LoginDTO {
// 用户名
@Rule(RuleType.string().required())
username: string;
// 密码
@Rule(RuleType.string().required())
password: string;
// 验证码ID
@Rule(RuleType.string().required())
captchaId: string;
// 验证码
@Rule(RuleType.required())
verifyCode: number;
}

View File

@ -0,0 +1,15 @@
import { Column, Index, Entity } from 'typeorm';
import { BaseEntity } from '@cool-midway/core';
/**
*
*/
@Entity('base_sys_conf')
export class BaseSysConfEntity extends BaseEntity {
@Index({ unique: true })
@Column({ comment: '配置键' })
cKey: string;
@Column({ comment: '配置值' })
cValue: string;
}

View File

@ -0,0 +1,19 @@
import { BaseEntity } from '@cool-midway/core';
import { Column, Entity } from 'typeorm';
/**
*
*/
@Entity('base_sys_department')
export class BaseSysDepartmentEntity extends BaseEntity {
@Column({ comment: '部门名称' })
name: string;
@Column({ comment: '上级部门ID', nullable: true })
parentId: number;
@Column({ comment: '排序', default: 0 })
orderNum: number;
// 父菜单名称
parentName: string;
}

View File

@ -0,0 +1,23 @@
import { BaseEntity } from '@cool-midway/core';
import { Column, Index, Entity } from 'typeorm';
/**
*
*/
@Entity('base_sys_log')
export class BaseSysLogEntity extends BaseEntity {
@Index()
@Column({ comment: '用户ID', nullable: true })
userId: number;
@Index()
@Column({ comment: '行为' })
action: string;
@Index()
@Column({ comment: 'ip', nullable: true })
ip: string;
@Column({ comment: '参数', nullable: true, type: 'json' })
params: string;
}

View File

@ -0,0 +1,47 @@
import { BaseEntity } from '@cool-midway/core';
import { Column, Entity } from 'typeorm';
/**
*
*/
@Entity('base_sys_menu')
export class BaseSysMenuEntity extends BaseEntity {
@Column({ comment: '父菜单ID', nullable: true })
parentId: number;
@Column({ comment: '菜单名称' })
name: string;
@Column({ comment: '菜单地址', nullable: true })
router: string;
@Column({ comment: '权限标识', type: 'text', nullable: true })
perms: string;
@Column({
comment: '类型 0-目录 1-菜单 2-按钮',
default: 0,
})
type: number;
@Column({ comment: '图标', nullable: true })
icon: string;
@Column({ comment: '排序', default: 0 })
orderNum: number;
@Column({ comment: '视图地址', nullable: true })
viewPath: string;
@Column({ comment: '路由缓存', default: true })
keepAlive: boolean;
@Column({ comment: '是否显示', default: true })
isShow: boolean;
// 父菜单名称
parentName: string;
// 子菜单
childMenus: any;
}

View File

@ -0,0 +1,27 @@
import { BaseEntity } from '@cool-midway/core';
import { Column, Index, Entity } from 'typeorm';
/**
*
*/
@Entity('base_sys_param')
export class BaseSysParamEntity extends BaseEntity {
@Index({ unique: true })
@Column({ comment: '键' })
keyName: string;
@Column({ comment: '名称' })
name: string;
@Column({ comment: '数据', type: 'text' })
data: string;
@Column({
comment: '数据类型 0-字符串 1-富文本 2-文件 ',
default: 0,
})
dataType: number;
@Column({ comment: '备注', nullable: true })
remark: string;
}

View File

@ -0,0 +1,31 @@
import { BaseEntity } from '@cool-midway/core';
import { Column, Index, Entity } from 'typeorm';
/**
*
*/
@Entity('base_sys_role')
export class BaseSysRoleEntity extends BaseEntity {
@Column({ comment: '用户ID' })
userId: string;
@Index({ unique: true })
@Column({ comment: '名称' })
name: string;
@Index({ unique: true })
@Column({ comment: '角色标签', nullable: true, length: 50 })
label: string;
@Column({ comment: '备注', nullable: true })
remark: string;
@Column({ comment: '数据权限是否关联上下级', default: false })
relevance: boolean;
@Column({ comment: '菜单权限', type: 'json' })
menuIdList: number[];
@Column({ comment: '部门权限', type: 'json' })
departmentIdList: number[];
}

View File

@ -0,0 +1,14 @@
import { BaseEntity } from '@cool-midway/core';
import { Column, Entity } from 'typeorm';
/**
*
*/
@Entity('base_sys_role_department')
export class BaseSysRoleDepartmentEntity extends BaseEntity {
@Column({ comment: '角色ID' })
roleId: number;
@Column({ comment: '部门ID' })
departmentId: number;
}

View File

@ -0,0 +1,14 @@
import { BaseEntity } from '@cool-midway/core';
import { Column, Entity } from 'typeorm';
/**
*
*/
@Entity('base_sys_role_menu')
export class BaseSysRoleMenuEntity extends BaseEntity {
@Column({ comment: '角色ID' })
roleId: number;
@Column({ comment: '菜单ID' })
menuId: number;
}

View File

@ -0,0 +1,54 @@
import { BaseEntity } from '@cool-midway/core';
import { Column, Index, Entity } from 'typeorm';
/**
*
*/
@Entity('base_sys_user')
export class BaseSysUserEntity extends BaseEntity {
@Index()
@Column({ comment: '部门ID', nullable: true })
departmentId: number;
@Column({ comment: '姓名', nullable: true })
name: string;
@Index({ unique: true })
@Column({ comment: '用户名', length: 100 })
username: string;
@Column({ comment: '密码' })
password: string;
@Column({
comment: '密码版本, 作用是改完密码让原来的token失效',
default: 1,
})
passwordV: number;
@Column({ comment: '昵称', nullable: true })
nickName: string;
@Column({ comment: '头像', nullable: true })
headImg: string;
@Index()
@Column({ comment: '手机', nullable: true, length: 20 })
phone: string;
@Column({ comment: '邮箱', nullable: true })
email: string;
@Column({ comment: '备注', nullable: true })
remark: string;
@Column({ comment: '状态 0-禁用 1-启用', default: 1 })
status: number;
// 部门名称
departmentName: string;
// 角色ID列表
roleIdList: number[];
@Column({ comment: 'socketId', nullable: true })
socketId: string;
}

View File

@ -0,0 +1,14 @@
import { BaseEntity } from '@cool-midway/core';
import { Column, Entity } from 'typeorm';
/**
*
*/
@Entity('base_sys_user_role')
export class BaseSysUserRoleEntity extends BaseEntity {
@Column({ comment: '用户ID' })
userId: number;
@Column({ comment: '角色ID' })
roleId: number;
}

View File

@ -0,0 +1,102 @@
import { CoolEvent, Event } from '@cool-midway/core';
import { App, Config, ILogger, Logger } from '@midwayjs/core';
import { IMidwayKoaApplication } from '@midwayjs/koa';
import * as fs from 'fs';
import * as path from 'path';
import { v1 as uuid } from 'uuid';
/**
* jwt.secret
*/
@CoolEvent()
export class BaseAppEvent {
@Logger()
coreLogger: ILogger;
@Config('module')
config;
@Config('keys')
configKeys;
@Config('koa.port')
port;
@App()
app: IMidwayKoaApplication;
@Event('onMenuInit')
async onMenuInit() {
if (this.app.getEnv() != 'local') return;
this.checkConfig();
this.checkKeys();
}
@Event('onServerReady')
async onServerReady() {
this.coreLogger.info(`服务启动成功,端口:${this.port}`);
}
/**
*
*/
async checkConfig() {
if (this.config.base.jwt.secret == 'cool-admin-xxxxxx') {
this.coreLogger.warn(
'\x1B[36m 检测到模块[base] jwt.secret 配置是默认值,请不要关闭!即将自动修改... \x1B[0m'
);
setTimeout(() => {
const filePath = path.join(
this.app.getBaseDir(),
'..',
'src',
'modules',
'base',
'config.ts'
);
// 替换文件内容
let fileData = fs.readFileSync(filePath, 'utf8');
const secret = uuid().replace(/-/g, '');
this.config.base.jwt.secret = secret;
fs.writeFileSync(
filePath,
fileData.replace('cool-admin-xxxxxx', secret)
);
this.coreLogger.info(
'\x1B[36m [cool:module:base] midwayjs cool module base auto modify jwt.secret\x1B[0m'
);
}, 6000);
}
}
/**
* keys
*/
async checkKeys() {
if (this.configKeys == 'cool-admin-keys-xxxxxx') {
this.coreLogger.warn(
'\x1B[36m 检测到基础配置[Keys] 是默认值,请不要关闭!即将自动修改... \x1B[0m'
);
setTimeout(() => {
const filePath = path.join(
this.app.getBaseDir(),
'..',
'src',
'config',
'config.default.ts'
);
// 替换文件内容
let fileData = fs.readFileSync(filePath, 'utf8');
const secret = uuid().replace(/-/g, '');
this.config.base.jwt.secret = secret;
fs.writeFileSync(
filePath,
fileData.replace('cool-admin-keys-xxxxxx', secret)
);
this.coreLogger.info(
'\x1B[36m [cool:module:base] midwayjs cool keys auto modify \x1B[0m'
);
}, 6000);
}
}
}

View File

@ -0,0 +1,40 @@
import { CoolEvent, CoolEventManager, Event } from '@cool-midway/core';
import { BaseSysMenuService } from '../service/sys/menu';
import {
App,
ILogger,
IMidwayApplication,
Inject,
Logger,
} from '@midwayjs/core';
/**
*
*/
@CoolEvent()
export class BaseMenuEvent {
@Logger()
coreLogger: ILogger;
@Inject()
baseSysMenuService: BaseSysMenuService;
@App()
app: IMidwayApplication;
@Inject()
coolEventManager: CoolEventManager;
@Event('onMenuImport')
async onMenuImport(datas) {
for (const module in datas) {
await this.baseSysMenuService.import(datas[module]);
this.coreLogger.info(
'\x1B[36m [cool:module:base] midwayjs cool module base import [' +
module +
'] module menu success \x1B[0m'
);
}
this.coolEventManager.emit('onMenuInit', {});
}
}

View File

@ -0,0 +1,25 @@
import { Job, IJob } from '@midwayjs/cron';
import { FORMAT, ILogger, Inject } from '@midwayjs/core';
import { BaseSysLogService } from '../service/sys/log';
/**
*
*/
@Job({
cronTime: FORMAT.CRONTAB.EVERY_DAY,
start: true,
})
export class BaseLogJob implements IJob {
@Inject()
baseSysLogService: BaseSysLogService;
@Inject()
logger: ILogger;
async onTick() {
this.logger.info('清除日志定时任务开始执行');
const startTime = Date.now();
await this.baseSysLogService.clear();
this.logger.info(`清除日志定时任务结束,耗时:${Date.now() - startTime}ms`);
}
}

875
src/modules/base/menu.json Normal file
View File

@ -0,0 +1,875 @@
[
{
"name": "系统管理",
"router": "/sys",
"perms": null,
"type": 0,
"icon": "icon-system",
"orderNum": 2,
"viewPath": null,
"keepAlive": true,
"isShow": true,
"childMenus": [
{
"name": "权限管理",
"router": null,
"perms": null,
"type": 0,
"icon": "icon-auth",
"orderNum": 1,
"viewPath": null,
"keepAlive": false,
"isShow": true,
"childMenus": [
{
"name": "菜单列表",
"router": "/sys/menu",
"perms": null,
"type": 1,
"icon": "icon-menu",
"orderNum": 2,
"viewPath": "modules/base/views/menu/index.vue",
"keepAlive": true,
"isShow": true,
"childMenus": [
{
"name": "新增",
"router": null,
"perms": "base:sys:menu:add",
"type": 2,
"icon": null,
"orderNum": 1,
"viewPath": null,
"keepAlive": false,
"isShow": true,
"childMenus": []
},
{
"name": "删除",
"router": null,
"perms": "base:sys:menu:delete",
"type": 2,
"icon": null,
"orderNum": 2,
"viewPath": null,
"keepAlive": false,
"isShow": true,
"childMenus": []
},
{
"name": "查询",
"router": null,
"perms": "base:sys:menu:page,base:sys:menu:list,base:sys:menu:info",
"type": 2,
"icon": null,
"orderNum": 4,
"viewPath": null,
"keepAlive": false,
"isShow": true,
"childMenus": []
},
{
"name": "参数",
"router": "/test/aa",
"perms": null,
"type": 1,
"icon": "icon-goods",
"orderNum": 0,
"viewPath": "modules/base/views/info.vue",
"keepAlive": true,
"isShow": true,
"childMenus": []
},
{
"name": "编辑",
"router": null,
"perms": "base:sys:menu:info,base:sys:menu:update",
"type": 2,
"icon": null,
"orderNum": 0,
"viewPath": null,
"keepAlive": true,
"isShow": true,
"childMenus": []
}
]
},
{
"name": "角色列表",
"router": "/sys/role",
"perms": null,
"type": 1,
"icon": "icon-dept",
"orderNum": 3,
"viewPath": "cool/modules/base/views/role.vue",
"keepAlive": true,
"isShow": true,
"childMenus": [
{
"name": "新增",
"router": null,
"perms": "base:sys:role:add",
"type": 2,
"icon": null,
"orderNum": 1,
"viewPath": null,
"keepAlive": false,
"isShow": true,
"childMenus": []
},
{
"name": "删除",
"router": null,
"perms": "base:sys:role:delete",
"type": 2,
"icon": null,
"orderNum": 2,
"viewPath": null,
"keepAlive": false,
"isShow": true,
"childMenus": []
},
{
"name": "修改",
"router": null,
"perms": "base:sys:role:update",
"type": 2,
"icon": null,
"orderNum": 3,
"viewPath": null,
"keepAlive": false,
"isShow": true,
"childMenus": []
},
{
"name": "查询",
"router": null,
"perms": "base:sys:role:page,base:sys:role:list,base:sys:role:info",
"type": 2,
"icon": null,
"orderNum": 4,
"viewPath": null,
"keepAlive": false,
"isShow": true,
"childMenus": []
}
]
},
{
"name": "用户列表",
"router": "/sys/user",
"perms": null,
"type": 1,
"icon": "icon-user",
"orderNum": 0,
"viewPath": "modules/base/views/user/index.vue",
"keepAlive": true,
"isShow": true,
"childMenus": [
{
"name": "部门列表",
"router": null,
"perms": "base:sys:department:list",
"type": 2,
"icon": null,
"orderNum": 0,
"viewPath": null,
"keepAlive": true,
"isShow": true,
"childMenus": []
},
{
"name": "新增部门",
"router": null,
"perms": "base:sys:department:add",
"type": 2,
"icon": null,
"orderNum": 0,
"viewPath": null,
"keepAlive": true,
"isShow": true,
"childMenus": []
},
{
"name": "更新部门",
"router": null,
"perms": "base:sys:department:update",
"type": 2,
"icon": null,
"orderNum": 0,
"viewPath": null,
"keepAlive": true,
"isShow": true,
"childMenus": []
},
{
"name": "删除部门",
"router": null,
"perms": "base:sys:department:delete",
"type": 2,
"icon": null,
"orderNum": 0,
"viewPath": null,
"keepAlive": true,
"isShow": true,
"childMenus": []
},
{
"name": "部门排序",
"router": null,
"perms": "base:sys:department:order",
"type": 2,
"icon": null,
"orderNum": 0,
"viewPath": null,
"keepAlive": true,
"isShow": true,
"childMenus": []
},
{
"name": "用户转移",
"router": null,
"perms": "base:sys:user:move",
"type": 2,
"icon": null,
"orderNum": 0,
"viewPath": null,
"keepAlive": true,
"isShow": true,
"childMenus": []
},
{
"name": "新增",
"router": null,
"perms": "base:sys:user:add",
"type": 2,
"icon": null,
"orderNum": 0,
"viewPath": null,
"keepAlive": true,
"isShow": true,
"childMenus": []
},
{
"name": "删除",
"router": null,
"perms": "base:sys:user:delete",
"type": 2,
"icon": null,
"orderNum": 0,
"viewPath": null,
"keepAlive": true,
"isShow": true,
"childMenus": []
},
{
"name": "修改",
"router": null,
"perms": "base:sys:user:delete,base:sys:user:update",
"type": 2,
"icon": null,
"orderNum": 0,
"viewPath": null,
"keepAlive": true,
"isShow": true,
"childMenus": []
},
{
"name": "查询",
"router": null,
"perms": "base:sys:user:page,base:sys:user:list,base:sys:user:info",
"type": 2,
"icon": null,
"orderNum": 0,
"viewPath": null,
"keepAlive": true,
"isShow": true,
"childMenus": []
}
]
}
]
},
{
"name": "参数配置",
"router": null,
"perms": null,
"type": 0,
"icon": "icon-params",
"orderNum": 3,
"viewPath": null,
"keepAlive": true,
"isShow": true,
"childMenus": [
{
"name": "参数列表",
"router": "/sys/param",
"perms": null,
"type": 1,
"icon": "icon-menu",
"orderNum": 0,
"viewPath": "cool/modules/base/views/param.vue",
"keepAlive": true,
"isShow": true,
"childMenus": [
{
"name": "新增",
"router": null,
"perms": "base:sys:param:add",
"type": 2,
"icon": null,
"orderNum": 0,
"viewPath": null,
"keepAlive": true,
"isShow": true,
"childMenus": []
},
{
"name": "修改",
"router": null,
"perms": "base:sys:param:info,base:sys:param:update",
"type": 2,
"icon": null,
"orderNum": 0,
"viewPath": null,
"keepAlive": true,
"isShow": true,
"childMenus": []
},
{
"name": "删除",
"router": null,
"perms": "base:sys:param:delete",
"type": 2,
"icon": null,
"orderNum": 0,
"viewPath": null,
"keepAlive": true,
"isShow": true,
"childMenus": []
},
{
"name": "查看",
"router": null,
"perms": "base:sys:param:page,base:sys:param:list,base:sys:param:info",
"type": 2,
"icon": null,
"orderNum": 0,
"viewPath": null,
"keepAlive": true,
"isShow": true,
"childMenus": []
}
]
}
]
},
{
"name": "监控管理",
"router": null,
"perms": null,
"type": 0,
"icon": "icon-monitor",
"orderNum": 9,
"viewPath": null,
"keepAlive": true,
"isShow": true,
"childMenus": [
{
"name": "请求日志",
"router": "/sys/log",
"perms": null,
"type": 1,
"icon": "icon-log",
"orderNum": 1,
"viewPath": "cool/modules/base/views/log.vue",
"keepAlive": true,
"isShow": true,
"childMenus": [
{
"name": "权限",
"router": null,
"perms": "base:sys:log:page,base:sys:log:clear,base:sys:log:getKeep,base:sys:log:setKeep",
"type": 2,
"icon": null,
"orderNum": 1,
"viewPath": null,
"keepAlive": false,
"isShow": true,
"childMenus": []
}
]
}
]
},
{
"name": "任务管理",
"router": null,
"perms": null,
"type": 0,
"icon": "icon-activity",
"orderNum": 9,
"viewPath": null,
"keepAlive": true,
"isShow": true,
"childMenus": [
{
"name": "任务列表",
"router": "/task/list",
"perms": null,
"type": 1,
"icon": "icon-menu",
"orderNum": 0,
"viewPath": "modules/task/views/list.vue",
"keepAlive": true,
"isShow": true,
"childMenus": [
{
"name": "权限",
"router": null,
"perms": "task:info:page,task:info:list,task:info:info,task:info:add,task:info:delete,task:info:update,task:info:stop,task:info:start,task:info:once,task:info:log",
"type": 2,
"icon": null,
"orderNum": 0,
"viewPath": null,
"keepAlive": true,
"isShow": true,
"childMenus": []
}
]
}
]
}
]
},
{
"name": "框架教程",
"router": "/tutorial",
"perms": null,
"type": 0,
"icon": "icon-task",
"orderNum": 98,
"viewPath": null,
"keepAlive": true,
"isShow": true,
"childMenus": [
{
"name": "文档官网",
"router": "/tutorial/doc",
"perms": null,
"type": 1,
"icon": "icon-log",
"orderNum": 0,
"viewPath": "https://admin.cool-js.com",
"keepAlive": true,
"isShow": true,
"childMenus": []
},
{
"name": "crud 示例",
"router": "/demo/crud",
"perms": null,
"type": 1,
"icon": "icon-favor",
"orderNum": 1,
"viewPath": "modules/demo/views/crud/index.vue",
"keepAlive": true,
"isShow": true,
"childMenus": []
}
]
},
{
"name": "通用",
"router": null,
"perms": null,
"type": 0,
"icon": "icon-radioboxfill",
"orderNum": 99,
"viewPath": null,
"keepAlive": true,
"isShow": false,
"childMenus": [
{
"name": "图片上传",
"router": null,
"perms": "space:info:page,space:info:list,space:info:info,space:info:add,space:info:delete,space:info:update,space:type:page,space:type:list,space:type:info,space:type:add,space:type:delete,space:type:update",
"type": 2,
"icon": null,
"orderNum": 1,
"viewPath": null,
"keepAlive": true,
"isShow": true,
"childMenus": []
}
]
},
{
"name": "首页",
"router": "/",
"perms": null,
"type": 1,
"icon": null,
"orderNum": 0,
"viewPath": "modules/demo/views/home/index.vue",
"keepAlive": true,
"isShow": false,
"childMenus": []
},
{
"name": "数据管理",
"router": null,
"perms": null,
"type": 0,
"icon": "icon-data",
"orderNum": 7,
"viewPath": null,
"keepAlive": true,
"isShow": true,
"childMenus": [
{
"name": "字典管理",
"router": "/dict/list",
"perms": null,
"type": 1,
"icon": "icon-dict",
"orderNum": 3,
"viewPath": "modules/dict/views/list.vue",
"keepAlive": true,
"isShow": true,
"childMenus": [
{
"name": "删除",
"router": null,
"perms": "dict:info:delete",
"type": 2,
"icon": null,
"orderNum": 0,
"viewPath": null,
"keepAlive": true,
"isShow": true,
"childMenus": []
},
{
"name": "修改",
"router": null,
"perms": "dict:info:update,dict:info:info",
"type": 2,
"icon": null,
"orderNum": 0,
"viewPath": null,
"keepAlive": true,
"isShow": true,
"childMenus": []
},
{
"name": "获得字典数据",
"router": null,
"perms": "dict:info:data",
"type": 2,
"icon": null,
"orderNum": 0,
"viewPath": null,
"keepAlive": true,
"isShow": true,
"childMenus": []
},
{
"name": "单个信息",
"router": null,
"perms": "dict:info:info",
"type": 2,
"icon": null,
"orderNum": 0,
"viewPath": null,
"keepAlive": true,
"isShow": true,
"childMenus": []
},
{
"name": "列表查询",
"router": null,
"perms": "dict:info:list",
"type": 2,
"icon": null,
"orderNum": 0,
"viewPath": null,
"keepAlive": true,
"isShow": true,
"childMenus": []
},
{
"name": "分页查询",
"router": null,
"perms": "dict:info:page",
"type": 2,
"icon": null,
"orderNum": 0,
"viewPath": null,
"keepAlive": true,
"isShow": true,
"childMenus": []
},
{
"name": "新增",
"router": null,
"perms": "dict:info:add",
"type": 2,
"icon": null,
"orderNum": 0,
"viewPath": null,
"keepAlive": true,
"isShow": true,
"childMenus": []
},
{
"name": "组权限",
"router": null,
"perms": "dict:type:list,dict:type:update,dict:type:delete,dict:type:add",
"type": 2,
"icon": null,
"orderNum": 0,
"viewPath": null,
"keepAlive": true,
"isShow": true,
"childMenus": []
},
{
"name": "字典类型",
"router": null,
"perms": "dict:type:delete,dict:type:update,dict:type:info,dict:type:list,dict:type:page,dict:type:add",
"type": 2,
"icon": null,
"orderNum": 0,
"viewPath": null,
"keepAlive": true,
"isShow": true,
"childMenus": []
}
]
},
{
"name": "数据回收站",
"router": "/recycle/data",
"perms": null,
"type": 1,
"icon": "icon-delete",
"orderNum": 6,
"viewPath": "modules/recycle/views/data.vue",
"keepAlive": true,
"isShow": true,
"childMenus": [
{
"name": "恢复数据",
"router": null,
"perms": "recycle:data:restore",
"type": 2,
"icon": null,
"orderNum": 0,
"viewPath": null,
"keepAlive": true,
"isShow": true,
"childMenus": []
},
{
"name": "单个信息",
"router": null,
"perms": "recycle:data:info",
"type": 2,
"icon": null,
"orderNum": 0,
"viewPath": null,
"keepAlive": true,
"isShow": true,
"childMenus": []
},
{
"name": "分页查询",
"router": null,
"perms": "recycle:data:page",
"type": 2,
"icon": null,
"orderNum": 0,
"viewPath": null,
"keepAlive": true,
"isShow": true,
"childMenus": []
}
]
},
{
"name": "文件管理",
"router": "/upload/list",
"perms": null,
"type": 1,
"icon": "icon-log",
"orderNum": 5,
"viewPath": "modules/space/views/list.vue",
"keepAlive": true,
"isShow": true,
"childMenus": [
{
"name": "权限",
"router": null,
"perms": "space:type:delete,space:type:update,space:type:info,space:type:list,space:type:page,space:type:add,space:info:getConfig,space:info:delete,space:info:update,space:info:info,space:info:list,space:info:page,space:info:add",
"type": 2,
"icon": null,
"orderNum": 0,
"viewPath": null,
"keepAlive": true,
"isShow": true,
"childMenus": []
}
]
}
]
},
{
"name": "用户管理",
"router": null,
"perms": null,
"type": 0,
"icon": "icon-user",
"orderNum": 11,
"viewPath": null,
"keepAlive": true,
"isShow": true,
"childMenus": [
{
"name": "用户列表",
"router": "/user/list",
"perms": null,
"type": 1,
"icon": "icon-menu",
"orderNum": 1,
"viewPath": "modules/user/views/list.vue",
"keepAlive": true,
"isShow": true,
"childMenus": [
{
"name": "删除",
"router": null,
"perms": "user:info:delete",
"type": 2,
"icon": null,
"orderNum": 0,
"viewPath": null,
"keepAlive": true,
"isShow": true,
"childMenus": []
},
{
"name": "修改",
"router": null,
"perms": "user:info:update,user:info:info",
"type": 2,
"icon": null,
"orderNum": 0,
"viewPath": null,
"keepAlive": true,
"isShow": true,
"childMenus": []
},
{
"name": "单个信息",
"router": null,
"perms": "user:info:info",
"type": 2,
"icon": null,
"orderNum": 0,
"viewPath": null,
"keepAlive": true,
"isShow": true,
"childMenus": []
},
{
"name": "列表查询",
"router": null,
"perms": "user:info:list",
"type": 2,
"icon": null,
"orderNum": 0,
"viewPath": null,
"keepAlive": true,
"isShow": true,
"childMenus": []
},
{
"name": "分页查询",
"router": null,
"perms": "user:info:page",
"type": 2,
"icon": null,
"orderNum": 0,
"viewPath": null,
"keepAlive": true,
"isShow": true,
"childMenus": []
},
{
"name": "新增",
"router": null,
"perms": "user:info:add",
"type": 2,
"icon": null,
"orderNum": 0,
"viewPath": null,
"keepAlive": true,
"isShow": true,
"childMenus": []
}
]
}
]
},
{
"name": "扩展管理",
"router": null,
"perms": null,
"type": 0,
"icon": "icon-favor",
"orderNum": 8,
"viewPath": null,
"keepAlive": true,
"isShow": true,
"childMenus": [
{
"name": "后端插件",
"router": "/helper/plugins/serve",
"perms": null,
"type": 1,
"icon": "icon-component",
"orderNum": 2,
"viewPath": "modules/helper/views/plugins/serve.vue",
"keepAlive": true,
"isShow": true,
"childMenus": [
{
"name": "权限",
"router": null,
"perms": "plugin:info:install,plugin:info:delete,plugin:info:update,plugin:info:page,plugin:info:info",
"type": 2,
"icon": null,
"orderNum": 0,
"viewPath": null,
"keepAlive": true,
"isShow": true,
"childMenus": []
}
]
},
{
"name": "前端插件",
"router": "/helper/plugins/vue",
"perms": null,
"type": 1,
"icon": "icon-vue",
"orderNum": 1,
"viewPath": "modules/helper/views/plugins/vue.vue",
"keepAlive": true,
"isShow": true,
"childMenus": []
}
]
}
]

View File

@ -0,0 +1,185 @@
import { App, Config, Inject, Middleware } from '@midwayjs/core';
import * as _ from 'lodash';
import { CoolUrlTagData, RESCODE, TagTypes } from '@cool-midway/core';
import * as jwt from 'jsonwebtoken';
import { NextFunction, Context } from '@midwayjs/koa';
import {
IMiddleware,
IMidwayApplication,
Init,
InjectClient,
} from '@midwayjs/core';
import { CachingFactory, MidwayCache } from '@midwayjs/cache-manager';
/**
*
*/
@Middleware()
export class BaseAuthorityMiddleware
implements IMiddleware<Context, NextFunction>
{
@Config('koa.globalPrefix')
prefix;
@Config('module.base')
jwtConfig;
@InjectClient(CachingFactory, 'default')
midwayCache: MidwayCache;
@Inject()
coolUrlTagData: CoolUrlTagData;
@App()
app: IMidwayApplication;
ignoreUrls: string[] = [];
@Init()
async init() {
this.ignoreUrls = this.coolUrlTagData.byKey(TagTypes.IGNORE_TOKEN, 'admin');
}
resolve() {
return async (ctx: Context, next: NextFunction) => {
let statusCode = 200;
let { url } = ctx;
url = url.replace(this.prefix, '').split('?')[0];
const token = ctx.get('Authorization');
const adminUrl = '/admin/';
// 路由地址为 admin前缀的 需要权限校验
if (_.startsWith(url, adminUrl)) {
try {
ctx.admin = jwt.verify(token, this.jwtConfig.jwt.secret);
if (ctx.admin.isRefresh) {
ctx.status = 401;
ctx.body = {
code: RESCODE.COMMFAIL,
message: '登录失效~',
};
return;
}
} catch (error) {}
// 使用matchUrl方法来检查URL是否应该被忽略
const isIgnored = this.ignoreUrls.some(pattern =>
this.matchUrl(pattern, url)
);
if (isIgnored) {
await next();
return;
}
if (ctx.admin) {
const rToken = await this.midwayCache.get(
`admin:token:${ctx.admin.userId}`
);
// 判断密码版本是否正确
const passwordV = await this.midwayCache.get(
`admin:passwordVersion:${ctx.admin.userId}`
);
if (passwordV != ctx.admin.passwordVersion) {
ctx.status = 401;
ctx.body = {
code: RESCODE.COMMFAIL,
message: '登录失效~',
};
return;
}
// 超管拥有所有权限
if (ctx.admin.username == 'admin' && !ctx.admin.isRefresh) {
if (rToken !== token && this.jwtConfig.jwt.sso) {
ctx.status = 401;
ctx.body = {
code: RESCODE.COMMFAIL,
message: '登录失效~',
};
return;
} else {
await next();
return;
}
}
// 要登录每个人都有权限的接口
if (
new RegExp(`^${adminUrl}?.*/comm/`).test(url) ||
// 字典接口
url == '/admin/dict/info/data'
) {
await next();
return;
}
// 如果传的token是refreshToken则校验失败
if (ctx.admin.isRefresh) {
ctx.status = 401;
ctx.body = {
code: RESCODE.COMMFAIL,
message: '登录失效~',
};
return;
}
if (!rToken) {
ctx.status = 401;
ctx.body = {
code: RESCODE.COMMFAIL,
message: '登录失效或无权限访问~',
};
return;
}
if (rToken !== token && this.jwtConfig.jwt.sso) {
statusCode = 401;
} else {
let perms: string[] = await this.midwayCache.get(
`admin:perms:${ctx.admin.userId}`
);
if (!_.isEmpty(perms)) {
perms = perms.map(e => {
return e.replace(/:/g, '/');
});
if (!perms.includes(url.split('?')[0].replace('/admin/', ''))) {
statusCode = 403;
}
} else {
statusCode = 403;
}
}
} else {
statusCode = 401;
}
if (statusCode > 200) {
ctx.status = statusCode;
ctx.body = {
code: RESCODE.COMMFAIL,
message: '登录失效或无权限访问~',
};
return;
}
}
await next();
};
}
// 匹配URL的方法
matchUrl(pattern, url) {
const patternSegments = pattern.split('/').filter(Boolean);
const urlSegments = url.split('/').filter(Boolean);
// 如果段的数量不同,则无法匹配
if (patternSegments.length !== urlSegments.length) {
return false;
}
// 逐段进行匹配
for (let i = 0; i < patternSegments.length; i++) {
if (patternSegments[i].startsWith(':')) {
// 如果模式段以':'开始,我们认为它是一个参数,可以匹配任何内容
continue;
}
// 如果两个段不相同,则不匹配
if (patternSegments[i] !== urlSegments[i]) {
return false;
}
}
// 所有段都匹配
return true;
}
}

View File

@ -0,0 +1,26 @@
import { Middleware } from '@midwayjs/core';
import * as _ from 'lodash';
import { NextFunction, Context } from '@midwayjs/koa';
import { IMiddleware } from '@midwayjs/core';
import { BaseSysLogService } from '../service/sys/log';
/**
*
*/
@Middleware()
export class BaseLogMiddleware implements IMiddleware<Context, NextFunction> {
resolve() {
return async (ctx: Context, next: NextFunction) => {
const baseSysLogService = await ctx.requestContext.getAsync(
BaseSysLogService
);
baseSysLogService.record(
ctx,
ctx.url,
ctx.req.method === 'GET' ? ctx.request.query : ctx.request.body,
ctx.admin ? ctx.admin.userId : null
);
await next();
};
}
}

View File

@ -0,0 +1,39 @@
import { Provide } from '@midwayjs/core';
import { BaseService } from '@cool-midway/core';
import { InjectEntityModel } from '@midwayjs/typeorm';
import { Repository } from 'typeorm';
import { BaseSysConfEntity } from '../../entity/sys/conf';
/**
*
*/
@Provide()
export class BaseSysConfService extends BaseService {
@InjectEntityModel(BaseSysConfEntity)
baseSysConfEntity: Repository<BaseSysConfEntity>;
/**
*
* @param key
*/
async getValue(key) {
const conf = await this.baseSysConfEntity.findOneBy({ cKey: key });
if (conf) {
return conf.cValue;
}
}
/**
*
* @param cKey
* @param cValue
*/
async updateVaule(cKey, cValue) {
await this.baseSysConfEntity
.createQueryBuilder()
.update()
.where({ cKey })
.set({ cKey, cValue })
.execute();
}
}

View File

@ -0,0 +1,10 @@
import { DataSource } from 'typeorm';
export class TempDataSource extends DataSource {
/**
*
*/
async buildMetadatas() {
await super.buildMetadatas();
}
}

View File

@ -0,0 +1,124 @@
import { Inject, Provide } from '@midwayjs/core';
import { BaseService } from '@cool-midway/core';
import { InjectEntityModel } from '@midwayjs/typeorm';
import { In, Repository } from 'typeorm';
import { BaseSysDepartmentEntity } from '../../entity/sys/department';
import * as _ from 'lodash';
import { BaseSysRoleDepartmentEntity } from '../../entity/sys/role_department';
import { BaseSysPermsService } from './perms';
import { BaseSysUserEntity } from '../../entity/sys/user';
/**
*
*/
@Provide()
export class BaseSysDepartmentService extends BaseService {
@InjectEntityModel(BaseSysDepartmentEntity)
baseSysDepartmentEntity: Repository<BaseSysDepartmentEntity>;
@InjectEntityModel(BaseSysUserEntity)
baseSysUserEntity: Repository<BaseSysUserEntity>;
@InjectEntityModel(BaseSysRoleDepartmentEntity)
baseSysRoleDepartmentEntity: Repository<BaseSysRoleDepartmentEntity>;
@Inject()
baseSysPermsService: BaseSysPermsService;
@Inject()
ctx;
/**
*
*/
async list() {
// 部门权限
const permsDepartmentArr = await this.baseSysPermsService.departmentIds(
this.ctx.admin.userId
);
// 过滤部门权限
const find = this.baseSysDepartmentEntity.createQueryBuilder('a');
if (this.ctx.admin.username !== 'admin')
find.andWhere('a.id in (:...ids)', {
ids: !_.isEmpty(permsDepartmentArr) ? permsDepartmentArr : [null],
});
find.addOrderBy('a.orderNum', 'ASC');
const departments: BaseSysDepartmentEntity[] = await find.getMany();
if (!_.isEmpty(departments)) {
departments.forEach(e => {
const parentMenu = departments.filter(m => {
e.parentId = parseInt(e.parentId + '');
if (e.parentId == m.id) {
return m.name;
}
});
if (!_.isEmpty(parentMenu)) {
e.parentName = parentMenu[0].name;
}
});
}
return departments;
}
/**
* ID获得部门权限信息
* @param {[]} roleIds
* @param isAdmin
*/
async getByRoleIds(roleIds: number[], isAdmin) {
if (!_.isEmpty(roleIds)) {
if (isAdmin) {
const result = await this.baseSysDepartmentEntity.find();
return result.map(e => {
return e.id;
});
}
const result = await this.baseSysRoleDepartmentEntity
.createQueryBuilder('a')
.where('a.roleId in (:...roleIds)', { roleIds })
.getMany();
if (!_.isEmpty(result)) {
return _.uniq(
result.map(e => {
return e.departmentId;
})
);
}
}
return [];
}
/**
*
* @param params
*/
async order(params) {
for (const e of params) {
await this.baseSysDepartmentEntity.update(e.id, e);
}
}
/**
*
*/
async delete(ids: number[]) {
const { deleteUser } = this.ctx.request.body;
await super.delete(ids);
if (deleteUser) {
await this.baseSysUserEntity.delete({ departmentId: In(ids) });
} else {
const topDepartment = await this.baseSysDepartmentEntity
.createQueryBuilder('a')
.where('a.parentId is null')
.getOne();
if (topDepartment) {
await this.baseSysUserEntity.update(
{ departmentId: In(ids) },
{ departmentId: topDepartment.id }
);
}
}
}
}

View File

@ -0,0 +1,62 @@
import { Inject, Provide } from '@midwayjs/core';
import { BaseService } from '@cool-midway/core';
import { InjectEntityModel } from '@midwayjs/typeorm';
import { LessThan, Repository } from 'typeorm';
import * as _ from 'lodash';
import { BaseSysLogEntity } from '../../entity/sys/log';
import * as moment from 'moment';
import { Utils } from '../../../../comm/utils';
import { BaseSysConfService } from './conf';
import { Context } from '@midwayjs/koa';
/**
*
*/
@Provide()
export class BaseSysLogService extends BaseService {
@Inject()
ctx;
@Inject()
utils: Utils;
@InjectEntityModel(BaseSysLogEntity)
baseSysLogEntity: Repository<BaseSysLogEntity>;
@Inject()
baseSysConfService: BaseSysConfService;
/**
*
* @param url URL地址
* @param params
* @param userId ID
*/
async record(context: Context, url, params, userId) {
const ip = await this.utils.getReqIP(context);
const sysLog = new BaseSysLogEntity();
sysLog.userId = userId;
sysLog.ip = typeof ip === 'string' ? ip : ip.join(',');
sysLog.action = url.split('?')[0];
sysLog.params = params;
await this.baseSysLogEntity.insert(sysLog);
}
/**
*
* @param isAll
*/
async clear(isAll?) {
if (isAll) {
await this.baseSysLogEntity.clear();
return;
}
const keepDay = await this.baseSysConfService.getValue('logKeep');
if (keepDay) {
const beforeDate = moment().add(-keepDay, 'days').startOf('day').toDate();
await this.baseSysLogEntity.delete({ createTime: LessThan(beforeDate) });
} else {
await this.baseSysLogEntity.clear();
}
}
}

View File

@ -0,0 +1,272 @@
import { Inject, Provide, Config, InjectClient } from '@midwayjs/core';
import { BaseService, CoolCommException } from '@cool-midway/core';
import { LoginDTO } from '../../dto/login';
import * as svgCaptcha from 'svg-captcha';
import { v1 as uuid } from 'uuid';
import { BaseSysUserEntity } from '../../entity/sys/user';
import { Repository } from 'typeorm';
import { InjectEntityModel } from '@midwayjs/typeorm';
import * as md5 from 'md5';
import { BaseSysRoleService } from './role';
import * as _ from 'lodash';
import { BaseSysMenuService } from './menu';
import { BaseSysDepartmentService } from './department';
import * as jwt from 'jsonwebtoken';
import { Context } from '@midwayjs/koa';
import { CachingFactory, MidwayCache } from '@midwayjs/cache-manager';
// import * as sharp from 'sharp';
/**
*
*/
@Provide()
export class BaseSysLoginService extends BaseService {
@InjectClient(CachingFactory, 'default')
midwayCache: MidwayCache;
@InjectEntityModel(BaseSysUserEntity)
baseSysUserEntity: Repository<BaseSysUserEntity>;
@Inject()
baseSysRoleService: BaseSysRoleService;
@Inject()
baseSysMenuService: BaseSysMenuService;
@Inject()
baseSysDepartmentService: BaseSysDepartmentService;
@Inject()
ctx: Context;
@Config('module.base')
coolConfig;
/**
*
* @param login
*/
async login(login: LoginDTO) {
const { username, captchaId, verifyCode, password } = login;
// 校验验证码
const checkV = await this.captchaCheck(captchaId, verifyCode);
if (checkV) {
const user = await this.baseSysUserEntity.findOneBy({ username });
// 校验用户
if (user) {
// 校验用户状态及密码
if (user.status === 0 || user.password !== md5(password)) {
throw new CoolCommException('账户或密码不正确~');
}
} else {
throw new CoolCommException('账户或密码不正确~');
}
// 校验角色
const roleIds = await this.baseSysRoleService.getByUser(user.id);
if (_.isEmpty(roleIds)) {
throw new CoolCommException('该用户未设置任何角色,无法登录~');
}
// 生成token
const { expire, refreshExpire } = this.coolConfig.jwt.token;
const result = {
expire,
token: await this.generateToken(user, roleIds, expire),
refreshExpire,
refreshToken: await this.generateToken(
user,
roleIds,
refreshExpire,
true
),
};
// 将用户相关信息保存到缓存
const perms = await this.baseSysMenuService.getPerms(roleIds);
const departments = await this.baseSysDepartmentService.getByRoleIds(
roleIds,
user.username === 'admin'
);
await this.midwayCache.set(`admin:department:${user.id}`, departments);
await this.midwayCache.set(`admin:perms:${user.id}`, perms);
await this.midwayCache.set(`admin:token:${user.id}`, result.token);
await this.midwayCache.set(
`admin:token:refresh:${user.id}`,
result.token
);
return result;
} else {
throw new CoolCommException('验证码不正确');
}
}
/**
*
* @param type svg
* @param width
* @param height
*/
async captcha(type: string, width = 150, height = 50, color = '#fff') {
const svg = svgCaptcha.create({
ignoreChars: 'qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM',
width,
height,
});
const result = {
captchaId: uuid(),
data: svg.data.replace(/"/g, "'"),
};
// 文字变白
const rpList = [
'#111',
'#222',
'#333',
'#444',
'#555',
'#666',
'#777',
'#888',
'#999',
];
rpList.forEach(rp => {
result.data = result.data['replaceAll'](rp, color);
});
if (type === 'png' || type === 'base64') {
result.data = await this.svgToBase64Png(result.data, {
width,
height,
});
}
// 半小时过期
await this.midwayCache.set(
`verify:img:${result.captchaId}`,
svg.text.toLowerCase(),
1800 * 1000
);
return result;
}
/**
* svg转base64
* @param svgBuffer
* @param options
*/
async svgToBase64Png(svgBuffer: string, options = {} as any) {
try {
// const svgBufferData = Buffer.from(svgBuffer);
// // 处理图片
// const pngBuffer = await sharp(svgBufferData)
// .png({
// quality: options.quality || 100,
// compressionLevel: options.compression || 6,
// })
// .resize(options.width, options.height, {
// fit: 'contain',
// background: { r: 255, g: 255, b: 255, alpha: 1 },
// })
// .toBuffer();
// // 转换为base64
// const base64String = `data:image/png;base64,${pngBuffer.toString(
// 'base64'
// )}`;
return '';
} catch (error) {
console.error('转换失败:', error);
throw error;
}
}
/**
* 退
*/
async logout() {
if (!this.coolConfig.jwt.sso) return;
const { userId } = this.ctx.admin;
await this.midwayCache.del(`admin:department:${userId}`);
await this.midwayCache.del(`admin:perms:${userId}`);
await this.midwayCache.del(`admin:token:${userId}`);
await this.midwayCache.del(`admin:token:refresh:${userId}`);
await this.midwayCache.del(`admin:passwordVersion:${userId}`);
}
/**
*
* @param captchaId ID
* @param value
*/
async captchaCheck(captchaId, value) {
const rv = await this.midwayCache.get(`verify:img:${captchaId}`);
if (!rv || !value || value.toLowerCase() !== rv) {
return false;
} else {
this.midwayCache.del(`verify:img:${captchaId}`);
return true;
}
}
/**
* token
* @param user
* @param roleIds
* @param expire
* @param isRefresh
*/
async generateToken(user, roleIds, expire, isRefresh?) {
await this.midwayCache.set(
`admin:passwordVersion:${user.id}`,
user.passwordV
);
const tokenInfo = {
isRefresh: false,
roleIds,
username: user.username,
userId: user.id,
passwordVersion: user.passwordV,
};
if (isRefresh) {
tokenInfo.isRefresh = true;
}
return jwt.sign(tokenInfo, this.coolConfig.jwt.secret, {
expiresIn: expire,
});
}
/**
* token
* @param token
*/
async refreshToken(token: string) {
const decoded = jwt.verify(token, this.coolConfig.jwt.secret);
if (decoded && decoded['isRefresh']) {
delete decoded['exp'];
delete decoded['iat'];
const { expire, refreshExpire } = this.coolConfig.jwt.token;
decoded['isRefresh'] = false;
const result = {
expire,
token: jwt.sign(decoded, this.coolConfig.jwt.secret, {
expiresIn: expire,
}),
refreshExpire,
refreshToken: '',
};
decoded['isRefresh'] = true;
result.refreshToken = jwt.sign(decoded, this.coolConfig.jwt.secret, {
expiresIn: refreshExpire,
});
await this.midwayCache.set(
`admin:passwordVersion:${decoded['userId']}`,
decoded['passwordVersion']
);
await this.midwayCache.set(
`admin:token:${decoded['userId']}`,
result.token
);
return result;
}
}
}

View File

@ -0,0 +1,463 @@
import { App, IMidwayApplication, Scope, ScopeEnum } from '@midwayjs/core';
import { ALL, Config, Inject, Provide } from '@midwayjs/core';
import { BaseService, CoolCommException } from '@cool-midway/core';
import { InjectEntityModel } from '@midwayjs/typeorm';
import { In, Repository } from 'typeorm';
import { BaseSysMenuEntity } from '../../entity/sys/menu';
import * as _ from 'lodash';
import { BaseSysPermsService } from './perms';
import { Context } from '@midwayjs/koa';
import { TempDataSource } from './data';
// eslint-disable-next-line node/no-unpublished-import
import * as ts from 'typescript';
import * as fs from 'fs';
import * as pathUtil from 'path';
import { BaseSysRoleMenuEntity } from '../../entity/sys/role_menu';
import { BaseSysUserRoleEntity } from '../../entity/sys/user_role';
/**
*
*/
@Scope(ScopeEnum.Request, { allowDowngrade: true })
@Provide()
export class BaseSysMenuService extends BaseService {
@Inject()
ctx: Context;
@InjectEntityModel(BaseSysMenuEntity)
baseSysMenuEntity: Repository<BaseSysMenuEntity>;
@InjectEntityModel(BaseSysRoleMenuEntity)
baseSysRoleMenuEntity: Repository<BaseSysRoleMenuEntity>;
@Inject()
baseSysPermsService: BaseSysPermsService;
@Config(ALL)
config;
@App()
app: IMidwayApplication;
/**
*
*/
async list() {
const menus = await this.getMenus(
this.ctx.admin.roleIds,
this.ctx.admin.username === 'admin'
);
if (!_.isEmpty(menus)) {
menus.forEach((e: any) => {
const parentMenu = menus.filter(m => {
e.parentId = parseInt(e.parentId);
if (e.parentId == m.id) {
return m.name;
}
});
if (!_.isEmpty(parentMenu)) {
e.parentName = parentMenu[0].name;
}
});
}
return menus;
}
/**
*
* @param param
*/
async modifyAfter(param) {
if (param.id) {
await this.refreshPerms(param.id);
}
}
/**
*
* @param {[]} roleIds
*/
async getPerms(roleIds) {
let perms = [];
if (!_.isEmpty(roleIds)) {
const find = await this.baseSysMenuEntity.createQueryBuilder('a');
if (!roleIds.includes(1)) {
find.innerJoinAndSelect(
BaseSysRoleMenuEntity,
'b',
'a.id = b.menuId AND b.roleId in (:...roleIds)',
{ roleIds }
);
}
find.where('a.perms is not NULL');
const result = await find.getMany();
if (result) {
result.forEach(d => {
if (d.perms) {
perms = perms.concat(d.perms.split(','));
}
});
}
perms = _.uniq(perms);
perms = _.remove(perms, n => {
return !_.isEmpty(n);
});
}
return _.uniq(perms);
}
/**
*
* @param roleIds
* @param isAdmin
*/
async getMenus(roleIds, isAdmin) {
const find = this.baseSysMenuEntity.createQueryBuilder('a');
if (!isAdmin) {
find.innerJoinAndSelect(
BaseSysRoleMenuEntity,
'b',
'a.id = b.menuId AND b.roleId in (:...roleIds)',
{ roleIds }
);
}
find.orderBy('a.orderNum', 'ASC');
const list = await find.getMany();
return _.uniqBy(list, 'id');
}
/**
*
* @param ids
*/
async delete(ids) {
let idArr;
if (ids instanceof Array) {
idArr = ids;
} else {
idArr = ids.split(',');
}
for (const id of idArr) {
await this.baseSysMenuEntity.delete({ id });
await this.delChildMenu(id);
}
}
/**
*
* @param id
*/
private async delChildMenu(id) {
await this.refreshPerms(id);
const delMenu = await this.baseSysMenuEntity.findBy({ parentId: id });
if (_.isEmpty(delMenu)) {
return;
}
const delMenuIds = delMenu.map(e => {
return e.id;
});
await this.baseSysMenuEntity.delete(delMenuIds);
for (const menuId of delMenuIds) {
await this.delChildMenu(menuId);
}
}
/**
*
* @param menuId
*/
async refreshPerms(menuId) {
const find = this.baseSysRoleMenuEntity.createQueryBuilder('a');
find.leftJoinAndSelect(BaseSysUserRoleEntity, 'b', 'a.roleId = b.roleId');
find.where('a.menuId = :menuId', { menuId: menuId });
find.select('b.userId', 'userId');
const users = await find.getRawMany();
// 刷新admin权限
await this.baseSysPermsService.refreshPerms(1);
if (!_.isEmpty(users)) {
// 刷新其他权限
for (const user of _.uniqBy(users, 'userId')) {
await this.baseSysPermsService.refreshPerms(user.userId);
}
}
}
/**
* Controller
* @param entityString
* @param controller
* @param module
*/
async parse(entityString: string, controller: string, module: string) {
const tempDataSource = new TempDataSource({
...this.config.typeorm.dataSource.default,
entities: [],
});
// 连接数据库
await tempDataSource.initialize();
const { newCode, className, oldTableName } = this.parseCode(entityString);
const code = ts.transpile(
`${newCode}
tempDataSource.options.entities.push(${className})
`,
{
emitDecoratorMetadata: true,
module: ts.ModuleKind.CommonJS,
target: ts.ScriptTarget.ES2018,
removeComments: true,
experimentalDecorators: true,
noImplicitThis: true,
noUnusedLocals: true,
stripInternal: true,
skipLibCheck: true,
pretty: true,
declaration: true,
noImplicitAny: false,
}
);
eval(code);
await tempDataSource.buildMetadatas();
const meta = tempDataSource.getMetadata(className);
const columnArr = meta.columns;
await tempDataSource.destroy();
const commColums = [];
const columns = _.filter(
columnArr.map(e => {
return {
propertyName: e.propertyName,
type: typeof e.type == 'string' ? e.type : e.type.name.toLowerCase(),
length: e.length,
comment: e.comment,
nullable: e.isNullable,
};
}),
o => {
if (['createTime', 'updateTime'].includes(o.propertyName)) {
commColums.push(o);
}
return o && !['createTime', 'updateTime'].includes(o.propertyName);
}
).concat(commColums);
if (!controller) {
const tableNames = oldTableName.split('_');
const fileName = tableNames[tableNames.length - 1];
return {
columns,
className: className.replace('TEMP', ''),
tableName: oldTableName,
fileName,
path: `/admin/${module}/${fileName}`,
};
}
const fileName = await this.fileName(controller);
return {
columns,
path: `/admin/${module}/${fileName}`,
};
}
/**
* Entity类名
* @param code
* @returns
*/
parseCode(code: string) {
try {
const oldClassName = code
.match('class(.*)extends')[1]
.replace(/\s*/g, '');
const oldTableStart = code.indexOf('@Entity(');
const oldTableEnd = code.indexOf(')');
const oldTableName = code
.substring(oldTableStart + 9, oldTableEnd - 1)
.replace(/\s*/g, '')
// eslint-disable-next-line no-useless-escape
.replace(/\"/g, '')
// eslint-disable-next-line no-useless-escape
.replace(/\'/g, '');
const className = `${oldClassName}TEMP`;
return {
newCode: code
.replace(oldClassName, className)
.replace(oldTableName, `func_${oldTableName}`),
className,
tableName: `func_${oldTableName}`,
oldTableName,
};
} catch (err) {
throw new CoolCommException('代码结构不正确,请检查');
}
}
/**
*
* @param body body
*/
async create(body) {
const { module, entity, controller, service, fileName } = body;
const basePath = this.app.getBaseDir();
const modulePath = pathUtil.join(basePath, '..', 'src', 'modules', module);
// 生成Entity
const entityPath = pathUtil.join(modulePath, 'entity', `${fileName}.ts`);
// 生成Controller
const controllerPath = pathUtil.join(
modulePath,
'controller',
'admin',
`${fileName}.ts`
);
// 生成Service
const servicePath = pathUtil.join(modulePath, 'service', `${fileName}.ts`);
this.createConfigFile(module);
this.createFile(entityPath, entity);
this.createFile(controllerPath, controller);
this.createFile(servicePath, service);
}
/**
*
* @param module
*/
async createConfigFile(module: string) {
const basePath = this.app.getBaseDir();
const configFilePath = pathUtil.join(
basePath,
'..',
'src',
'modules',
module,
'config.ts'
);
if (!fs.existsSync(configFilePath)) {
const data = `import { ModuleConfig } from '@cool-midway/core';
/**
*
*/
export default () => {
return {
// 模块名称
name: 'xxx',
// 模块描述
description: 'xxx',
// 中间件,只对本模块有效
middlewares: [],
// 中间件,全局有效
globalMiddlewares: [],
// 模块加载顺序默认为0值越大越优先加载
order: 0,
} as ModuleConfig;
};
`;
await this.createFile(configFilePath, data);
}
}
/**
*
* @param controller
* @returns
*/
async fileName(controller: string) {
const regex = /import\s*{\s*\w+\s*}\s*from\s*'[^']*\/([\w-]+)';/;
const match = regex.exec(controller);
if (match && match.length > 1) {
return match[1];
}
return null;
}
/**
*
* @param filePath
* @param content
*/
async createFile(filePath: string, content: string) {
const folderPath = pathUtil.dirname(filePath);
if (!fs.existsSync(folderPath)) {
fs.mkdirSync(folderPath, { recursive: true });
}
fs.writeFileSync(filePath, content);
}
/**
*
* @param ids
* @returns
*/
async export(ids: number[]) {
const result: any[] = [];
const menus = await this.baseSysMenuEntity.findBy({ id: In(ids) });
// 递归取出子菜单
const getChildMenus = (parentId: number): any[] => {
const children = _.remove(menus, e => e.parentId == parentId);
children.forEach(child => {
child.childMenus = getChildMenus(child.id);
// 删除不需要的字段
delete child.id;
delete child.createTime;
delete child.updateTime;
delete child.parentId;
});
return children;
};
// lodash取出父级菜单(parentId为 null) 并从menus 删除
const parentMenus = _.remove(menus, e => {
return e.parentId == null;
});
// 对于每个父级菜单,获取它的子菜单
parentMenus.forEach(parent => {
parent.childMenus = getChildMenus(parent.id);
// 删除不需要的字段
delete parent.id;
delete parent.createTime;
delete parent.updateTime;
delete parent.parentId;
result.push(parent);
});
return result;
}
/**
*
* @param menus
*/
async import(menus: any[]) {
// 递归保存子菜单
const saveChildMenus = async (parentMenu: any, parentId: number | null) => {
const children = parentMenu.childMenus || [];
for (let child of children) {
const childData = { ...child, parentId: parentId }; // 保持与数据库的parentId字段的一致性
delete childData.childMenus; // 删除childMenus属性因为我们不想将它保存到数据库中
// 保存子菜单并获取其ID以便为其子菜单设置parentId
const savedChild = await this.baseSysMenuEntity.save(childData);
if (!_.isEmpty(child.childMenus)) {
await saveChildMenus(child, savedChild.id);
}
}
};
for (let menu of menus) {
const menuData = { ...menu };
delete menuData.childMenus; // 删除childMenus属性因为我们不想将它保存到数据库中
// 保存主菜单并获取其ID
const savedMenu = await this.baseSysMenuEntity.save(menuData);
if (menu.childMenus && menu.childMenus.length > 0) {
await saveChildMenus(menu, savedMenu.id);
}
}
}
}

View File

@ -0,0 +1,91 @@
import { Inject, InjectClient, Provide } from '@midwayjs/core';
import { BaseService, CoolCommException } from '@cool-midway/core';
import { InjectEntityModel } from '@midwayjs/typeorm';
import { Not, Repository } from 'typeorm';
import { BaseSysParamEntity } from '../../entity/sys/param';
import { CachingFactory, MidwayCache } from '@midwayjs/cache-manager';
/**
*
*/
@Provide()
export class BaseSysParamService extends BaseService {
@InjectEntityModel(BaseSysParamEntity)
baseSysParamEntity: Repository<BaseSysParamEntity>;
@InjectClient(CachingFactory, 'default')
midwayCache: MidwayCache;
/**
* key获得对应的参数
* @param key
*/
async dataByKey(key) {
let result: any = await this.midwayCache.get(`param:${key}`);
if (!result) {
result = await this.baseSysParamEntity.findOneBy({ keyName: key });
this.midwayCache.set(`param:${key}`, result);
}
if (result) {
if (result.dataType == 0) {
try {
return JSON.parse(result.data);
} catch (error) {
return result.data;
}
}
if (result.dataType == 1) {
return result.data;
}
if (result.dataType == 2) {
return result.data.split(',');
}
}
return;
}
/**
* key获得对应的网页数据
* @param key
*/
async htmlByKey(key) {
let html = '<html><title>@title</title><body>@content</body></html>';
let result: any = await this.midwayCache.get(`param:${key}`);
if (result) {
html = html
.replace('@content', result.data)
.replace('@title', result.name);
} else {
html = html.replace('@content', 'key notfound');
}
return html;
}
/**
*
* @param param
*/
async addOrUpdate(param: any, type): Promise<void> {
const find = {
keyName: param.keyName,
};
if (param.id) {
find['id'] = Not(param.id);
}
const check = await this.baseSysParamEntity.findOneBy(find);
if (check) {
throw new CoolCommException('存在相同的keyName');
}
await super.addOrUpdate(param, type);
}
/**
*
*/
async modifyAfter() {
const params = await this.baseSysParamEntity.find();
for (const param of params) {
await this.midwayCache.set(`param:${param.keyName}`, param);
}
}
}

View File

@ -0,0 +1,90 @@
import { Inject, InjectClient, Provide } from '@midwayjs/core';
import { BaseService } from '@cool-midway/core';
import { BaseSysMenuService } from './menu';
import { BaseSysRoleService } from './role';
import { BaseSysDepartmentService } from './department';
import { Context } from '@midwayjs/koa';
import { CachingFactory, MidwayCache } from '@midwayjs/cache-manager';
import { BaseSysRoleEntity } from '../../entity/sys/role';
import { In, Repository } from 'typeorm';
import { InjectEntityModel } from '@midwayjs/typeorm';
/**
*
*/
@Provide()
export class BaseSysPermsService extends BaseService {
@InjectClient(CachingFactory, 'default')
midwayCache: MidwayCache;
@Inject()
baseSysMenuService: BaseSysMenuService;
@Inject()
baseSysRoleService: BaseSysRoleService;
@Inject()
baseSysDepartmentService: BaseSysDepartmentService;
@InjectEntityModel(BaseSysRoleEntity)
baseSysRoleEntity: Repository<BaseSysRoleEntity>;
@Inject()
ctx: Context;
base: any;
/**
*
* @param userId ID
*/
async refreshPerms(userId) {
const roleIds = await this.baseSysRoleService.getByUser(userId);
const perms = await this.baseSysMenuService.getPerms(roleIds);
await this.midwayCache.set(`admin:perms:${userId}`, perms);
// 更新部门权限
const departments = await this.baseSysDepartmentService.getByRoleIds(
roleIds,
await this.isAdmin(roleIds)
);
await this.midwayCache.set(`admin:department:${userId}`, departments);
}
/**
*
* @param roleIds
*/
async isAdmin(roleIds: number[]) {
const roles = await this.baseSysRoleEntity.findBy({ id: In(roleIds) });
const roleLabels = roles.map(item => item.label);
return roleLabels.includes('admin');
}
/**
*
* @param roleIds
*/
async permmenu(roleIds: number[]) {
const perms = await this.baseSysMenuService.getPerms(roleIds);
const menus = await this.baseSysMenuService.getMenus(
roleIds,
this.ctx.admin.username === 'admin'
);
return { perms, menus };
}
/**
* ID获得部门权限
* @param userId
* @return ID数组
*/
async departmentIds(userId: number) {
const department: any = await this.midwayCache.get(
`admin:department:${userId}`
);
if (department) {
return department;
} else {
return [];
}
}
}

View File

@ -0,0 +1,136 @@
import { Inject, Provide } from '@midwayjs/core';
import { BaseService } from '@cool-midway/core';
import { InjectEntityModel } from '@midwayjs/typeorm';
import { Repository } from 'typeorm';
import { BaseSysRoleEntity } from '../../entity/sys/role';
import { BaseSysUserRoleEntity } from '../../entity/sys/user_role';
import * as _ from 'lodash';
import { BaseSysRoleMenuEntity } from '../../entity/sys/role_menu';
import { BaseSysRoleDepartmentEntity } from '../../entity/sys/role_department';
import { BaseSysPermsService } from './perms';
import { Brackets } from 'typeorm';
/**
*
*/
@Provide()
export class BaseSysRoleService extends BaseService {
@InjectEntityModel(BaseSysRoleEntity)
baseSysRoleEntity: Repository<BaseSysRoleEntity>;
@InjectEntityModel(BaseSysUserRoleEntity)
baseSysUserRoleEntity: Repository<BaseSysUserRoleEntity>;
@InjectEntityModel(BaseSysRoleMenuEntity)
baseSysRoleMenuEntity: Repository<BaseSysRoleMenuEntity>;
@InjectEntityModel(BaseSysRoleDepartmentEntity)
baseSysRoleDepartmentEntity: Repository<BaseSysRoleDepartmentEntity>;
@Inject()
baseSysPermsService: BaseSysPermsService;
@Inject()
ctx;
/**
* ID获得所有用户角色
* @param userId
*/
async getByUser(userId: number): Promise<number[]> {
const userRole = await this.baseSysUserRoleEntity.findBy({ userId });
if (!_.isEmpty(userRole)) {
return userRole.map(e => {
return e.roleId;
});
}
return [];
}
/**
*
* @param param
*/
async modifyAfter(param) {
if (param.id) {
this.updatePerms(param.id, param.menuIdList, param.departmentIdList);
}
}
/**
*
* @param roleId
* @param menuIdList
* @param departmentIds
*/
async updatePerms(roleId, menuIdList?, departmentIds = []) {
// 更新菜单权限
await this.baseSysRoleMenuEntity.delete({ roleId });
await Promise.all(
menuIdList.map(async e => {
return await this.baseSysRoleMenuEntity.save({ roleId, menuId: e });
})
);
// 更新部门权限
await this.baseSysRoleDepartmentEntity.delete({ roleId });
await Promise.all(
departmentIds.map(async e => {
return await this.baseSysRoleDepartmentEntity.save({
roleId,
departmentId: e,
});
})
);
// 刷新权限
const userRoles = await this.baseSysUserRoleEntity.findBy({ roleId });
for (const userRole of userRoles) {
await this.baseSysPermsService.refreshPerms(userRole.userId);
}
}
/**
*
* @param id
*/
async info(id) {
const info = await this.baseSysRoleEntity.findOneBy({ id });
if (info) {
const menus = await this.baseSysRoleMenuEntity.findBy(
id !== 1 ? { roleId: id } : {}
);
const menuIdList = menus.map(e => {
return parseInt(e.menuId + '');
});
const departments = await this.baseSysRoleDepartmentEntity.findBy(
id !== 1 ? { roleId: id } : {}
);
const departmentIdList = departments.map(e => {
return parseInt(e.departmentId + '');
});
return {
...info,
menuIdList,
departmentIdList,
};
}
return {};
}
async list() {
return this.baseSysRoleEntity
.createQueryBuilder('a')
.where(
new Brackets(qb => {
qb.where('a.id !=:id', { id: 1 }); // 超级管理员的角色不展示
// 如果不是超管,只能看到自己新建的或者自己有的角色
if (this.ctx.admin.username !== 'admin') {
qb.andWhere('(a.userId=:userId or a.id in (:...roleId))', {
userId: this.ctx.admin.userId,
roleId: this.ctx.admin.roleIds,
});
}
})
)
.getMany();
}
}

View File

@ -0,0 +1,235 @@
import { Inject, InjectClient, Provide } from '@midwayjs/core';
import { BaseService, CoolCommException } from '@cool-midway/core';
import { InjectEntityModel } from '@midwayjs/typeorm';
import { Equal, In, Repository } from 'typeorm';
import { BaseSysUserEntity } from '../../entity/sys/user';
import { BaseSysPermsService } from './perms';
import * as _ from 'lodash';
import { BaseSysUserRoleEntity } from '../../entity/sys/user_role';
import * as md5 from 'md5';
import { BaseSysDepartmentEntity } from '../../entity/sys/department';
import { CachingFactory, MidwayCache } from '@midwayjs/cache-manager';
/**
*
*/
@Provide()
export class BaseSysUserService extends BaseService {
@InjectEntityModel(BaseSysUserEntity)
baseSysUserEntity: Repository<BaseSysUserEntity>;
@InjectEntityModel(BaseSysUserRoleEntity)
baseSysUserRoleEntity: Repository<BaseSysUserRoleEntity>;
@InjectEntityModel(BaseSysDepartmentEntity)
baseSysDepartmentEntity: Repository<BaseSysDepartmentEntity>;
@InjectClient(CachingFactory, 'default')
midwayCache: MidwayCache;
@Inject()
baseSysPermsService: BaseSysPermsService;
@Inject()
ctx;
/**
*
* @param query
*/
async page(query) {
const { keyWord, status, departmentIds = [] } = query;
const permsDepartmentArr = await this.baseSysPermsService.departmentIds(
this.ctx.admin.userId
); // 部门权限
const sql = `
SELECT
a.id,a.name,a.nickName,a.headImg,a.email,a.remark,a.status,a.createTime,a.updateTime,a.username,a.phone,a.departmentId,
b.name as "departmentName"
FROM
base_sys_user a
LEFT JOIN base_sys_department b on a.departmentId = b.id
WHERE 1 = 1
${this.setSql(
!_.isEmpty(departmentIds),
'and a.departmentId in (?)',
[departmentIds]
)}
${this.setSql(status, 'and a.status = ?', [status])}
${this.setSql(keyWord, 'and (a.name LIKE ? or a.username LIKE ?)', [
`%${keyWord}%`,
`%${keyWord}%`,
])}
${this.setSql(true, 'and a.username != ?', ['admin'])}
${this.setSql(
this.ctx.admin.username !== 'admin',
'and a.departmentId in (?)',
[!_.isEmpty(permsDepartmentArr) ? permsDepartmentArr : [null]]
)} `;
const result = await this.sqlRenderPage(sql, query);
// 匹配角色
if (!_.isEmpty(result.list)) {
const userIds = result.list.map(e => e.id);
const roles = await this.nativeQuery(
'SELECT b.name, a.userId FROM base_sys_user_role a LEFT JOIN base_sys_role b ON a.roleId = b.id WHERE a.userId in (?) ',
[userIds]
);
result.list.forEach(e => {
e['roleName'] = roles
.filter(role => role.userId == e.id)
.map(role => role.name)
.join(',');
});
}
return result;
}
/**
*
* @param departmentId
* @param userIds
*/
async move(departmentId, userIds) {
await this.baseSysUserEntity.update({ id: In(userIds) }, { departmentId });
}
/**
*
*/
async person(userId) {
const info = await this.baseSysUserEntity.findOneBy({
id: Equal(userId),
});
delete info?.password;
return info;
}
/**
*
* @param user
*/
async updateUserRole(user) {
if (_.isEmpty(user.roleIdList)) {
return;
}
if (user.username === 'admin') {
throw new CoolCommException('非法操作~');
}
await this.baseSysUserRoleEntity.delete({ userId: user.id });
if (user.roleIdList) {
for (const roleId of user.roleIdList) {
await this.baseSysUserRoleEntity.save({ userId: user.id, roleId });
}
}
await this.baseSysPermsService.refreshPerms(user.id);
}
/**
*
* @param param
*/
async add(param) {
const exists = await this.baseSysUserEntity.findOneBy({
username: param.username,
});
if (!_.isEmpty(exists)) {
throw new CoolCommException('用户名已经存在~');
}
param.password = md5(param.password);
await this.baseSysUserEntity.save(param);
await this.updateUserRole(param);
return param.id;
}
/**
* ID获得信息
* @param id
*/
public async info(id) {
const info = await this.baseSysUserEntity.findOneBy({ id });
const userRoles = await this.nativeQuery(
'select a.roleId from base_sys_user_role a where a.userId = ?',
[id]
);
const department = await this.baseSysDepartmentEntity.findOneBy({
id: info.departmentId,
});
if (info) {
delete info.password;
if (userRoles) {
info.roleIdList = userRoles.map(e => {
return parseInt(e.roleId);
});
}
}
delete info.password;
if (department) {
info.departmentName = department.name;
}
return info;
}
/**
*
* @param param
*/
public async personUpdate(param) {
param.id = this.ctx.admin.userId;
if (!_.isEmpty(param.password)) {
param.password = md5(param.password);
const oldPassword = md5(param.oldPassword);
const userInfo = await this.baseSysUserEntity.findOneBy({ id: param.id });
if (!userInfo) {
throw new CoolCommException('用户不存在');
}
if (oldPassword !== userInfo.password) {
throw new CoolCommException('原密码错误');
}
param.passwordV = userInfo.passwordV + 1;
await this.midwayCache.set(
`admin:passwordVersion:${param.id}`,
param.passwordV
);
} else {
delete param.password;
}
await this.baseSysUserEntity.save(param);
}
/**
*
* @param param
*/
async update(param) {
if (param.id && param.username === 'admin') {
throw new CoolCommException('非法操作~');
}
if (!_.isEmpty(param.password)) {
param.password = md5(param.password);
const userInfo = await this.baseSysUserEntity.findOneBy({ id: param.id });
if (!userInfo) {
throw new CoolCommException('用户不存在');
}
param.passwordV = userInfo.passwordV + 1;
await this.midwayCache.set(
`admin:passwordVersion:${param.id}`,
param.passwordV
);
} else {
delete param.password;
}
if (param.status === 0) {
await this.forbidden(param.id);
}
await this.baseSysUserEntity.save(param);
await this.updateUserRole(param);
}
/**
*
* @param userId
*/
async forbidden(userId) {
await this.midwayCache.del(`admin:token:${userId}`);
}
}

View File

@ -6,23 +6,25 @@
"moduleResolution": "node",
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"inlineSourceMap":true,
"inlineSourceMap": true,
"noImplicitThis": true,
"noUnusedLocals": true,
"noUnusedLocals": false,
"stripInternal": true,
"skipLibCheck": true,
"resolveJsonModule": true,
"pretty": true,
"declaration": true,
"forceConsistentCasingInFileNames": true,
"typeRoots": [ "./typings", "./node_modules/@types"],
"declaration": false,
"noImplicitAny": false,
"typeRoots": [
"typings",
"./node_modules/@types"
],
"outDir": "dist",
"rootDir": "src"
},
"exclude": [
"*.js",
"*.ts",
"dist",
"node_modules",
"test"
]
}
}