多租户

This commit is contained in:
COOL 2025-01-18 21:43:55 +08:00
parent 95f7218d9e
commit b165e97500
12 changed files with 141 additions and 32 deletions

View File

@ -5,8 +5,8 @@
"private": true, "private": true,
"dependencies": { "dependencies": {
"@cool-midway/core": "file:/Users/ap/Documents/src/admin/midway-packages/core", "@cool-midway/core": "file:/Users/ap/Documents/src/admin/midway-packages/core",
"@cool-midway/task": "file:/Users/ap/Documents/src/admin/midway-packages/task",
"@cool-midway/rpc": "file:/Users/ap/Documents/src/admin/midway-packages/rpc", "@cool-midway/rpc": "file:/Users/ap/Documents/src/admin/midway-packages/rpc",
"@cool-midway/task": "file:/Users/ap/Documents/src/admin/midway-packages/task",
"@midwayjs/bootstrap": "^3.20.0", "@midwayjs/bootstrap": "^3.20.0",
"@midwayjs/cache-manager": "^3.20.0", "@midwayjs/cache-manager": "^3.20.0",
"@midwayjs/core": "^3.20.0", "@midwayjs/core": "^3.20.0",

View File

@ -13,6 +13,10 @@ export default {
koa: { koa: {
port: 8001, port: 8001,
}, },
// 开启异步上下文管理
asyncContextManager: {
enable: true,
},
// 静态文件配置 // 静态文件配置
staticFile: { staticFile: {
buffer: true, buffer: true,

View File

@ -28,10 +28,10 @@ export default {
// 实体与路径跟生成代码、前端请求、swagger文档相关 注意:线上不建议开启,以免暴露敏感信息 // 实体与路径跟生成代码、前端请求、swagger文档相关 注意:线上不建议开启,以免暴露敏感信息
eps: true, eps: true,
// 是否自动导入模块数据库 // 是否自动导入模块数据库
initDB: true, initDB: false,
// 判断是否初始化的方式 // 判断是否初始化的方式
initJudge: 'db', initJudge: 'db',
// 是否自动导入模块菜单 // 是否自动导入模块菜单
initMenu: true, initMenu: false,
} as CoolConfig, } as CoolConfig,
} as MidwayConfig; } as MidwayConfig;

View File

@ -12,7 +12,7 @@ export default () => {
// 模块描述 // 模块描述
description: '基础的权限管理功能,包括登录,权限校验', description: '基础的权限管理功能,包括登录,权限校验',
// 中间件 // 中间件
globalMiddlewares: [BaseAuthorityMiddleware, BaseLogMiddleware], globalMiddlewares: [BaseAuthorityMiddleware],
// 模块加载顺序默认为0值越大越优先加载 // 模块加载顺序默认为0值越大越优先加载
order: 10, order: 10,
// app参数配置允许读取的key // app参数配置允许读取的key

View File

@ -1,34 +1,105 @@
import { EventSubscriberModel } from '@midwayjs/typeorm'; import { EventSubscriberModel } from '@midwayjs/typeorm';
import { EntitySubscriberInterface, TransactionCommitEvent } from 'typeorm'; import {
import { BeforeQueryEvent } from 'typeorm/subscriber/event/QueryEvent'; DeleteQueryBuilder,
EntitySubscriberInterface,
InsertQueryBuilder,
SelectQueryBuilder,
UpdateQueryBuilder,
} from 'typeorm';
import * as _ from 'lodash'; import * as _ from 'lodash';
import { App, IMidwayApplication } from '@midwayjs/core'; import {
App,
ASYNC_CONTEXT_KEY,
ASYNC_CONTEXT_MANAGER_KEY,
AsyncContextManager,
IMidwayApplication,
IMidwayContext,
Inject,
} from '@midwayjs/core';
/**
*
* @param ctx
* @param func
*/
export const noTenant = async (ctx, func) => {
let result;
const tenantId = ctx?.admin?.tenantId;
if (tenantId) {
ctx.admin.tenantId = null;
result = await func();
ctx.admin.tenantId = tenantId;
} else {
result = await func();
}
return result;
};
@EventSubscriberModel() @EventSubscriberModel()
export class TenantSubscriber implements EntitySubscriberInterface<any> { export class TenantSubscriber implements EntitySubscriberInterface<any> {
@App() @App()
app: IMidwayApplication; app: IMidwayApplication;
@Inject()
ctx: IMidwayContext;
/** /**
* * ID
* @param event * @returns string | undefined
*/ */
// @ts-ignore getTenantId(): number | undefined {
beforeQuery(event: BeforeQueryEvent<any>, query: string, parameters: any[]) { const contextManager: AsyncContextManager = this.app
if (_.startsWith(event.query, 'SELECT')) { .getApplicationContext()
console.log('event.query', this.app); .get(ASYNC_CONTEXT_MANAGER_KEY);
query = 'SELECT * FROM "tenant11"'; const ctx: any = contextManager.active().getValue(ASYNC_CONTEXT_KEY);
// // 参数 const url = ctx?.url;
// const parameters = event.parameters; if (_.startsWith(url, '/admin/')) {
// // 连接 return ctx?.admin?.tenantId;
// const queryRunner = event.connection.createQueryRunner();
// event.connection.destroy();
// // // sql语句
// // const query = event.query;
// // const queryRunner = connection.createQueryRunner();
// // 原来的event.queryRunner 干掉
// event.query = 'SELECT * FROM "tenant11"';
// throw new Error('test');
} }
} }
/**
* ID条件
* @param queryBuilder
*/
afterSelectQueryBuilder(queryBuilder: SelectQueryBuilder<any>) {
const tenantId = this.getTenantId();
if (tenantId) {
queryBuilder.where('tenantId = :tenantId', { tenantId });
}
}
// /**
// * 插入时添加租户ID
// * @param queryBuilder
// */
// async afterInsertQueryBuilder(queryBuilder: InsertQueryBuilder<any>) {
// const tenantId = await this.getTenantId();
// if (tenantId) {
// queryBuilder.values({ tenantId });
// }
// }
// /**
// * 更新时添加租户ID和条件
// * @param queryBuilder
// */
// async afterUpdateQueryBuilder(queryBuilder: UpdateQueryBuilder<any>) {
// const tenantId = await this.getTenantId();
// if (tenantId) {
// queryBuilder.set({ tenantId });
// queryBuilder.where('tenantId = :tenantId', { tenantId });
// }
// }
// /**
// * 删除时添加租户ID和条件
// * @param queryBuilder
// */
// async afterDeleteQueryBuilder(queryBuilder: DeleteQueryBuilder<any>) {
// const tenantId = await this.getTenantId();
// if (tenantId) {
// queryBuilder.where('tenantId = :tenantId', { tenantId });
// }
// }
} }

View File

@ -3,6 +3,7 @@ import {
UpdateDateColumn, UpdateDateColumn,
CreateDateColumn, CreateDateColumn,
PrimaryGeneratedColumn, PrimaryGeneratedColumn,
Column,
} from 'typeorm'; } from 'typeorm';
import { CoolBaseEntity } from '@cool-midway/core'; import { CoolBaseEntity } from '@cool-midway/core';
@ -24,7 +25,7 @@ export abstract class BaseEntity extends CoolBaseEntity {
@UpdateDateColumn({ comment: '更新时间' }) @UpdateDateColumn({ comment: '更新时间' })
updateTime: Date; updateTime: Date;
// @Index() @Index()
// @Column({ comment: '租户ID', nullable: true }) @Column({ comment: '租户ID', nullable: true })
// tenantId: number; tenantId: number;
} }

