Files
shrimp-server/src/forMongo/MongoShrimpCrud.ts
T
wc24 ca283c6733 v0.5.0: 重新组织目录结构,按职责拆分
- Shrimp.ts (875 行) 按类拆分到 Shrimp/(7 文件: Shrimp, ShrimpModule, ShrimpTableModule, ShrimpPower, ShrimpCrud, UserRoleModule, OperationLogModule)
- forMongo/: MongoShrimpCrud + MongoShrimp(原 ShrimpServer 的 mongo 装配部分,无 express)
- forExpress/: buildShrimpRouter + jwtAuth + types
- main/: index(0 依赖核心) + expmon(express+mongo 整合,导出 ShrimpServer) + types(纯协议)
- 入口子路径 server → expmon(express+mongo 整合方案命名更准确)
- 删除冗余的 index.ts / core.ts(合并为 main/index.ts)
2026-05-23 19:44:02 +08:00

124 lines
4.7 KiB
TypeScript

import { Model, FilterQuery } from 'mongoose'
import type { ShrimpCrud } from "../Shrimp/ShrimpCrud"
import { IShrimpQuery, IShrimpCondition, IShrimpPopulate, IPageResult, ShrimpConfig, isShrimpCondition } from "../IShrimpType"
export class MongoShrimpCrud<T> implements ShrimpCrud<T> {
constructor(private model: Model<any>) { }
/** 获取关联模块的 Mongoose Model(用于 populate / $lookup) */
getRelatedModel?: (moduleKey: string) => Model<any> | undefined
private isCondition = isShrimpCondition
private translateCondition(cond: IShrimpCondition | any): any {
if (!this.isCondition(cond)) return cond
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
}
private buildMongooseQuery(query?: IShrimpQuery): { filter: FilterQuery<T>, sort: any, skip: number, limit: number } {
const filter: FilterQuery<T> = {}
if (query?.where) {
for (const [key, cond] of Object.entries(query.where)) {
(filter as any)[key] = this.translateCondition(cond)
}
}
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<IPageResult<T>> {
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<string, any>): Promise<void> {
for (const pop of populates) {
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<string>()
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<T | null> {
return await this.model.findOne(query || {}).lean() as T | null
}
async get(id: string): Promise<T | null> {
return await this.model.findById(id).lean() as T | null
}
async add(item: Omit<T, '_id'>): Promise<T> {
return await this.model.create(item)
}
async edit(id: string, item: Partial<T>): Promise<T | null> {
return await this.model.findByIdAndUpdate(id, item, { new: true })
}
async delete(id: string): Promise<boolean> {
const result = await this.model.findByIdAndDelete(id)
return !!result
}
async clear(): Promise<boolean> {
await this.model.deleteMany({})
return true
}
}