feat(plugin-field-sequence): auto update sequence collection`s current base on business collections (#8063)

* feat(plugin-field-sequence): auto update sequence collection`s current base on business collections

* refactor(plugin-field-sequence): invoke collection load method instead of trigger beforeStart event

* chore(plugin-field-sequence): change command refresh-data`s name to repair

* refactor(plugin-field-sequence): add overwrite options in  SequenceField`s update for repair event
This commit is contained in:
Drol
2025-12-05 00:06:22 +08:00
committed by GitHub
parent 17342ed65d
commit 908fd246a7
5 changed files with 1229 additions and 2 deletions
@@ -23,6 +23,7 @@ import start from './start';
import stop from './stop';
import upgrade from './upgrade';
import consoleCommand from './console';
import repair from './repair';
export function registerCli(app: Application) {
consoleCommand(app);
@@ -39,6 +40,7 @@ export function registerCli(app: Application) {
destroy(app);
start(app);
refresh(app);
repair(app);
// development only with @nocobase/cli
app.command('build').argument('[packages...]');
@@ -0,0 +1,28 @@
/**
* This file is part of the NocoBase (R) project.
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
* Authors: NocoBase Team.
*
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import Application from '../application';
export default (app: Application) => {
app
.command('repair')
.auth()
.preload()
.action(async (options) => {
app.log.info('start repair data...');
const Collection = app.db.getCollection('collections');
if (Collection) {
// @ts-ignore
await Collection.repository.setApp(app);
// @ts-ignore
await Collection.repository.load();
}
await app.emitAsync('repair', options);
});
};
@@ -14,6 +14,8 @@ import { promisify } from 'util';
import { Plugin } from '@nocobase/server';
import { Registry } from '@nocobase/utils';
import { Pattern, SequenceField } from './fields/sequence-field';
import _ from 'lodash';
import { Field, Model } from '@nocobase/database';
const asyncRandomInt = promisify(randomInt);
@@ -108,6 +110,55 @@ export default class PluginFieldSequenceServer extends Plugin {
transaction,
});
});
app.on('repair', async () => {
app.log.info(`app ${app.name} plugin ${this.name} start repair data...`);
const sequencesModel = app.db.getModel('sequences');
const allSequences = await sequencesModel.findAll();
const groupedSequences = _.groupBy(allSequences, 'collection');
const tasks: (() => Promise<void>)[] = [];
for (const [collectionName, sequencesList] of Object.entries(groupedSequences)) {
tasks.push(async () => {
const collection = app.db.getCollection(collectionName);
const fields: Field[] = collection.getFields();
const fieldMap = Object.fromEntries<Field>(fields.map((field) => [field.name, field]));
const [autoIncrementField] = fields.filter((field) => field.options.primaryKey && field.type === 'bigInt');
const [createAtField] = fields.filter((field) => field.options.interface === 'createdAt');
if (!autoIncrementField && !createAtField) {
app.log.warn(
`Collection [${collection}] does not have autoIncrement or createdAt fields. Skipping sequences refresh`,
);
return;
}
const [record] = await collection.model.findAll({
order: [[autoIncrementField?.name ?? createAtField?.name, 'DESC']],
limit: 1,
});
if (!record) {
app.log.warn(`Collection [${collection}] has no records. Skipping sequences repair`);
return;
}
const sequencesFieldSet = _.uniq<string>(sequencesList.map(({ field }) => field));
for (const sequencesField of sequencesFieldSet) {
const field = fieldMap[sequencesField] as SequenceField;
if (!field) {
app.log.warn(
`Collection [${collection}] field [${sequencesField}] definition not found. Skipping sequences repair`,
);
continue;
}
await field.update(record, { overwrite: true });
}
});
}
await Promise.all(tasks.map((t) => t()));
app.log.info(`app ${app.name} plugin ${this.name} finish repair data`);
});
}
async install() {}
@@ -44,7 +44,7 @@ export interface Pattern {
instance: Model,
value: string,
options,
transactionable: Transactionable,
transactionable: Transactionable & { overwrite?: boolean },
): Promise<void>;
}
@@ -235,7 +235,7 @@ sequencePatterns.register('integer', {
await lastSeq.save({ transaction });
},
async update(instance, value, options, { transaction }) {
async update(instance, value, options, { transaction, overwrite }) {
const recordTime = <Date>instance.get('createdAt') ?? new Date();
const { digits = 1, start = 0, base = 10, cycle, key } = options;
const SeqRepo = this.database.getRepository('sequences');
@@ -269,6 +269,15 @@ sequencePatterns.register('integer', {
{ transaction },
);
}
if (overwrite === true) {
return lastSeq.update(
{
current,
lastGeneratedAt: recordTime,
},
{ transaction },
);
}
if (cycle) {
const interval = parser.parseExpression(cycle, { currentDate: <Date>lastSeq.get('lastGeneratedAt') });