import { Types, Model, FilterQuery } from 'mongoose'; import { ShrimpCrud } from './Shrimp'; import { IShrimpQuery, IShrimpCondition, IShrimpPopulate, IPageResult, ShrimpConfig, isShrimpCondition } from './IShrimpType'; export class MongoShrimpCrud implements ShrimpCrud { constructor(private model: Model) { } /** 获取关联模块的 Mongoose Model(用于 populate / $lookup) */ getRelatedModel?: (moduleKey: string) => Model | undefined /** 判断值是否为 IShrimpCondition 对象 */ private isCondition = isShrimpCondition /** 将 IShrimpCondition 翻译为 MongoDB 查询片段 */ private translateCondition(cond: IShrimpCondition | any): any { if (!this.isCondition(cond)) return cond // 简写: 原始值 = eq const q: any = {} if (cond.eq !== undefined) q.$eq = cond.eq if (cond.in !== undefined) q.$in = cond.in if (cond.like !== undefined) q.$regex = cond.like, q.$options = 'i' if (cond.gte !== undefined) q.$gte = cond.gte if (cond.lte !== undefined) q.$lte = cond.lte return q } /** 将 IShrimpQuery 翻译为 Mongoose 查询 */ private buildMongooseQuery(query?: IShrimpQuery): { filter: FilterQuery, sort: any, skip: number, limit: number } { const filter: FilterQuery = {} // where → AND 条件 if (query?.where) { for (const [key, cond] of Object.entries(query.where)) { (filter as any)[key] = this.translateCondition(cond) } } // orWhere → $or if (query?.orWhere && query.orWhere.length > 0) { filter.$or = query.orWhere.map(group => { const obj: any = {} for (const [key, cond] of Object.entries(group)) { obj[key] = this.translateCondition(cond) } return obj }) } // 排序 const sort: any = {} if (query?.order) { for (const [key, dir] of query.order) { sort[key] = dir === 'desc' ? -1 : 1 } } return { filter, sort, skip: query?.offset || 0, limit: query?.limit ?? ShrimpConfig.defaultPageSize, } } async list(query?: IShrimpQuery): Promise> { const { filter, sort, skip, limit } = this.buildMongooseQuery(query) const [docs, total] = await Promise.all([ this.model.find(filter).sort(sort).skip(skip).limit(limit).lean(), this.model.countDocuments(filter), ]) const page = Math.floor(skip / limit) + 1 const totalPages = Math.ceil(total / limit) return { list: docs, total, page, pageSize: limit, totalPages } } async populateItems(items: any[], populates: IShrimpPopulate[], crudRegistry?: Map): Promise { for (const pop of populates) { // 优先用 getRelatedModel,其次用 crudRegistry(自动解析 relation) let relatedModel = this.getRelatedModel?.(pop.moduleKey) if (!relatedModel && crudRegistry) { const relatedCrud = crudRegistry.get(pop.moduleKey) as any relatedModel = relatedCrud?.model } if (!relatedModel) continue const allRefIds = new Set() for (const item of items) { const val = (item as any)[pop.key] if (Array.isArray(val)) { val.forEach((id: any) => { if (id != null) allRefIds.add(String(id)) }) } else if (val != null) { allRefIds.add(String(val)) } } if (allRefIds.size === 0) continue const docs = await relatedModel.find({ _id: { $in: Array.from(allRefIds) } }).select(`${pop.labelKey} _id`) const labelMap = new Map(docs.map(doc => [String(doc._id), (doc as any)[pop.labelKey]])) for (const item of items) { const val = (item as any)[pop.key] if (Array.isArray(val)) { (item as any)[`${pop.key}_label`] = val.map((id: any) => labelMap.get(String(id)) ?? null) } else if (val != null) { (item as any)[`${pop.key}_label`] = labelMap.get(String(val)) ?? null } } } } async findOne(query?: any): Promise { return await this.model.findOne(query || {}).lean() as T | null } async get(id: string): Promise { return await this.model.findById(id).lean() as T | null; } async add(item: Omit): Promise { return await this.model.create(item); } async edit(id: string, item: Partial): Promise { return await this.model.findByIdAndUpdate(id, item, { new: true }); } async delete(id: string): Promise { const result = await this.model.findByIdAndDelete(id); return !!result; } async clear(): Promise { await this.model.deleteMany({}); return true; } }