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)
This commit is contained in:
2026-05-23 19:44:02 +08:00
parent e8fc126fa6
commit ca283c6733
27 changed files with 1059 additions and 1128 deletions
+172
View File
@@ -0,0 +1,172 @@
import {
IFieldConfig, IUser, IRole, WithId, IRoleProto, IPageQuery, IPageResult,
IShrimpOperator, IModuleInfo, ILabel, ShrimpError,
} from "../IShrimpType"
import { Shrimp } from "./Shrimp"
import { ShrimpModule } from "./ShrimpModule"
import { ShrimpTableModule } from "./ShrimpTableModule"
import type { ShrimpCrud } from "./ShrimpCrud"
/** 用户模块扩展 - 添加登录和获取用户信息功能 */
const userFields: IFieldConfig[] = [
{ key: "username", label: { t: "user.username" }, type: "string", required: true, searchable: true, editable: "addOnly" },
{ key: "password", label: { t: "user.password" }, type: "password", tableHide: true },
{ key: "nickname", label: { t: "user.nickname" }, type: "string", searchable: true },
{ key: "roleId", label: { t: "user.roleId" }, type: "relation", relation: { moduleKey: "role", labelKey: "name" }, filterable: true, multiple: true, defaultValue: [] },
{ key: "active", label: { t: "user.active" }, type: "boolean", filterable: true, defaultValue: true },
{ key: "createdAt", label: { t: "user.createdAt" }, type: "date", editable: "readonly", sortable: true, filterable: true },
{ key: "updatedAt", label: { t: "user.updatedAt" }, type: "date", editable: "readonly", sortable: true },
]
export class UserModule extends ShrimpTableModule<WithId<IUser>> {
constructor(public parent: Shrimp | ShrimpModule, dataSource: ShrimpCrud<WithId<IUser>>, fields?: IFieldConfig[]) {
super(parent, "user", dataSource, fields || userFields, { t: "user.menuManage" })
this.initCrud()
}
protected async verifyPassword(storedPassword: string, plainPassword: string): Promise<boolean> {
return Shrimp.passwordHasher.verify(plainPassword, storedPassword)
}
protected getUserRoleIds(user: WithId<IUser>): string[] {
return Array.isArray(user.roleId) ? user.roleId.map(String) : user.roleId ? [String(user.roleId)] : []
}
async getUser(username: string, password: string): Promise<WithId<IUser>> {
const user = await this.dataSource.findOne({ username, active: true })
if (!user || !(await this.verifyPassword(user.password, password))) {
throw new ShrimpError({ t: 'shrimp.loginFailed' })
}
return user
}
async getModule(userId: string, roleModule: RoleModule): Promise<IModuleInfo[]> {
const user = await this.dataSource.get(userId)
if (!user) {
throw new ShrimpError({ t: 'shrimp.userNotFound' })
}
const roleIds = this.getUserRoleIds(user)
const roles = await Promise.all(roleIds.map(id => roleModule.dataSource.get(id)))
const isSuperAdmin = roles.some(r => r?.isSuperAdmin)
const powerKeys = isSuperAdmin
? (await this.root.allPower()).map(p => p.key)
: roles.flatMap(r => r?.powers || [])
return this.root.getModuleInfo(new Set(powerKeys))
}
async edit(id: string, item: Partial<IUser>): Promise<WithId<IUser> | null> {
const op = this.root.operator
if (op && String(op.userId) === String(id)) {
if (item.active === false) {
throw new ShrimpError({ t: 'shrimp.cannotDisableSelf' })
}
if (item.roleId != null) {
const newRoleIds = Array.isArray(item.roleId) ? item.roleId.map(String) : [String(item.roleId)]
const oldUser = await this.dataSource.get(id)
const oldRoleIds = oldUser ? this.getUserRoleIds(oldUser) : []
if (JSON.stringify([...newRoleIds].sort()) !== JSON.stringify([...oldRoleIds].sort())) {
throw new ShrimpError({ t: 'shrimp.cannotChangeOwnRole' })
}
}
}
return super.edit(id, item)
}
async delete(id: string): Promise<boolean> {
const op = this.root.operator
if (op && String(op.userId) === String(id)) {
throw new ShrimpError({ t: 'shrimp.cannotDeleteSelf' })
}
return super.delete(id)
}
async clear(): Promise<boolean> {
if (this.root.operator) {
throw new ShrimpError({ t: 'shrimp.cannotClearAllUsers' })
}
return super.clear()
}
}
/** 角色模块 */
const roleFields: IFieldConfig[] = [
{ key: "name", label: { t: "role.name" }, type: "string", required: true, searchable: true },
{ key: "description", label: { t: "role.description" }, type: "textarea" },
{ key: "isSuperAdmin", label: { t: "role.isSuperAdmin" }, type: "boolean" },
{ key: "powers", label: { t: "role.powers" }, type: "string" },
{ key: "createdAt", label: { t: "role.createdAt" }, type: "date", editable: "readonly", sortable: true },
{ key: "updatedAt", label: { t: "role.updatedAt" }, type: "date", editable: "readonly", sortable: true },
]
export class RoleModule extends ShrimpTableModule<WithId<IRole>, IRoleProto> {
constructor(public parent: Shrimp | ShrimpModule, dataSource: ShrimpCrud<WithId<IRole>>, fields?: IFieldConfig[]) {
super(parent, "role", dataSource, fields || roleFields, { t: "role.menuManage" })
this.initCrud()
this.addEvent("clear", { t: "power.clear" })
this.addEvent("export", { t: "power.export" })
this.addEvent("import", { t: "power.import" })
this.addEvent("allPower", this.power)
}
async allPower(): Promise<{ key: string; label: ILabel }[]> {
return this.root.allPower()
}
protected getOperatorRoleIds(op: IShrimpOperator): string[] {
return Array.isArray(op.roleId) ? op.roleId.map(String) : op.roleId ? [String(op.roleId)] : []
}
async edit(id: string, item: Partial<IRole>): Promise<WithId<IRole> | null> {
const op = this.root.operator
if (op) {
const opRoleIds = this.getOperatorRoleIds(op)
if (opRoleIds.includes(String(id))) {
throw new ShrimpError({ t: 'shrimp.cannotEditOwnRole' })
}
}
return super.edit(id, item)
}
private async fillSuperAdminPowers(role: WithId<IRole> | null): Promise<WithId<IRole> | null> {
if (role && role.isSuperAdmin) {
const allPowers = await this.allPower()
role.powers = allPowers.map(p => p.key)
}
return role
}
async list(query?: IPageQuery & any): Promise<IPageResult<WithId<IRole>>> {
const result = await super.list(query)
for (const item of result.list) {
if (item.isSuperAdmin) {
const allPowers = await this.allPower()
item.powers = allPowers.map(p => p.key)
}
}
return result
}
async get(id: string): Promise<WithId<IRole> | null> {
const result = await super.get(id)
return this.fillSuperAdminPowers(result)
}
async delete(id: string): Promise<boolean> {
const op = this.root.operator
if (op) {
const opRoleIds = this.getOperatorRoleIds(op)
if (opRoleIds.includes(String(id))) {
throw new ShrimpError({ t: 'shrimp.cannotDeleteOwnRole' })
}
}
return super.delete(id)
}
async clear(): Promise<boolean> {
if (this.root.operator) {
throw new ShrimpError({ t: 'shrimp.cannotClearAllRoles' })
}
return super.clear()
}
}