From 0bb38452da6de7e31b6595b514163dd2c17d419f Mon Sep 17 00:00:00 2001 From: Junyi Date: Fri, 5 Sep 2025 23:55:22 +0800 Subject: [PATCH 1/2] refactor(server): use system logger for queue logs (#7480) * refactor(server): use system logger for queue logs * fix(plugin-workflow): fix test case --- packages/core/server/src/event-queue.ts | 50 +++++++++++-------- .../src/server/__tests__/cluster.test.ts | 2 +- 2 files changed, 30 insertions(+), 22 deletions(-) diff --git a/packages/core/server/src/event-queue.ts b/packages/core/server/src/event-queue.ts index e15b76a85cd..107d850fa5d 100644 --- a/packages/core/server/src/event-queue.ts +++ b/packages/core/server/src/event-queue.ts @@ -13,6 +13,7 @@ import path from 'path'; import fs from 'fs/promises'; import Application from './application'; +import { SystemLogger } from '@nocobase/logger'; import { sleep } from '@nocobase/utils'; export const QUEUE_DEFAULT_INTERVAL = 250; @@ -86,9 +87,10 @@ export class MemoryEventQueueAdapter implements IEventQueueAdapter { if (!this.connected) { return; } + const { logger } = this.options; const event = this.events.get(channel); if (!event) { - console.warn(`memory queue (${channel}) not found, skipping...`); + logger.warn(`memory queue (${channel}) not found, skipping...`); return; } if (!event.idle()) { @@ -98,12 +100,12 @@ export class MemoryEventQueueAdapter implements IEventQueueAdapter { const reading = this.reading.get(channel) || []; const count = (event.concurrency || QUEUE_DEFAULT_CONCURRENCY) - reading.length; if (count <= 0) { - console.debug( - `memory queue (${channel}) is already reading as max concurrency (${reading.length}), waiting last reading to end...`, - ); + // logger.debug( + // `memory queue (${channel}) is already reading as max concurrency (${reading.length}), waiting last reading to end...`, + // ); return; } - console.debug(`reading more from queue (${channel}), count: ${count}`); + logger.debug(`reading more from queue (${channel}), count: ${count}`); this.read(channel, count).forEach((promise) => { reading.push(promise); // eslint-disable-next-line promise/catch-or-return @@ -117,7 +119,7 @@ export class MemoryEventQueueAdapter implements IEventQueueAdapter { this.reading.set(channel, reading); }; - constructor(private options: { appName: string }) { + constructor(private options: { appName: string; logger: SystemLogger }) { this.emitter.setMaxListeners(0); } @@ -132,20 +134,21 @@ export class MemoryEventQueueAdapter implements IEventQueueAdapter { async loadFromStorage() { let queues = {}; let exists = false; + const { logger } = this.options; try { await fs.stat(this.storagePath); exists = true; } catch (ex) { - console.info(`memory queue storage file not found, skip`); + logger.info(`memory queue storage file not found, skip`); } if (exists) { try { const queueJson = await fs.readFile(this.storagePath); queues = JSON.parse(queueJson.toString()); - console.debug('memory queue loaded from storage', queues); + logger.debug('memory queue loaded from storage', queues); await fs.unlink(this.storagePath); } catch (ex) { - console.error('failed to load queue from storage', ex); + logger.error('failed to load queue from storage', ex); } } this.queues = new Map(Object.entries(queues)); @@ -159,12 +162,13 @@ export class MemoryEventQueueAdapter implements IEventQueueAdapter { return acc; }, {}); + const { logger } = this.options; if (Object.keys(queues).length) { await fs.mkdir(path.dirname(this.storagePath), { recursive: true }); await fs.writeFile(this.storagePath, JSON.stringify(queues)); - console.debug('memory queue saved to storage', queues); + logger.debug('memory queue saved to storage', queues); } else { - console.debug('memory queue empty, no need to save to storage'); + logger.debug('memory queue empty, no need to save to storage'); } } @@ -195,14 +199,15 @@ export class MemoryEventQueueAdapter implements IEventQueueAdapter { if (!this.connected) { return; } + const { logger } = this.options; this.connected = false; if (this.processing) { - console.info('memory queue waiting for processing job...'); + logger.info('memory queue waiting for processing job...'); await this.processing; - console.info('memory queue job cleaned'); + logger.info('memory queue job cleaned'); } - console.log('memory queue gracefully shutting down...'); + logger.info('memory queue gracefully shutting down...'); await this.saveToStorage(); } @@ -241,7 +246,8 @@ export class MemoryEventQueueAdapter implements IEventQueueAdapter { const queue = this.queues.get(channel); const message = { id: randomUUID(), content, options }; queue.push(message); - console.debug(`memory queue (${channel}) published message`, content); + const { logger } = this.options; + logger.debug(`memory queue (${channel}) published message`, content); setImmediate(() => { this.emitter.emit(channel, channel); @@ -271,8 +277,9 @@ export class MemoryEventQueueAdapter implements IEventQueueAdapter { if (!queue?.length) { return []; } + const { logger } = this.options; const messages = queue.slice(0, n); - console.debug(`memory queue (${channel}) read ${messages.length} messages`, messages); + logger.debug(`memory queue (${channel}) read ${messages.length} messages`, messages); queue.splice(0, messages.length); const batch = messages.map(({ id, ...message }) => this.process(channel, { id, message })); return batch; @@ -281,7 +288,8 @@ export class MemoryEventQueueAdapter implements IEventQueueAdapter { async process(channel: string, { id, message }) { const event = this.events.get(channel); const { content, options: { timeout = QUEUE_DEFAULT_ACK_TIMEOUT, maxRetries = 0, retried = 0 } = {} } = message; - console.debug(`memory queue (${channel}) processing message (${id})...`, content); + const { logger } = this.options; + logger.debug(`memory queue (${channel}) processing message (${id})...`, content); return (async () => event.process(content, { id, @@ -289,12 +297,12 @@ export class MemoryEventQueueAdapter implements IEventQueueAdapter { signal: AbortSignal.timeout(timeout), }))() .then(() => { - console.debug(`memory queue (${channel}) consumed message (${id})`); + logger.debug(`memory queue (${channel}) consumed message (${id})`); }) .catch((ex) => { if (maxRetries > 0 && retried < maxRetries) { const currentRetry = retried + 1; - console.warn( + logger.warn( `memory queue (${channel}) consum message (${id}) failed, retrying (${currentRetry} / ${maxRetries})...`, ex, ); @@ -302,7 +310,7 @@ export class MemoryEventQueueAdapter implements IEventQueueAdapter { this.publish(channel, content, { timeout, maxRetries, retried: currentRetry, timestamp: Date.now() }); }, 500); } else { - console.error(ex); + logger.error(ex); } }); } @@ -320,7 +328,7 @@ export class EventQueue { protected app: Application, protected options: EventQueueOptions = {}, ) { - this.setAdapter(new MemoryEventQueueAdapter({ appName: this.app.name })); + this.setAdapter(new MemoryEventQueueAdapter({ appName: this.app.name, logger: this.app.logger })); app.on('afterStart', async () => { await this.connect(); diff --git a/packages/plugins/@nocobase/plugin-workflow/src/server/__tests__/cluster.test.ts b/packages/plugins/@nocobase/plugin-workflow/src/server/__tests__/cluster.test.ts index 165d99a3f17..208c0eb6345 100644 --- a/packages/plugins/@nocobase/plugin-workflow/src/server/__tests__/cluster.test.ts +++ b/packages/plugins/@nocobase/plugin-workflow/src/server/__tests__/cluster.test.ts @@ -104,7 +104,7 @@ describe('workflow > cluster', () => { sharedQueues = new Map(); for (const node of cluster.nodes) { await node.eventQueue.close(); - const adapter = new MockMemoryEventQueueAdapter({ appName: node.name }); + const adapter = new MockMemoryEventQueueAdapter({ appName: node.name, logger: node.logger }); adapter.setQueues(sharedQueues); node.eventQueue.setAdapter(adapter); await node.eventQueue.connect(); From 4d2581eab0c0b607b65dc583b340c9e8e5021195 Mon Sep 17 00:00:00 2001 From: Katherine Date: Tue, 9 Sep 2025 10:11:07 +0800 Subject: [PATCH 2/2] fix: readonly select/multiselect/date/richtext still editable on public forms (#7484) --- .../antd/rich-text/RichText.tsx | 20 +++++++++++++++++-- .../components/MobileDatePicker.tsx | 4 +++- .../components/components/MobilePicker.tsx | 5 +++-- 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/packages/core/client/src/schema-component/antd/rich-text/RichText.tsx b/packages/core/client/src/schema-component/antd/rich-text/RichText.tsx index fb0aa7bbec9..49de46dc2ee 100644 --- a/packages/core/client/src/schema-component/antd/rich-text/RichText.tsx +++ b/packages/core/client/src/schema-component/antd/rich-text/RichText.tsx @@ -9,9 +9,11 @@ import { connect, mapProps, mapReadPretty } from '@formily/react'; import React from 'react'; +import { css } from '@emotion/css'; +import classNames from 'classnames'; import { lazy } from '../../../lazy-helper'; import { isVariable } from '../../../variables/utils/isVariable'; -import { Input, ReadPretty as InputReadPretty } from '../input'; +import { ReadPretty as InputReadPretty } from '../input'; import { useStyles } from './style'; const ReactQuill = lazy(() => import('react-quill')); @@ -37,9 +39,23 @@ export const RichText = connect( ]; const { value, defaultValue, onChange, disabled, modules: propsModules, formats: propsFormats } = props; const resultValue = isVariable(value || defaultValue) ? undefined : value || ''; + const quillDisabled = css` + .ql-container.ql-disabled { + background-color: #f5f5f5; /* 灰色背景 */ + color: #999; + opacity: 0.7; + cursor: not-allowed; + pointer-events: none; + border: 1px solid #d9d9d9; /* 模拟 input 的禁用边框 */ + border-radius: 6px; + } + `; + return wrapSSR( -
setVisible(true)}> +
!disabled && setVisible(true)}> setVisible(true)} value={value} picker={picker} + disabled={disabled} {...rest} popupStyle={{ display: 'none' }} style={{ pointerEvents: 'none', width: '100%' }} diff --git a/packages/plugins/@nocobase/plugin-public-forms/src/client/components/components/MobilePicker.tsx b/packages/plugins/@nocobase/plugin-public-forms/src/client/components/components/MobilePicker.tsx index b0180e40d8c..952a5da875d 100644 --- a/packages/plugins/@nocobase/plugin-public-forms/src/client/components/components/MobilePicker.tsx +++ b/packages/plugins/@nocobase/plugin-public-forms/src/client/components/components/MobilePicker.tsx @@ -15,7 +15,7 @@ import { useTranslation } from 'react-i18next'; const MobilePicker = connect( (props) => { - const { value, onChange, options = [], mode } = props; + const { value, onChange, options = [], mode, disabled } = props; const { t } = useTranslation(); const [visible, setVisible] = useState(false); const [selected, setSelected] = useState(value || []); @@ -38,12 +38,13 @@ const MobilePicker = connect( return ( <> -
setVisible(true)}> +
!disabled && setVisible(true)}>