View File

@ -51,6 +51,7 @@ export class BaseAuthorityMiddleware
if (_.startsWith(url, adminUrl)) { if (_.startsWith(url, adminUrl)) {
try { try {
ctx.admin = jwt.verify(token, this.jwtConfig.jwt.secret); ctx.admin = jwt.verify(token, this.jwtConfig.jwt.secret);
ctx.admin.tenantId = 1;
if (ctx.admin.isRefresh) { if (ctx.admin.isRefresh) {
ctx.status = 401; ctx.status = 401;
ctx.body = { ctx.body = {

View File

@ -195,6 +195,7 @@ export class BaseSysLoginService extends BaseService {
username: user.username, username: user.username,
userId: user.id, userId: user.id,
passwordVersion: user.passwordV, passwordVersion: user.passwordV,
tenantId: user['tenantId'],
}; };
if (isRefresh) { if (isRefresh) {
tokenInfo.isRefresh = true; tokenInfo.isRefresh = true;

View File

@ -1,5 +1,7 @@
import { CoolController, BaseController } from '@cool-midway/core'; import { CoolController, BaseController } from '@cool-midway/core';
import { DemoGoodsEntity } from '../../entity/goods'; import { DemoGoodsEntity } from '../../entity/goods';
import { Get, Inject } from '@midwayjs/core';
import { DemoGoodsService } from '../../service/goods';
/** /**
* - * -
@ -8,4 +10,13 @@ import { DemoGoodsEntity } from '../../entity/goods';
api: ['add', 'delete', 'update', 'info', 'list', 'page'], api: ['add', 'delete', 'update', 'info', 'list', 'page'],
entity: DemoGoodsEntity, entity: DemoGoodsEntity,
}) })
export class AdminDemoGoodsController extends BaseController {} export class AdminDemoGoodsController extends BaseController {
@Inject()
demoGoodsService: DemoGoodsService;
@Get('/test', { summary: '测试' })
async test() {
await this.demoGoodsService.test();
return this.ok('test');
}
}

View File

@ -1,6 +1,6 @@
import { DemoGoodsService } from '../../service/goods'; import { DemoGoodsService } from '../../service/goods';
import { DemoGoodsEntity } from '../../entity/goods'; import { DemoGoodsEntity } from '../../entity/goods';
import { Body, Config, Inject, Post, Provide } from '@midwayjs/core'; import { Body, Config, Get, Inject, Post, Provide } from '@midwayjs/core';
import { CoolController, BaseController } from '@cool-midway/core'; import { CoolController, BaseController } from '@cool-midway/core';
import { InjectEntityModel } from '@midwayjs/typeorm'; import { InjectEntityModel } from '@midwayjs/typeorm';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
@ -29,4 +29,10 @@ export class OpenDemoGoodsController extends BaseController {
async entityPage(@Body() query) { async entityPage(@Body() query) {
return this.ok(await this.demoGoodsService.entityPage(query)); return this.ok(await this.demoGoodsService.entityPage(query));
} }
@Get('/test', { summary: '测试' })
async test() {
await this.demoGoodsService.test();
return this.ok('test');
}
} }

View File

@ -1,8 +1,9 @@
import { DemoGoodsEntity } from './../entity/goods'; import { DemoGoodsEntity } from './../entity/goods';
import { Provide } from '@midwayjs/core'; import { Inject, Provide } from '@midwayjs/core';
import { BaseService } from '@cool-midway/core'; import { BaseService } from '@cool-midway/core';
import { InjectEntityModel } from '@midwayjs/typeorm'; import { InjectEntityModel } from '@midwayjs/typeorm';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
import { noTenant } from '../../base/db/tenant';
/** /**
* *
@ -12,6 +13,9 @@ export class DemoGoodsService extends BaseService {
@InjectEntityModel(DemoGoodsEntity) @InjectEntityModel(DemoGoodsEntity)
demoGoodsEntity: Repository<DemoGoodsEntity>; demoGoodsEntity: Repository<DemoGoodsEntity>;
@Inject()
ctx;
/** /**
* sql分页 * sql分页
*/ */
@ -37,4 +41,14 @@ export class DemoGoodsService extends BaseService {
const find = this.demoGoodsEntity.createQueryBuilder(); const find = this.demoGoodsEntity.createQueryBuilder();
return this.entityRenderPage(find, query); return this.entityRenderPage(find, query);
} }
async test() {
const a = await this.demoGoodsEntity.createQueryBuilder().getMany();
await noTenant(this.ctx, async () => {
const b = await this.demoGoodsEntity.createQueryBuilder().getMany();
console.log('b');
});
const c = await this.demoGoodsEntity.createQueryBuilder().getMany();
console.log(a);
}
} }

View File

@ -38,7 +38,7 @@ export default ({ app }) => {
components: { components: {
schemas: {}, schemas: {},
securitySchemes: { securitySchemes: {
Auth: { ApiKeyAuth: {
type: 'apiKey', type: 'apiKey',
name: 'Authorization', name: 'Authorization',
in: 'header', in: 'header',