mirror of
https://github.com/cool-team-official/cool-admin-midway.git
synced 2025-12-11 16:52:49 +00:00
完善多租户&国际化
This commit is contained in:
parent
b165e97500
commit
71ee2e7393
2
.vscode/middleware.code-snippets
vendored
2
.vscode/middleware.code-snippets
vendored
@ -2,7 +2,7 @@
|
|||||||
"middleware": {
|
"middleware": {
|
||||||
"prefix": "middleware",
|
"prefix": "middleware",
|
||||||
"body": [
|
"body": [
|
||||||
"import { Middleware } from '@midwayjs/decorator';",
|
"import { Middleware } from '@midwayjs/core';",
|
||||||
"import { NextFunction, Context } from '@midwayjs/koa';",
|
"import { NextFunction, Context } from '@midwayjs/koa';",
|
||||||
"import { IMiddleware } from '@midwayjs/core';",
|
"import { IMiddleware } from '@midwayjs/core';",
|
||||||
"",
|
"",
|
||||||
|
|||||||
3
.vscode/service.code-snippets
vendored
3
.vscode/service.code-snippets
vendored
@ -2,7 +2,8 @@
|
|||||||
"service": {
|
"service": {
|
||||||
"prefix": "service",
|
"prefix": "service",
|
||||||
"body": [
|
"body": [
|
||||||
"import { BaseService, Init, Provide } from '@cool-midway/core';",
|
"import { Init, Provide } from '@midwayjs/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';",
|
||||||
"",
|
"",
|
||||||
|
|||||||
1
bootstrap.js
vendored
1
bootstrap.js
vendored
@ -1,4 +1,3 @@
|
|||||||
process.env.NODE_ENV = 'local';
|
|
||||||
const { Bootstrap } = require('@midwayjs/bootstrap');
|
const { Bootstrap } = require('@midwayjs/bootstrap');
|
||||||
|
|
||||||
// 显式以组件方式引入用户代码
|
// 显式以组件方式引入用户代码
|
||||||
|
|||||||
17
pnpm-lock.yaml
generated
17
pnpm-lock.yaml
generated
@ -1344,8 +1344,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==}
|
resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
caniuse-lite@1.0.30001692:
|
caniuse-lite@1.0.30001695:
|
||||||
resolution: {integrity: sha512-A95VKan0kdtrsnMubMKxEKUKImOPSuCpYgxSQBo036P5YYgVIcOYJEgt/txJWqObiRQeISNCfef9nvlQ0vbV7A==}
|
resolution: {integrity: sha512-vHyLade6wTgI2u1ec3WQBxv+2BrTERV28UXQu9LO6lZ9pYeMk34vjXFLOxo1A4UBA8XTL4njRQZdno/yYaSmWw==}
|
||||||
|
|
||||||
chalk@2.4.2:
|
chalk@2.4.2:
|
||||||
resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==}
|
resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==}
|
||||||
@ -3179,6 +3179,11 @@ packages:
|
|||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
mkdirp@2.1.6:
|
||||||
|
resolution: {integrity: sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
mkdirp@3.0.1:
|
mkdirp@3.0.1:
|
||||||
resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==}
|
resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
@ -6106,7 +6111,7 @@ snapshots:
|
|||||||
|
|
||||||
browserslist@4.24.4:
|
browserslist@4.24.4:
|
||||||
dependencies:
|
dependencies:
|
||||||
caniuse-lite: 1.0.30001692
|
caniuse-lite: 1.0.30001695
|
||||||
electron-to-chromium: 1.5.83
|
electron-to-chromium: 1.5.83
|
||||||
node-releases: 2.0.19
|
node-releases: 2.0.19
|
||||||
update-browserslist-db: 1.1.2(browserslist@4.24.4)
|
update-browserslist-db: 1.1.2(browserslist@4.24.4)
|
||||||
@ -6244,7 +6249,7 @@ snapshots:
|
|||||||
|
|
||||||
camelcase@6.3.0: {}
|
camelcase@6.3.0: {}
|
||||||
|
|
||||||
caniuse-lite@1.0.30001692: {}
|
caniuse-lite@1.0.30001695: {}
|
||||||
|
|
||||||
chalk@2.4.2:
|
chalk@2.4.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -8352,6 +8357,8 @@ snapshots:
|
|||||||
|
|
||||||
mkdirp@2.1.3: {}
|
mkdirp@2.1.3: {}
|
||||||
|
|
||||||
|
mkdirp@2.1.6: {}
|
||||||
|
|
||||||
mkdirp@3.0.1: {}
|
mkdirp@3.0.1: {}
|
||||||
|
|
||||||
module-details-from-path@1.0.3: {}
|
module-details-from-path@1.0.3: {}
|
||||||
@ -9716,7 +9723,7 @@ snapshots:
|
|||||||
debug: 4.4.0
|
debug: 4.4.0
|
||||||
dotenv: 16.4.7
|
dotenv: 16.4.7
|
||||||
glob: 10.4.5
|
glob: 10.4.5
|
||||||
mkdirp: 2.1.3
|
mkdirp: 2.1.6
|
||||||
reflect-metadata: 0.2.2
|
reflect-metadata: 0.2.2
|
||||||
sha.js: 2.4.11
|
sha.js: 2.4.11
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|||||||
@ -150,4 +150,45 @@ export class Utils {
|
|||||||
}
|
}
|
||||||
return camelCaseObject;
|
return camelCaseObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 匹配URL
|
||||||
|
* @param pattern
|
||||||
|
* @param url
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
matchUrl(pattern, url) {
|
||||||
|
// 将 pattern 和 url 按 `/` 分割
|
||||||
|
const patternParts = pattern.split('/').filter(Boolean);
|
||||||
|
const urlParts = url.split('/').filter(Boolean);
|
||||||
|
// 如果长度不匹配且 pattern 不包含 **,直接返回 false
|
||||||
|
if (patternParts.length !== urlParts.length && !pattern.includes('**')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (let i = 0; i < patternParts.length; i++) {
|
||||||
|
const patternPart = patternParts[i];
|
||||||
|
const urlPart = urlParts[i];
|
||||||
|
// 如果 patternPart 是 **,匹配剩余的所有部分
|
||||||
|
if (patternPart === '**') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// 如果 patternPart 以 : 开头,说明是参数,直接匹配任意非空值
|
||||||
|
if (patternPart.startsWith(':')) {
|
||||||
|
if (!urlPart) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// 如果 patternPart 是 *,匹配任意非空部分
|
||||||
|
if (patternPart === '*') {
|
||||||
|
if (!urlPart) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if (patternPart !== urlPart) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 如果 pattern 和 url 的部分数量一致,则匹配成功
|
||||||
|
return patternParts.length === urlParts.length;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -65,15 +65,19 @@ export default {
|
|||||||
cool: {
|
cool: {
|
||||||
// 已经插件化,本地文件上传查看 plugin/config.ts,其他云存储查看对应插件的使用
|
// 已经插件化,本地文件上传查看 plugin/config.ts,其他云存储查看对应插件的使用
|
||||||
file: {},
|
file: {},
|
||||||
rpc: {
|
// 是否开启多租户
|
||||||
name: 'main',
|
tenant: {
|
||||||
|
// 是否开启多租户
|
||||||
|
enable: true,
|
||||||
|
// 需要过滤多租户的url
|
||||||
|
urls: [],
|
||||||
},
|
},
|
||||||
// redis配置
|
// 国际化配置
|
||||||
redis: {
|
i18n: {
|
||||||
port: 6379,
|
// 是否开启
|
||||||
host: '127.0.0.1',
|
enable: false,
|
||||||
password: '',
|
// 语言
|
||||||
db: 0,
|
languages: ['zh-cn', 'zh-tw', 'en'],
|
||||||
},
|
},
|
||||||
// crud配置
|
// crud配置
|
||||||
crud: {
|
crud: {
|
||||||
|
|||||||
@ -28,10 +28,10 @@ export default {
|
|||||||
// 实体与路径,跟生成代码、前端请求、swagger文档相关 注意:线上不建议开启,以免暴露敏感信息
|
// 实体与路径,跟生成代码、前端请求、swagger文档相关 注意:线上不建议开启,以免暴露敏感信息
|
||||||
eps: true,
|
eps: true,
|
||||||
// 是否自动导入模块数据库
|
// 是否自动导入模块数据库
|
||||||
initDB: false,
|
initDB: true,
|
||||||
// 判断是否初始化的方式
|
// 判断是否初始化的方式
|
||||||
initJudge: 'db',
|
initJudge: 'db',
|
||||||
// 是否自动导入模块菜单
|
// 是否自动导入模块菜单
|
||||||
initMenu: false,
|
initMenu: true,
|
||||||
} as CoolConfig,
|
} as CoolConfig,
|
||||||
} as MidwayConfig;
|
} as MidwayConfig;
|
||||||
|
|||||||
@ -18,8 +18,8 @@ import * as LocalConfig from './config/config.local';
|
|||||||
import * as ProdConfig from './config/config.prod';
|
import * as ProdConfig from './config/config.prod';
|
||||||
import * as cool from '@cool-midway/core';
|
import * as cool from '@cool-midway/core';
|
||||||
import * as upload from '@midwayjs/upload';
|
import * as upload from '@midwayjs/upload';
|
||||||
import * as task from '@cool-midway/task';
|
// import * as task from '@cool-midway/task';
|
||||||
import * as rpc from '@cool-midway/rpc';
|
// import * as rpc from '@cool-midway/rpc';
|
||||||
|
|
||||||
@Configuration({
|
@Configuration({
|
||||||
imports: [
|
imports: [
|
||||||
@ -40,9 +40,9 @@ import * as rpc from '@cool-midway/rpc';
|
|||||||
// cool-admin 官方组件 https://cool-js.com
|
// cool-admin 官方组件 https://cool-js.com
|
||||||
cool,
|
cool,
|
||||||
// rpc 微服务 远程调用
|
// rpc 微服务 远程调用
|
||||||
rpc,
|
// rpc,
|
||||||
// 任务与队列
|
// 任务与队列
|
||||||
task,
|
// task,
|
||||||
{
|
{
|
||||||
component: info,
|
component: info,
|
||||||
enabledEnvironment: ['local', 'prod'],
|
enabledEnvironment: ['local', 'prod'],
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { BaseLogMiddleware } from './middleware/log';
|
import { BaseLogMiddleware } from './middleware/log';
|
||||||
import { BaseAuthorityMiddleware } from './middleware/authority';
|
import { BaseAuthorityMiddleware } from './middleware/authority';
|
||||||
import { ModuleConfig } from '@cool-midway/core';
|
import { ModuleConfig } from '@cool-midway/core';
|
||||||
|
import { BaseTranslateMiddleware } from './middleware/translate';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 模块的配置
|
* 模块的配置
|
||||||
@ -12,7 +13,11 @@ export default () => {
|
|||||||
// 模块描述
|
// 模块描述
|
||||||
description: '基础的权限管理功能,包括登录,权限校验',
|
description: '基础的权限管理功能,包括登录,权限校验',
|
||||||
// 中间件
|
// 中间件
|
||||||
globalMiddlewares: [BaseAuthorityMiddleware],
|
globalMiddlewares: [
|
||||||
|
BaseTranslateMiddleware,
|
||||||
|
BaseAuthorityMiddleware,
|
||||||
|
BaseLogMiddleware,
|
||||||
|
],
|
||||||
// 模块加载顺序,默认为0,值越大越优先加载
|
// 模块加载顺序,默认为0,值越大越优先加载
|
||||||
order: 10,
|
order: 10,
|
||||||
// app参数配置允许读取的key
|
// app参数配置允许读取的key
|
||||||
|
|||||||
@ -12,10 +12,12 @@ import {
|
|||||||
ASYNC_CONTEXT_KEY,
|
ASYNC_CONTEXT_KEY,
|
||||||
ASYNC_CONTEXT_MANAGER_KEY,
|
ASYNC_CONTEXT_MANAGER_KEY,
|
||||||
AsyncContextManager,
|
AsyncContextManager,
|
||||||
|
Config,
|
||||||
IMidwayApplication,
|
IMidwayApplication,
|
||||||
IMidwayContext,
|
IMidwayContext,
|
||||||
Inject,
|
Inject,
|
||||||
} from '@midwayjs/core';
|
} from '@midwayjs/core';
|
||||||
|
import { Utils } from '../../../comm/utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 不操作租户
|
* 不操作租户
|
||||||
@ -43,19 +45,67 @@ export class TenantSubscriber implements EntitySubscriberInterface<any> {
|
|||||||
@Inject()
|
@Inject()
|
||||||
ctx: IMidwayContext;
|
ctx: IMidwayContext;
|
||||||
|
|
||||||
|
@Config('cool.tenant')
|
||||||
|
tenant: {
|
||||||
|
// 是否开启多租户
|
||||||
|
enable: boolean;
|
||||||
|
// 需要过滤多租户的url
|
||||||
|
urls: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
utils: Utils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查是否需要租户
|
||||||
|
*/
|
||||||
|
checkHandler() {
|
||||||
|
const ctx = this.getCtx();
|
||||||
|
if (!ctx) return false;
|
||||||
|
const url = ctx?.url;
|
||||||
|
if (!url) return false;
|
||||||
|
if (this.tenant.enable) {
|
||||||
|
const isNeedTenant = this.tenant.urls.some(pattern =>
|
||||||
|
this.utils.matchUrl(pattern, url)
|
||||||
|
);
|
||||||
|
return isNeedTenant;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取ctx
|
||||||
|
*/
|
||||||
|
getCtx(): any {
|
||||||
|
try {
|
||||||
|
const contextManager: AsyncContextManager = this.app
|
||||||
|
.getApplicationContext()
|
||||||
|
.get(ASYNC_CONTEXT_MANAGER_KEY);
|
||||||
|
return contextManager.active().getValue(ASYNC_CONTEXT_KEY);
|
||||||
|
} catch (error) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 从登录的用户中获取租户ID
|
* 从登录的用户中获取租户ID
|
||||||
* @returns string | undefined
|
* @returns string | undefined
|
||||||
*/
|
*/
|
||||||
getTenantId(): number | undefined {
|
getTenantId(): number | undefined {
|
||||||
const contextManager: AsyncContextManager = this.app
|
let ctx, url, tenantId;
|
||||||
.getApplicationContext()
|
ctx = this.getCtx();
|
||||||
.get(ASYNC_CONTEXT_MANAGER_KEY);
|
if (!ctx || this.checkHandler()) return undefined;
|
||||||
const ctx: any = contextManager.active().getValue(ASYNC_CONTEXT_KEY);
|
url = ctx?.url;
|
||||||
const url = ctx?.url;
|
|
||||||
if (_.startsWith(url, '/admin/')) {
|
if (_.startsWith(url, '/admin/')) {
|
||||||
return ctx?.admin?.tenantId;
|
tenantId = ctx?.admin?.tenantId;
|
||||||
|
} else if (_.startsWith(url, '/app/')) {
|
||||||
|
tenantId = ctx?.user?.tenantId;
|
||||||
}
|
}
|
||||||
|
if (tenantId && url) {
|
||||||
|
return tenantId;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -63,43 +113,47 @@ export class TenantSubscriber implements EntitySubscriberInterface<any> {
|
|||||||
* @param queryBuilder
|
* @param queryBuilder
|
||||||
*/
|
*/
|
||||||
afterSelectQueryBuilder(queryBuilder: SelectQueryBuilder<any>) {
|
afterSelectQueryBuilder(queryBuilder: SelectQueryBuilder<any>) {
|
||||||
|
if (!this.tenant.enable) return;
|
||||||
const tenantId = this.getTenantId();
|
const tenantId = this.getTenantId();
|
||||||
if (tenantId) {
|
if (tenantId) {
|
||||||
queryBuilder.where('tenantId = :tenantId', { tenantId });
|
queryBuilder.where('tenantId = :tenantId', { tenantId });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// /**
|
/**
|
||||||
// * 插入时添加租户ID
|
* 插入时添加租户ID
|
||||||
// * @param queryBuilder
|
* @param queryBuilder
|
||||||
// */
|
*/
|
||||||
// async afterInsertQueryBuilder(queryBuilder: InsertQueryBuilder<any>) {
|
async afterInsertQueryBuilder(queryBuilder: InsertQueryBuilder<any>) {
|
||||||
// const tenantId = await this.getTenantId();
|
if (!this.tenant.enable) return;
|
||||||
// if (tenantId) {
|
const tenantId = await this.getTenantId();
|
||||||
// queryBuilder.values({ tenantId });
|
if (tenantId) {
|
||||||
// }
|
queryBuilder.values({ tenantId });
|
||||||
// }
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// /**
|
/**
|
||||||
// * 更新时添加租户ID和条件
|
* 更新时添加租户ID和条件
|
||||||
// * @param queryBuilder
|
* @param queryBuilder
|
||||||
// */
|
*/
|
||||||
// async afterUpdateQueryBuilder(queryBuilder: UpdateQueryBuilder<any>) {
|
async afterUpdateQueryBuilder(queryBuilder: UpdateQueryBuilder<any>) {
|
||||||
// const tenantId = await this.getTenantId();
|
if (!this.tenant.enable) return;
|
||||||
// if (tenantId) {
|
const tenantId = await this.getTenantId();
|
||||||
// queryBuilder.set({ tenantId });
|
if (tenantId) {
|
||||||
// queryBuilder.where('tenantId = :tenantId', { tenantId });
|
queryBuilder.set({ tenantId });
|
||||||
// }
|
queryBuilder.where('tenantId = :tenantId', { tenantId });
|
||||||
// }
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// /**
|
/**
|
||||||
// * 删除时添加租户ID和条件
|
* 删除时添加租户ID和条件
|
||||||
// * @param queryBuilder
|
* @param queryBuilder
|
||||||
// */
|
*/
|
||||||
// async afterDeleteQueryBuilder(queryBuilder: DeleteQueryBuilder<any>) {
|
async afterDeleteQueryBuilder(queryBuilder: DeleteQueryBuilder<any>) {
|
||||||
// const tenantId = await this.getTenantId();
|
if (!this.tenant.enable) return;
|
||||||
// if (tenantId) {
|
const tenantId = await this.getTenantId();
|
||||||
// queryBuilder.where('tenantId = :tenantId', { tenantId });
|
if (tenantId) {
|
||||||
// }
|
queryBuilder.where('tenantId = :tenantId', { tenantId });
|
||||||
// }
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import {
|
|||||||
import { CoolBaseEntity } from '@cool-midway/core';
|
import { CoolBaseEntity } from '@cool-midway/core';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 模型基类
|
* 实体基类
|
||||||
*/
|
*/
|
||||||
export abstract class BaseEntity extends CoolBaseEntity {
|
export abstract class BaseEntity extends CoolBaseEntity {
|
||||||
// 默认自增
|
// 默认自增
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import {
|
|||||||
Inject,
|
Inject,
|
||||||
Logger,
|
Logger,
|
||||||
} from '@midwayjs/core';
|
} from '@midwayjs/core';
|
||||||
|
import { BaseTranslateService } from '../service/translate';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 导入菜单
|
* 导入菜单
|
||||||
@ -25,6 +26,9 @@ export class BaseMenuEvent {
|
|||||||
@Inject()
|
@Inject()
|
||||||
coolEventManager: CoolEventManager;
|
coolEventManager: CoolEventManager;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
baseTranslateService: BaseTranslateService;
|
||||||
|
|
||||||
@Event('onMenuImport')
|
@Event('onMenuImport')
|
||||||
async onMenuImport(datas) {
|
async onMenuImport(datas) {
|
||||||
for (const module in datas) {
|
for (const module in datas) {
|
||||||
@ -36,5 +40,11 @@ export class BaseMenuEvent {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
this.coolEventManager.emit('onMenuInit', {});
|
this.coolEventManager.emit('onMenuInit', {});
|
||||||
|
this.baseTranslateService.check();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Event('onServerReady')
|
||||||
|
async onServerReady() {
|
||||||
|
this.baseTranslateService.loadTranslations();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { App, Config, Inject, Middleware } from '@midwayjs/core';
|
import { App, Config, Inject, Middleware } from '@midwayjs/core';
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
import { CoolUrlTagData, RESCODE, TagTypes } from '@cool-midway/core';
|
import { CoolCommException, CoolUrlTagData, TagTypes } from '@cool-midway/core';
|
||||||
import * as jwt from 'jsonwebtoken';
|
import * as jwt from 'jsonwebtoken';
|
||||||
import { NextFunction, Context } from '@midwayjs/koa';
|
import { NextFunction, Context } from '@midwayjs/koa';
|
||||||
import {
|
import {
|
||||||
@ -10,6 +10,7 @@ import {
|
|||||||
InjectClient,
|
InjectClient,
|
||||||
} from '@midwayjs/core';
|
} from '@midwayjs/core';
|
||||||
import { CachingFactory, MidwayCache } from '@midwayjs/cache-manager';
|
import { CachingFactory, MidwayCache } from '@midwayjs/cache-manager';
|
||||||
|
import { Utils } from '../../../comm/utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 权限校验
|
* 权限校验
|
||||||
@ -33,6 +34,9 @@ export class BaseAuthorityMiddleware
|
|||||||
@App()
|
@App()
|
||||||
app: IMidwayApplication;
|
app: IMidwayApplication;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
utils: Utils;
|
||||||
|
|
||||||
ignoreUrls: string[] = [];
|
ignoreUrls: string[] = [];
|
||||||
|
|
||||||
@Init()
|
@Init()
|
||||||
@ -51,19 +55,14 @@ 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 = {
|
throw new CoolCommException('登录失效~');
|
||||||
code: RESCODE.COMMFAIL,
|
|
||||||
message: '登录失效~',
|
|
||||||
};
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
} catch (error) {}
|
} catch (error) {}
|
||||||
// 使用matchUrl方法来检查URL是否应该被忽略
|
// 使用matchUrl方法来检查URL是否应该被忽略
|
||||||
const isIgnored = this.ignoreUrls.some(pattern =>
|
const isIgnored = this.ignoreUrls.some(pattern =>
|
||||||
this.matchUrl(pattern, url)
|
this.utils.matchUrl(pattern, url)
|
||||||
);
|
);
|
||||||
if (isIgnored) {
|
if (isIgnored) {
|
||||||
await next();
|
await next();
|
||||||
@ -79,21 +78,13 @@ export class BaseAuthorityMiddleware
|
|||||||
);
|
);
|
||||||
if (passwordV != ctx.admin.passwordVersion) {
|
if (passwordV != ctx.admin.passwordVersion) {
|
||||||
ctx.status = 401;
|
ctx.status = 401;
|
||||||
ctx.body = {
|
throw new CoolCommException('登录失效~');
|
||||||
code: RESCODE.COMMFAIL,
|
|
||||||
message: '登录失效~',
|
|
||||||
};
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
// 超管拥有所有权限
|
// 超管拥有所有权限
|
||||||
if (ctx.admin.username == 'admin' && !ctx.admin.isRefresh) {
|
if (ctx.admin.username == 'admin' && !ctx.admin.isRefresh) {
|
||||||
if (rToken !== token && this.jwtConfig.jwt.sso) {
|
if (rToken !== token && this.jwtConfig.jwt.sso) {
|
||||||
ctx.status = 401;
|
ctx.status = 401;
|
||||||
ctx.body = {
|
throw new CoolCommException('登录失效~');
|
||||||
code: RESCODE.COMMFAIL,
|
|
||||||
message: '登录失效~',
|
|
||||||
};
|
|
||||||
return;
|
|
||||||
} else {
|
} else {
|
||||||
await next();
|
await next();
|
||||||
return;
|
return;
|
||||||
@ -111,19 +102,11 @@ export class BaseAuthorityMiddleware
|
|||||||
// 如果传的token是refreshToken则校验失败
|
// 如果传的token是refreshToken则校验失败
|
||||||
if (ctx.admin.isRefresh) {
|
if (ctx.admin.isRefresh) {
|
||||||
ctx.status = 401;
|
ctx.status = 401;
|
||||||
ctx.body = {
|
throw new CoolCommException('登录失效~');
|
||||||
code: RESCODE.COMMFAIL,
|
|
||||||
message: '登录失效~',
|
|
||||||
};
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
if (!rToken) {
|
if (!rToken) {
|
||||||
ctx.status = 401;
|
ctx.status = 401;
|
||||||
ctx.body = {
|
throw new CoolCommException('登录失效或无权限访问~');
|
||||||
code: RESCODE.COMMFAIL,
|
|
||||||
message: '登录失效或无权限访问~',
|
|
||||||
};
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
if (rToken !== token && this.jwtConfig.jwt.sso) {
|
if (rToken !== token && this.jwtConfig.jwt.sso) {
|
||||||
statusCode = 401;
|
statusCode = 401;
|
||||||
@ -147,40 +130,10 @@ export class BaseAuthorityMiddleware
|
|||||||
}
|
}
|
||||||
if (statusCode > 200) {
|
if (statusCode > 200) {
|
||||||
ctx.status = statusCode;
|
ctx.status = statusCode;
|
||||||
ctx.body = {
|
throw new CoolCommException('登录失效或无权限访问~');
|
||||||
code: RESCODE.COMMFAIL,
|
|
||||||
message: '登录失效或无权限访问~',
|
|
||||||
};
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await next();
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
74
src/modules/base/middleware/translate.ts
Normal file
74
src/modules/base/middleware/translate.ts
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import { Config, ILogger, Middleware } from '@midwayjs/core';
|
||||||
|
import { NextFunction, Context } from '@midwayjs/koa';
|
||||||
|
import { IMiddleware, Inject } from '@midwayjs/core';
|
||||||
|
import { BaseTranslateService } from '../service/translate';
|
||||||
|
import * as _ from 'lodash';
|
||||||
|
import { RESCODE } from '@cool-midway/core';
|
||||||
|
/**
|
||||||
|
* 翻译中间件
|
||||||
|
*/
|
||||||
|
@Middleware()
|
||||||
|
export class BaseTranslateMiddleware
|
||||||
|
implements IMiddleware<Context, NextFunction>
|
||||||
|
{
|
||||||
|
@Inject()
|
||||||
|
baseTranslateService: BaseTranslateService;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
logger: ILogger;
|
||||||
|
|
||||||
|
@Config('cool.i18n')
|
||||||
|
config: {
|
||||||
|
/** 是否开启 */
|
||||||
|
enable: boolean;
|
||||||
|
/** 语言 */
|
||||||
|
languages: string[];
|
||||||
|
/** 翻译服务 */
|
||||||
|
serviceUrl?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
resolve() {
|
||||||
|
return async (ctx, next: NextFunction) => {
|
||||||
|
const url = ctx.url;
|
||||||
|
const language = 'en';
|
||||||
|
let data;
|
||||||
|
try {
|
||||||
|
data = await next();
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(error);
|
||||||
|
// 处理翻译消息
|
||||||
|
if (error.name == 'CoolCommException') {
|
||||||
|
if (language && error.message && error.message !== 'success') {
|
||||||
|
ctx.status = 200;
|
||||||
|
ctx.body = {
|
||||||
|
code: RESCODE.COMMFAIL,
|
||||||
|
message: await this.baseTranslateService.translate(
|
||||||
|
'msg',
|
||||||
|
language,
|
||||||
|
error.message
|
||||||
|
),
|
||||||
|
};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this.config.enable) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 处理菜单翻译
|
||||||
|
if (url == '/admin/base/comm/permmenu') {
|
||||||
|
console.log(data);
|
||||||
|
for (const menu of data.data.menus) {
|
||||||
|
if (menu.name) {
|
||||||
|
menu.name = await this.baseTranslateService.translate(
|
||||||
|
'menu',
|
||||||
|
language,
|
||||||
|
menu.name
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
295
src/modules/base/service/translate.ts
Normal file
295
src/modules/base/service/translate.ts
Normal file
@ -0,0 +1,295 @@
|
|||||||
|
import { I18N } from '@cool-midway/core';
|
||||||
|
import { InjectEntityModel } from '@midwayjs/typeorm';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
import { BaseSysMenuEntity } from '../entity/sys/menu';
|
||||||
|
import {
|
||||||
|
App,
|
||||||
|
Config,
|
||||||
|
ILogger,
|
||||||
|
IMidwayApplication,
|
||||||
|
Inject,
|
||||||
|
Provide,
|
||||||
|
Scope,
|
||||||
|
ScopeEnum,
|
||||||
|
} from '@midwayjs/core';
|
||||||
|
import * as path from 'path';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import axios from 'axios';
|
||||||
|
/**
|
||||||
|
* 翻译服务
|
||||||
|
*/
|
||||||
|
@Provide()
|
||||||
|
@Scope(ScopeEnum.Singleton)
|
||||||
|
export class BaseTranslateService {
|
||||||
|
@InjectEntityModel(BaseSysMenuEntity)
|
||||||
|
baseSysMenuEntity: Repository<BaseSysMenuEntity>;
|
||||||
|
|
||||||
|
// 基础路径
|
||||||
|
basePath: string;
|
||||||
|
|
||||||
|
@App()
|
||||||
|
app: IMidwayApplication;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
logger: ILogger;
|
||||||
|
|
||||||
|
@Config('cool.i18n')
|
||||||
|
config: {
|
||||||
|
/** 是否开启 */
|
||||||
|
enable: boolean;
|
||||||
|
/** 语言 */
|
||||||
|
languages: string[];
|
||||||
|
/** 翻译服务 */
|
||||||
|
serviceUrl?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
menuMap: Record<string, string> = {};
|
||||||
|
|
||||||
|
msgMap: Record<string, string> = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查是否存在锁文件
|
||||||
|
*/
|
||||||
|
private checkLockFile(type: 'menu' | 'msg'): boolean {
|
||||||
|
const lockFile = path.join(this.basePath, type, '.lock');
|
||||||
|
return fs.existsSync(lockFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建锁文件
|
||||||
|
*/
|
||||||
|
private createLockFile(type: 'menu' | 'msg'): void {
|
||||||
|
const lockFile = path.join(this.basePath, type, '.lock');
|
||||||
|
fs.writeFileSync(lockFile, new Date().toISOString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载翻译文件到内存
|
||||||
|
*/
|
||||||
|
async loadTranslations() {
|
||||||
|
if (!this.basePath) {
|
||||||
|
this.basePath = path.join(this.app.getBaseDir(), '..', 'src', 'locales');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载菜单翻译
|
||||||
|
const menuDir = path.join(this.basePath, 'menu');
|
||||||
|
if (fs.existsSync(menuDir)) {
|
||||||
|
const files = fs.readdirSync(menuDir);
|
||||||
|
for (const file of files) {
|
||||||
|
if (file.endsWith('.json')) {
|
||||||
|
const language = file.replace('.json', '');
|
||||||
|
const content = fs.readFileSync(path.join(menuDir, file), 'utf-8');
|
||||||
|
const translations = JSON.parse(content);
|
||||||
|
for (const [key, value] of Object.entries(translations)) {
|
||||||
|
this.menuMap[`${language}:${key}`] = value as string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载消息翻译
|
||||||
|
const msgDir = path.join(this.basePath, 'msg');
|
||||||
|
if (fs.existsSync(msgDir)) {
|
||||||
|
const files = fs.readdirSync(msgDir);
|
||||||
|
for (const file of files) {
|
||||||
|
if (file.endsWith('.json')) {
|
||||||
|
const language = file.replace('.json', '');
|
||||||
|
const content = fs.readFileSync(path.join(msgDir, file), 'utf-8');
|
||||||
|
const translations = JSON.parse(content);
|
||||||
|
for (const [key, value] of Object.entries(translations)) {
|
||||||
|
this.msgMap[`${language}:${key}`] = value as string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新翻译映射
|
||||||
|
* @param type 类型 menu | msg
|
||||||
|
* @param language 语言
|
||||||
|
*/
|
||||||
|
async updateTranslationMap(type: 'menu' | 'msg', language: string) {
|
||||||
|
const dirPath = path.join(this.basePath, type);
|
||||||
|
const file = path.join(dirPath, `${language}.json`);
|
||||||
|
|
||||||
|
if (fs.existsSync(file)) {
|
||||||
|
const content = fs.readFileSync(file, 'utf-8');
|
||||||
|
const translations = JSON.parse(content);
|
||||||
|
const map = type === 'menu' ? this.menuMap : this.msgMap;
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(translations)) {
|
||||||
|
map[`${language}:${key}`] = value as string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 翻译
|
||||||
|
* @param type 类型 menu | msg
|
||||||
|
* @param language 语言
|
||||||
|
* @param text 原文
|
||||||
|
* @returns 翻译后的文本
|
||||||
|
*/
|
||||||
|
translate(type: 'menu' | 'msg', language: string, text: string): string {
|
||||||
|
const map = type === 'menu' ? this.menuMap : this.msgMap;
|
||||||
|
const key = `${language}:${text}`;
|
||||||
|
return map[key] || text;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查翻译
|
||||||
|
*/
|
||||||
|
async check() {
|
||||||
|
if (this.config?.enable) {
|
||||||
|
this.basePath = path.join(this.app.getBaseDir(), '..', 'src', 'locales');
|
||||||
|
|
||||||
|
const menuLockExists = this.checkLockFile('menu');
|
||||||
|
const msgLockExists = this.checkLockFile('msg');
|
||||||
|
|
||||||
|
if (!menuLockExists || !msgLockExists) {
|
||||||
|
const tasks = [];
|
||||||
|
if (!msgLockExists) {
|
||||||
|
tasks.push(this.genBaseMsg());
|
||||||
|
}
|
||||||
|
if (!menuLockExists) {
|
||||||
|
tasks.push(this.genBaseMenu());
|
||||||
|
}
|
||||||
|
await Promise.all(tasks);
|
||||||
|
this.logger.info('All translations completed successfully');
|
||||||
|
// 更新翻译映射
|
||||||
|
await this.loadTranslations();
|
||||||
|
} else {
|
||||||
|
this.logger.info('Translation lock files exist, skipping translation');
|
||||||
|
// 直接加载翻译文件到内存
|
||||||
|
await this.loadTranslations();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成基础菜单
|
||||||
|
*/
|
||||||
|
async genBaseMenu() {
|
||||||
|
const menus = await this.baseSysMenuEntity.find();
|
||||||
|
const file = path.join(this.basePath, 'menu', 'zh-cn.json');
|
||||||
|
const content = {};
|
||||||
|
for (const menu of menus) {
|
||||||
|
content[menu.name] = menu.name;
|
||||||
|
}
|
||||||
|
// 确保目录存在
|
||||||
|
const msgDir = path.dirname(file);
|
||||||
|
if (!fs.existsSync(msgDir)) {
|
||||||
|
fs.mkdirSync(msgDir, { recursive: true });
|
||||||
|
}
|
||||||
|
const text = JSON.stringify(content, null, 2);
|
||||||
|
fs.writeFileSync(file, text);
|
||||||
|
this.logger.info('base menu generate success');
|
||||||
|
const translatePromises = [];
|
||||||
|
for (const language of this.config.languages) {
|
||||||
|
if (language !== 'zh-cn') {
|
||||||
|
translatePromises.push(
|
||||||
|
this.invokeTranslate(
|
||||||
|
text,
|
||||||
|
language,
|
||||||
|
path.join(this.basePath, 'menu'),
|
||||||
|
'menu'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await Promise.all(translatePromises);
|
||||||
|
this.createLockFile('menu');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成基础消息
|
||||||
|
*/
|
||||||
|
async genBaseMsg() {
|
||||||
|
const file = path.join(this.basePath, 'msg', 'zh-cn.json');
|
||||||
|
const scanPath = path.join(this.app.getBaseDir(), '..', 'src', 'modules');
|
||||||
|
const messages = {};
|
||||||
|
|
||||||
|
// 递归扫描目录
|
||||||
|
const scanDir = (dir: string) => {
|
||||||
|
const files = fs.readdirSync(dir);
|
||||||
|
for (const file of files) {
|
||||||
|
const fullPath = path.join(dir, file);
|
||||||
|
const stat = fs.statSync(fullPath);
|
||||||
|
|
||||||
|
if (stat.isDirectory()) {
|
||||||
|
scanDir(fullPath);
|
||||||
|
} else if (file.endsWith('.ts')) {
|
||||||
|
const content = fs.readFileSync(fullPath, 'utf-8');
|
||||||
|
const matches = content.match(
|
||||||
|
/throw new CoolCommException\((['"])(.*?)\1\)/g
|
||||||
|
);
|
||||||
|
if (matches) {
|
||||||
|
matches.forEach(match => {
|
||||||
|
const message = match.match(/(['"])(.*?)\1/)[2];
|
||||||
|
messages[message] = message;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 开始扫描
|
||||||
|
scanDir(scanPath);
|
||||||
|
|
||||||
|
// 确保目录存在
|
||||||
|
const msgDir = path.dirname(file);
|
||||||
|
if (!fs.existsSync(msgDir)) {
|
||||||
|
fs.mkdirSync(msgDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 写入文件
|
||||||
|
const text = JSON.stringify(messages, null, 2);
|
||||||
|
fs.writeFileSync(file, text);
|
||||||
|
this.logger.info('base msg generate success');
|
||||||
|
|
||||||
|
const translatePromises = [];
|
||||||
|
for (const language of this.config.languages) {
|
||||||
|
if (language !== 'zh-cn') {
|
||||||
|
translatePromises.push(
|
||||||
|
this.invokeTranslate(
|
||||||
|
text,
|
||||||
|
language,
|
||||||
|
path.join(this.basePath, 'msg'),
|
||||||
|
'msg'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await Promise.all(translatePromises);
|
||||||
|
this.createLockFile('msg');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调用翻译
|
||||||
|
* @param text 文本
|
||||||
|
* @param language 语言
|
||||||
|
* @param dirPath 目录
|
||||||
|
* @param type 类型
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
async invokeTranslate(
|
||||||
|
text: string,
|
||||||
|
language: string,
|
||||||
|
dirPath: string,
|
||||||
|
type: 'menu' | 'msg' = 'msg'
|
||||||
|
) {
|
||||||
|
this.logger.info(`${type} ${language} translate start`);
|
||||||
|
const response = await axios.post(I18N.DEFAULT_SERVICE_URL, {
|
||||||
|
label: 'i18n-node',
|
||||||
|
params: {
|
||||||
|
text,
|
||||||
|
language,
|
||||||
|
},
|
||||||
|
stream: false,
|
||||||
|
});
|
||||||
|
const file = path.join(dirPath, `${language}.json`);
|
||||||
|
fs.writeFileSync(file, response.data.data.result.data);
|
||||||
|
this.logger.info(`${type} ${language} translate success`);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,56 +0,0 @@
|
|||||||
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 UserAppEvent {
|
|
||||||
@Logger()
|
|
||||||
coreLogger: ILogger;
|
|
||||||
|
|
||||||
@Config('module')
|
|
||||||
config;
|
|
||||||
|
|
||||||
@App()
|
|
||||||
app: IMidwayKoaApplication;
|
|
||||||
|
|
||||||
@Event('onMenuInit')
|
|
||||||
async onMenuInit() {
|
|
||||||
if (this.app.getEnv() != 'local') return;
|
|
||||||
this.checkConfig();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 检查配置
|
|
||||||
*/
|
|
||||||
async checkConfig() {
|
|
||||||
if (this.config.user.jwt.secret == 'cool-app-xxxxxx') {
|
|
||||||
this.coreLogger.warn(
|
|
||||||
'\x1B[36m 检测到模块[user] jwt.secret 配置是默认值,请不要关闭!即将自动修改... \x1B[0m'
|
|
||||||
);
|
|
||||||
setTimeout(() => {
|
|
||||||
const filePath = path.join(
|
|
||||||
this.app.getBaseDir(),
|
|
||||||
'..',
|
|
||||||
'src',
|
|
||||||
'modules',
|
|
||||||
'user',
|
|
||||||
'config.ts'
|
|
||||||
);
|
|
||||||
// 替换文件内容
|
|
||||||
let fileData = fs.readFileSync(filePath, 'utf8');
|
|
||||||
const secret = uuid().replace(/-/g, '');
|
|
||||||
this.config.user.jwt.secret = secret;
|
|
||||||
fs.writeFileSync(filePath, fileData.replace('cool-app-xxxxxx', secret));
|
|
||||||
this.coreLogger.info(
|
|
||||||
'\x1B[36m [cool:module:user] midwayjs cool module user auto modify jwt.secret\x1B[0m'
|
|
||||||
);
|
|
||||||
}, 6000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -3,7 +3,8 @@ import { NextFunction, Context } from '@midwayjs/koa';
|
|||||||
import { IMiddleware, Init, Inject } from '@midwayjs/core';
|
import { IMiddleware, Init, Inject } from '@midwayjs/core';
|
||||||
import * as jwt from 'jsonwebtoken';
|
import * as jwt from 'jsonwebtoken';
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
import { CoolUrlTagData, RESCODE, TagTypes } from '@cool-midway/core';
|
import { CoolCommException, CoolUrlTagData, TagTypes } from '@cool-midway/core';
|
||||||
|
import { Utils } from '../../../comm/utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户
|
* 用户
|
||||||
@ -24,6 +25,9 @@ export class UserMiddleware implements IMiddleware<Context, NextFunction> {
|
|||||||
@Config('koa.globalPrefix')
|
@Config('koa.globalPrefix')
|
||||||
prefix;
|
prefix;
|
||||||
|
|
||||||
|
@Inject()
|
||||||
|
utils: Utils;
|
||||||
|
|
||||||
@Init()
|
@Init()
|
||||||
async init() {
|
async init() {
|
||||||
this.ignoreUrls = this.coolUrlTagData.byKey(TagTypes.IGNORE_TOKEN, 'app');
|
this.ignoreUrls = this.coolUrlTagData.byKey(TagTypes.IGNORE_TOKEN, 'app');
|
||||||
@ -37,18 +41,14 @@ export class UserMiddleware implements IMiddleware<Context, NextFunction> {
|
|||||||
const token = ctx.get('Authorization');
|
const token = ctx.get('Authorization');
|
||||||
try {
|
try {
|
||||||
ctx.user = jwt.verify(token, this.jwtConfig.secret);
|
ctx.user = jwt.verify(token, this.jwtConfig.secret);
|
||||||
|
|
||||||
if (ctx.user.isRefresh) {
|
if (ctx.user.isRefresh) {
|
||||||
ctx.status = 401;
|
throw new CoolCommException('登录失效~');
|
||||||
ctx.body = {
|
|
||||||
code: RESCODE.COMMFAIL,
|
|
||||||
message: '登录失效~',
|
|
||||||
};
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
} catch (error) {}
|
} catch (error) {}
|
||||||
// 使用matchUrl方法来检查URL是否应该被忽略
|
// 使用matchUrl方法来检查URL是否应该被忽略
|
||||||
const isIgnored = this.ignoreUrls.some(pattern =>
|
const isIgnored = this.ignoreUrls.some(pattern =>
|
||||||
this.matchUrl(pattern, url)
|
this.utils.matchUrl(pattern, url)
|
||||||
);
|
);
|
||||||
if (isIgnored) {
|
if (isIgnored) {
|
||||||
await next();
|
await next();
|
||||||
@ -56,41 +56,11 @@ export class UserMiddleware implements IMiddleware<Context, NextFunction> {
|
|||||||
} else {
|
} else {
|
||||||
if (!ctx.user) {
|
if (!ctx.user) {
|
||||||
ctx.status = 401;
|
ctx.status = 401;
|
||||||
ctx.body = {
|
throw new CoolCommException('登录失效~');
|
||||||
code: RESCODE.COMMFAIL,
|
|
||||||
message: '登录失效~',
|
|
||||||
};
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await next();
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -230,6 +230,7 @@ export class UserLoginService extends BaseService {
|
|||||||
nickName: wxUserInfo.nickName,
|
nickName: wxUserInfo.nickName,
|
||||||
avatarUrl,
|
avatarUrl,
|
||||||
gender: wxUserInfo.gender,
|
gender: wxUserInfo.gender,
|
||||||
|
tenantId: userInfo['tenantId'],
|
||||||
};
|
};
|
||||||
await this.userInfoEntity.insert(userInfo);
|
await this.userInfoEntity.insert(userInfo);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user