Jelajahi Sumber

🚀 feat(商户模块): 完成商户模块多租户复制

✅ 复制商户模块为多租户版本 @d8d/merchant-module-mt
✅ 创建多租户商户实体 MerchantMt 包含租户ID字段
✅ 更新多租户商户服务 MerchantServiceMt 支持租户过滤
✅ 修复实体关系注册和测试数据字段长度问题

🤖 Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
yourname 1 bulan lalu
induk
melakukan
c85b68afea

+ 80 - 0
packages/merchant-module-mt/package.json

@@ -0,0 +1,80 @@
+{
+  "name": "@d8d/merchant-module-mt",
+  "version": "1.0.0",
+  "description": "多租户商户管理模块 - 提供商户的完整CRUD功能,支持租户数据隔离,包括商户登录统计、状态管理等",
+  "type": "module",
+  "main": "src/index.ts",
+  "types": "src/index.ts",
+  "exports": {
+    ".": {
+      "types": "./src/index.ts",
+      "import": "./src/index.ts",
+      "require": "./src/index.ts"
+    },
+    "./services": {
+      "types": "./src/services/index.ts",
+      "import": "./src/services/index.ts",
+      "require": "./src/services/index.ts"
+    },
+    "./schemas": {
+      "types": "./src/schemas/index.ts",
+      "import": "./src/schemas/index.ts",
+      "require": "./src/schemas/index.ts"
+    },
+    "./routes": {
+      "types": "./src/routes/index.ts",
+      "import": "./src/routes/index.ts",
+      "require": "./src/routes/index.ts"
+    },
+    "./entities": {
+      "types": "./src/entities/index.ts",
+      "import": "./src/entities/index.ts",
+      "require": "./src/entities/index.ts"
+    }
+  },
+  "files": [
+    "src"
+  ],
+  "scripts": {
+    "build": "tsc",
+    "dev": "tsc --watch",
+    "test": "vitest run",
+    "test:watch": "vitest",
+    "test:coverage": "vitest run --coverage",
+    "test:integration": "vitest run tests/integration",
+    "lint": "eslint src --ext .ts,.tsx",
+    "typecheck": "tsc --noEmit"
+  },
+  "dependencies": {
+    "@d8d/shared-types": "workspace:*",
+    "@d8d/shared-utils": "workspace:*",
+    "@d8d/shared-crud": "workspace:*",
+    "@d8d/auth-module-mt": "workspace:*",
+    "@d8d/user-module-mt": "workspace:*",
+    "@d8d/file-module-mt": "workspace:*",
+    "@hono/zod-openapi": "^1.0.2",
+    "typeorm": "^0.3.20",
+    "zod": "^4.1.12"
+  },
+  "devDependencies": {
+    "@types/node": "^22.10.2",
+    "typescript": "^5.8.3",
+    "vitest": "^3.2.4",
+    "@d8d/shared-test-util": "workspace:*",
+    "@typescript-eslint/eslint-plugin": "^8.18.1",
+    "@typescript-eslint/parser": "^8.18.1",
+    "eslint": "^9.17.0"
+  },
+  "peerDependencies": {
+    "hono": "^4.8.5"
+  },
+  "keywords": [
+    "merchant",
+    "business",
+    "crud",
+    "api",
+    "management"
+  ],
+  "author": "D8D Team",
+  "license": "MIT"
+}

+ 1 - 0
packages/merchant-module-mt/src/entities/index.ts

@@ -0,0 +1 @@
+export { MerchantMt } from './merchant.mt.entity';

+ 61 - 0
packages/merchant-module-mt/src/entities/merchant.mt.entity.ts

@@ -0,0 +1,61 @@
+import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm';
+
+@Entity('merchants_mt')
+export class MerchantMt {
+  @PrimaryGeneratedColumn({ unsigned: true })
+  id!: number;
+
+  @Column({ name: 'tenant_id', type: 'int', unsigned: true, nullable: false, comment: '租户ID' })
+  tenantId!: number;
+
+  @Column({ name: 'name', type: 'varchar', length: 255, nullable: true, comment: '商户名称' })
+  name!: string | null;
+
+  @Column({ name: 'username', type: 'varchar', length: 20, unique: true, comment: '用户名' })
+  username!: string;
+
+  @Column({ name: 'password', type: 'varchar', length: 255, comment: '密码' })
+  password!: string;
+
+  @Column({ name: 'phone', type: 'char', length: 11, nullable: true, comment: '手机号码' })
+  phone!: string | null;
+
+  @Column({ name: 'realname', type: 'varchar', length: 20, nullable: true, comment: '姓名' })
+  realname!: string | null;
+
+  @Column({ name: 'login_num', type: 'int', unsigned: true, default: 0, comment: '登录次数' })
+  loginNum!: number;
+
+  @Column({ name: 'login_time', type: 'int', unsigned: true, default: 0, comment: '登录时间' })
+  loginTime!: number;
+
+  @Column({ name: 'login_ip', type: 'varchar', length: 15, nullable: true, comment: '登录IP' })
+  loginIp!: string | null;
+
+  @Column({ name: 'last_login_time', type: 'int', unsigned: true, default: 0, comment: '上次登录时间' })
+  lastLoginTime!: number;
+
+  @Column({ name: 'last_login_ip', type: 'varchar', length: 15, nullable: true, comment: '上次登录IP' })
+  lastLoginIp!: string | null;
+
+  @Column({ name: 'state', type: 'smallint', unsigned: true, default: 2, comment: '状态 1启用 2禁用' })
+  state!: number;
+
+  @Column({ name: 'rsa_public_key', type: 'varchar', length: 2000, nullable: true, comment: '公钥' })
+  rsaPublicKey!: string | null;
+
+  @Column({ name: 'aes_key', type: 'varchar', length: 32, nullable: true, comment: 'aes秘钥' })
+  aesKey!: string | null;
+
+  @CreateDateColumn({ name: 'created_at', type: 'timestamp', comment: '创建时间' })
+  createdAt!: Date;
+
+  @UpdateDateColumn({ name: 'updated_at', type: 'timestamp', comment: '更新时间' })
+  updatedAt!: Date;
+
+  @Column({ name: 'created_by', type: 'int', unsigned: true, nullable: true, comment: '创建用户ID' })
+  createdBy!: number | null;
+
+  @Column({ name: 'updated_by', type: 'int', unsigned: true, nullable: true, comment: '更新用户ID' })
+  updatedBy!: number | null;
+}

+ 5 - 0
packages/merchant-module-mt/src/index.ts

@@ -0,0 +1,5 @@
+export * from './entities';
+export * from './services';
+export * from './schemas';
+export * from './routes';
+export * from './types';

+ 26 - 0
packages/merchant-module-mt/src/routes/admin-routes.mt.ts

@@ -0,0 +1,26 @@
+import { createCrudRoutes } from '@d8d/shared-crud';
+import { MerchantMt } from '../entities/merchant.mt.entity';
+import { AdminMerchantSchemaMt, AdminCreateMerchantDtoMt, AdminUpdateMerchantDtoMt } from '../schemas/admin-merchant.mt.schema';
+import { authMiddleware } from '@d8d/auth-module-mt';
+
+// 多租户管理员专用商户路由(完整权限,包含租户隔离)
+export const adminMerchantRoutes = createCrudRoutes({
+  entity: MerchantMt,
+  createSchema: AdminCreateMerchantDtoMt,
+  updateSchema: AdminUpdateMerchantDtoMt,
+  getSchema: AdminMerchantSchemaMt,
+  listSchema: AdminMerchantSchemaMt,
+  searchFields: ['name', 'username', 'realname', 'phone'],
+  middleware: [authMiddleware],
+  userTracking: {
+    createdByField: 'createdBy',
+    updatedByField: 'updatedBy'
+  },
+  tenantOptions: {
+    tenantIdField: 'tenantId'
+  },
+  dataPermission: {
+    enabled: false, // 管理员路由不使用数据权限控制
+    userIdField: 'createdBy' // 即使不启用,也需要提供userIdField
+  }
+});

+ 5 - 0
packages/merchant-module-mt/src/routes/index.ts

@@ -0,0 +1,5 @@
+// 多租户商户路由导出
+// 注意:多租户版本不提供基础商户路由,只有用户和管理员路由
+
+export { userMerchantRoutes } from './user-routes.mt';
+export { adminMerchantRoutes } from './admin-routes.mt';

+ 29 - 0
packages/merchant-module-mt/src/routes/user-routes.mt.ts

@@ -0,0 +1,29 @@
+import { createCrudRoutes } from '@d8d/shared-crud';
+import { MerchantMt } from '../entities/merchant.mt.entity';
+import { UserMerchantSchemaMt, UserCreateMerchantDtoMt, UserUpdateMerchantDtoMt } from '../schemas/user-merchant.mt.schema';
+import { authMiddleware } from '@d8d/auth-module-mt';
+
+// 多租户用户专用商户路由(仅限当前用户使用,包含租户隔离)
+export const userMerchantRoutes = createCrudRoutes({
+  entity: MerchantMt,
+  createSchema: UserCreateMerchantDtoMt,
+  updateSchema: UserUpdateMerchantDtoMt,
+  getSchema: UserMerchantSchemaMt,
+  listSchema: UserMerchantSchemaMt,
+  searchFields: ['name', 'username', 'realname', 'phone'],
+  middleware: [authMiddleware],
+  userTracking: {
+    createdByField: 'createdBy',
+    updatedByField: 'updatedBy'
+  },
+  tenantOptions: {
+    tenantIdField: 'tenantId'
+  },
+  dataPermission: {
+    enabled: true,
+    userIdField: 'createdBy',
+    adminOverride: {
+      enabled: false // 用户路由不允许管理员覆盖
+    }
+  }
+});

+ 152 - 0
packages/merchant-module-mt/src/schemas/admin-merchant.mt.schema.ts

@@ -0,0 +1,152 @@
+import { z } from '@hono/zod-openapi';
+
+export const AdminMerchantSchemaMt = z.object({
+  tenantId: z.number().int().positive().openapi({ description: '租户ID' }),
+  id: z.number().int().positive().openapi({ description: '商户ID' }),
+  name: z.string().min(1, '商户名称不能为空').max(255, '商户名称最多255个字符').nullable().openapi({
+    description: '商户名称',
+    example: '商户A'
+  }),
+  username: z.string().min(1, '用户名不能为空').max(20, '用户名最多20个字符').openapi({
+    description: '用户名',
+    example: 'merchant001'
+  }),
+  password: z.string().min(6, '密码至少6位').max(255, '密码最多255位').openapi({
+    description: '密码',
+    example: 'password123'
+  }),
+  phone: z.string().regex(/^1[3-9]\d{9}$/, '请输入正确的手机号').nullable().optional().openapi({
+    description: '手机号码',
+    example: '13800138000'
+  }),
+  realname: z.string().max(20, '姓名最多20个字符').nullable().optional().openapi({
+    description: '姓名',
+    example: '李四'
+  }),
+  loginNum: z.number().int().nonnegative('登录次数必须为非负数').default(0).openapi({
+    description: '登录次数',
+    example: 0
+  }),
+  loginTime: z.number().int().nonnegative('登录时间必须为非负数').default(0).openapi({
+    description: '登录时间',
+    example: 0
+  }),
+  loginIp: z.string().max(15, 'IP地址最多15个字符').nullable().optional().openapi({
+    description: '登录IP',
+    example: '192.168.1.1'
+  }),
+  lastLoginTime: z.number().int().nonnegative('上次登录时间必须为非负数').default(0).openapi({
+    description: '上次登录时间',
+    example: 0
+  }),
+  lastLoginIp: z.string().max(15, 'IP地址最多15个字符').nullable().optional().openapi({
+    description: '上次登录IP',
+    example: '192.168.1.1'
+  }),
+  state: z.number().int().min(1).max(2).default(2).openapi({
+    description: '状态 1启用 2禁用',
+    example: 1
+  }),
+  rsaPublicKey: z.string().max(2000, '公钥最多2000个字符').nullable().optional().openapi({
+    description: '公钥',
+    example: '-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...\n-----END PUBLIC KEY-----'
+  }),
+  aesKey: z.string().max(32, 'aes秘钥最多32个字符').nullable().optional().openapi({
+    description: 'aes秘钥',
+    example: 'a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6'
+  }),
+  createdAt: z.coerce.date().openapi({
+    description: '创建时间',
+    example: '2024-01-01T12:00:00Z'
+  }),
+  updatedAt: z.coerce.date().openapi({
+    description: '更新时间',
+    example: '2024-01-01T12:00:00Z'
+  }),
+  createdBy: z.number().int().positive().nullable().openapi({
+    description: '创建用户ID',
+    example: 1
+  }),
+  updatedBy: z.number().int().positive().nullable().openapi({
+    description: '更新用户ID',
+    example: 1
+  })
+});
+
+export const AdminCreateMerchantDto = z.object({
+  name: z.string().min(1, '商户名称不能为空').max(255, '商户名称最多255个字符').nullable().optional().openapi({
+    description: '商户名称',
+    example: '商户A'
+  }),
+  username: z.string().min(1, '用户名不能为空').max(20, '用户名最多20个字符').openapi({
+    description: '用户名',
+    example: 'merchant001'
+  }),
+  password: z.string().min(6, '密码至少6位').max(255, '密码最多255位').openapi({
+    description: '密码',
+    example: 'password123'
+  }),
+  phone: z.string().regex(/^1[3-9]\d{9}$/, '请输入正确的手机号').nullable().optional().openapi({
+    description: '手机号码',
+    example: '13800138000'
+  }),
+  realname: z.string().max(20, '姓名最多20个字符').nullable().optional().openapi({
+    description: '姓名',
+    example: '李四'
+  }),
+  state: z.number().int().min(1).max(2).default(2).openapi({
+    description: '状态 1启用 2禁用',
+    example: 1
+  }),
+  rsaPublicKey: z.string().max(2000, '公钥最多2000个字符').nullable().optional().openapi({
+    description: '公钥',
+    example: '-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...\n-----END PUBLIC KEY-----'
+  }),
+  aesKey: z.string().max(32, 'aes秘钥最多32个字符').nullable().optional().openapi({
+    description: 'aes秘钥',
+    example: 'a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6'
+  }),
+  createdBy: z.number().int().positive().nullable().optional().openapi({
+    description: '创建用户ID',
+    example: 1
+  })
+});
+
+export const AdminUpdateMerchantDto = z.object({
+  name: z.string().min(1, '商户名称不能为空').max(255, '商户名称最多255个字符').nullable().optional().openapi({
+    description: '商户名称',
+    example: '商户A'
+  }),
+  username: z.string().min(1, '用户名不能为空').max(20, '用户名最多20个字符').optional().openapi({
+    description: '用户名',
+    example: 'merchant001'
+  }),
+  password: z.string().min(6, '密码至少6位').max(255, '密码最多255位').optional().openapi({
+    description: '密码',
+    example: 'password123'
+  }),
+  phone: z.string().regex(/^1[3-9]\d{9}$/, '请输入正确的手机号').nullable().optional().openapi({
+    description: '手机号码',
+    example: '13800138000'
+  }),
+  realname: z.string().max(20, '姓名最多20个字符').nullable().optional().openapi({
+    description: '姓名',
+    example: '李四'
+  }),
+  state: z.number().int().min(1).max(2).optional().openapi({
+    description: '状态 1启用 2禁用',
+    example: 1
+  }),
+  rsaPublicKey: z.string().max(2000, '公钥最多2000个字符').nullable().optional().openapi({
+    description: '公钥',
+    example: '-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...\n-----END PUBLIC KEY-----'
+  }),
+  aesKey: z.string().max(32, 'aes秘钥最多32个字符').nullable().optional().openapi({
+    description: 'aes秘钥',
+    example: 'a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6'
+  }),
+  updatedBy: z.number().int().positive().nullable().optional().openapi({
+    description: '更新用户ID',
+    example: 1
+  })
+});

+ 3 - 0
packages/merchant-module-mt/src/schemas/index.ts

@@ -0,0 +1,3 @@
+export { MerchantSchemaMt, CreateMerchantDtoMt, UpdateMerchantDtoMt } from './merchant.mt.schema';
+export { UserMerchantSchemaMt, UserCreateMerchantDtoMt, UserUpdateMerchantDtoMt } from './user-merchant.mt.schema';
+export { AdminMerchantSchemaMt, AdminCreateMerchantDtoMt, AdminUpdateMerchantDtoMt } from './admin-merchant.mt.schema';

+ 146 - 0
packages/merchant-module-mt/src/schemas/merchant.mt.schema.ts

@@ -0,0 +1,146 @@
+import { z } from '@hono/zod-openapi';
+
+export const MerchantSchemaMt = z.object({
+  tenantId: z.number().int().positive().openapi({ description: '租户ID' }),
+  id: z.number().int().positive().openapi({ description: '商户ID' }),
+  name: z.string().min(1, '商户名称不能为空').max(255, '商户名称最多255个字符').nullable().openapi({
+    description: '商户名称',
+    example: '商户A'
+  }),
+  username: z.string().min(1, '用户名不能为空').max(20, '用户名最多20个字符').openapi({
+    description: '用户名',
+    example: 'merchant001'
+  }),
+  password: z.string().min(6, '密码至少6位').max(255, '密码最多255位').openapi({
+    description: '密码',
+    example: 'password123'
+  }),
+  phone: z.string().regex(/^1[3-9]\d{9}$/, '请输入正确的手机号').nullable().optional().openapi({
+    description: '手机号码',
+    example: '13800138000'
+  }),
+  realname: z.string().max(20, '姓名最多20个字符').nullable().optional().openapi({
+    description: '姓名',
+    example: '李四'
+  }),
+  loginNum: z.number().int().nonnegative('登录次数必须为非负数').default(0).openapi({
+    description: '登录次数',
+    example: 0
+  }),
+  loginTime: z.number().int().nonnegative('登录时间必须为非负数').default(0).openapi({
+    description: '登录时间',
+    example: 0
+  }),
+  loginIp: z.string().max(15, 'IP地址最多15个字符').nullable().optional().openapi({
+    description: '登录IP',
+    example: '192.168.1.1'
+  }),
+  lastLoginTime: z.number().int().nonnegative('上次登录时间必须为非负数').default(0).openapi({
+    description: '上次登录时间',
+    example: 0
+  }),
+  lastLoginIp: z.string().max(15, 'IP地址最多15个字符').nullable().optional().openapi({
+    description: '上次登录IP',
+    example: '192.168.1.1'
+  }),
+  state: z.number().int().min(1).max(2).default(2).openapi({
+    description: '状态 1启用 2禁用',
+    example: 1
+  }),
+  rsaPublicKey: z.string().max(2000, '公钥最多2000个字符').nullable().optional().openapi({
+    description: '公钥',
+    example: '-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...\n-----END PUBLIC KEY-----'
+  }),
+  aesKey: z.string().max(32, 'aes秘钥最多32个字符').nullable().optional().openapi({
+    description: 'aes秘钥',
+    example: 'a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6'
+  }),
+  createdAt: z.coerce.date().openapi({
+    description: '创建时间',
+    example: '2024-01-01T12:00:00Z'
+  }),
+  updatedAt: z.coerce.date().openapi({
+    description: '更新时间',
+    example: '2024-01-01T12:00:00Z'
+  }),
+  createdBy: z.number().int().positive().nullable().openapi({
+    description: '创建用户ID',
+    example: 1
+  }),
+  updatedBy: z.number().int().positive().nullable().openapi({
+    description: '更新用户ID',
+    example: 1
+  })
+});
+
+export const CreateMerchantDtoMt = z.object({
+  tenantId: z.number().int().positive().openapi({ description: '租户ID' }),
+  name: z.string().min(1, '商户名称不能为空').max(255, '商户名称最多255个字符').nullable().optional().openapi({
+    description: '商户名称',
+    example: '商户A'
+  }),
+  username: z.string().min(1, '用户名不能为空').max(20, '用户名最多20个字符').openapi({
+    description: '用户名',
+    example: 'merchant001'
+  }),
+  password: z.string().min(6, '密码至少6位').max(255, '密码最多255位').openapi({
+    description: '密码',
+    example: 'password123'
+  }),
+  phone: z.string().regex(/^1[3-9]\d{9}$/, '请输入正确的手机号').nullable().optional().openapi({
+    description: '手机号码',
+    example: '13800138000'
+  }),
+  realname: z.string().max(20, '姓名最多20个字符').nullable().optional().openapi({
+    description: '姓名',
+    example: '李四'
+  }),
+  state: z.number().int().min(1).max(2).default(2).openapi({
+    description: '状态 1启用 2禁用',
+    example: 1
+  }),
+  rsaPublicKey: z.string().max(2000, '公钥最多2000个字符').nullable().optional().openapi({
+    description: '公钥',
+    example: '-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...\n-----END PUBLIC KEY-----'
+  }),
+  aesKey: z.string().max(32, 'aes秘钥最多32个字符').nullable().optional().openapi({
+    description: 'aes秘钥',
+    example: 'a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6'
+  })
+});
+
+export const UpdateMerchantDtoMt = z.object({
+  tenantId: z.number().int().positive().optional().openapi({ description: '租户ID' }),
+  name: z.string().min(1, '商户名称不能为空').max(255, '商户名称最多255个字符').nullable().optional().openapi({
+    description: '商户名称',
+    example: '商户A'
+  }),
+  username: z.string().min(1, '用户名不能为空').max(20, '用户名最多20个字符').optional().openapi({
+    description: '用户名',
+    example: 'merchant001'
+  }),
+  password: z.string().min(6, '密码至少6位').max(255, '密码最多255位').optional().openapi({
+    description: '密码',
+    example: 'password123'
+  }),
+  phone: z.string().regex(/^1[3-9]\d{9}$/, '请输入正确的手机号').nullable().optional().openapi({
+    description: '手机号码',
+    example: '13800138000'
+  }),
+  realname: z.string().max(20, '姓名最多20个字符').nullable().optional().openapi({
+    description: '姓名',
+    example: '李四'
+  }),
+  state: z.number().int().min(1).max(2).optional().openapi({
+    description: '状态 1启用 2禁用',
+    example: 1
+  }),
+  rsaPublicKey: z.string().max(2000, '公钥最多2000个字符').nullable().optional().openapi({
+    description: '公钥',
+    example: '-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...\n-----END PUBLIC KEY-----'
+  }),
+  aesKey: z.string().max(32, 'aes秘钥最多32个字符').nullable().optional().openapi({
+    description: 'aes秘钥',
+    example: 'a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6'
+  })
+});

+ 142 - 0
packages/merchant-module-mt/src/schemas/user-merchant.mt.schema.ts

@@ -0,0 +1,142 @@
+import { z } from '@hono/zod-openapi';
+
+export const UserMerchantSchemaMt = z.object({
+  tenantId: z.number().int().positive().openapi({ description: '租户ID' }),
+  id: z.number().int().positive().openapi({ description: '商户ID' }),
+  name: z.string().min(1, '商户名称不能为空').max(255, '商户名称最多255个字符').nullable().openapi({
+    description: '商户名称',
+    example: '商户A'
+  }),
+  username: z.string().min(1, '用户名不能为空').max(20, '用户名最多20个字符').openapi({
+    description: '用户名',
+    example: 'merchant001'
+  }),
+  phone: z.string().regex(/^1[3-9]\d{9}$/, '请输入正确的手机号').nullable().optional().openapi({
+    description: '手机号码',
+    example: '13800138000'
+  }),
+  realname: z.string().max(20, '姓名最多20个字符').nullable().optional().openapi({
+    description: '姓名',
+    example: '李四'
+  }),
+  loginNum: z.number().int().nonnegative('登录次数必须为非负数').default(0).openapi({
+    description: '登录次数',
+    example: 0
+  }),
+  loginTime: z.number().int().nonnegative('登录时间必须为非负数').default(0).openapi({
+    description: '登录时间',
+    example: 0
+  }),
+  loginIp: z.string().max(15, 'IP地址最多15个字符').nullable().optional().openapi({
+    description: '登录IP',
+    example: '192.168.1.1'
+  }),
+  lastLoginTime: z.number().int().nonnegative('上次登录时间必须为非负数').default(0).openapi({
+    description: '上次登录时间',
+    example: 0
+  }),
+  lastLoginIp: z.string().max(15, 'IP地址最多15个字符').nullable().optional().openapi({
+    description: '上次登录IP',
+    example: '192.168.1.1'
+  }),
+  state: z.number().int().min(1).max(2).default(2).openapi({
+    description: '状态 1启用 2禁用',
+    example: 1
+  }),
+  rsaPublicKey: z.string().max(2000, '公钥最多2000个字符').nullable().optional().openapi({
+    description: '公钥',
+    example: '-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...\n-----END PUBLIC KEY-----'
+  }),
+  aesKey: z.string().max(32, 'aes秘钥最多32个字符').nullable().optional().openapi({
+    description: 'aes秘钥',
+    example: 'a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6'
+  }),
+  createdAt: z.coerce.date().openapi({
+    description: '创建时间',
+    example: '2024-01-01T12:00:00Z'
+  }),
+  updatedAt: z.coerce.date().openapi({
+    description: '更新时间',
+    example: '2024-01-01T12:00:00Z'
+  }),
+  createdBy: z.number().int().positive().nullable().optional().openapi({
+    description: '创建用户ID',
+    example: 1
+  }),
+  updatedBy: z.number().int().positive().nullable().optional().openapi({
+    description: '更新用户ID',
+    example: 1
+  })
+});
+
+export const UserCreateMerchantDtoMt = z.object({
+  tenantId: z.number().int().positive().openapi({ description: '租户ID' }),
+  name: z.string().min(1, '商户名称不能为空').max(255, '商户名称最多255个字符').nullable().optional().openapi({
+    description: '商户名称',
+    example: '商户A'
+  }),
+  username: z.string().min(1, '用户名不能为空').max(20, '用户名最多20个字符').openapi({
+    description: '用户名',
+    example: 'merchant001'
+  }),
+  password: z.string().min(6, '密码至少6位').max(255, '密码最多255位').openapi({
+    description: '密码',
+    example: 'password123'
+  }),
+  phone: z.string().regex(/^1[3-9]\d{9}$/, '请输入正确的手机号').nullable().optional().openapi({
+    description: '手机号码',
+    example: '13800138000'
+  }),
+  realname: z.string().max(20, '姓名最多20个字符').nullable().optional().openapi({
+    description: '姓名',
+    example: '李四'
+  }),
+  state: z.number().int().min(1).max(2).default(2).openapi({
+    description: '状态 1启用 2禁用',
+    example: 1
+  }),
+  rsaPublicKey: z.string().max(2000, '公钥最多2000个字符').nullable().optional().openapi({
+    description: '公钥',
+    example: '-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...\n-----END PUBLIC KEY-----'
+  }),
+  aesKey: z.string().max(32, 'aes秘钥最多32个字符').nullable().optional().openapi({
+    description: 'aes秘钥',
+    example: 'a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6'
+  })
+});
+
+export const UserUpdateMerchantDtoMt = z.object({
+  tenantId: z.number().int().positive().optional().openapi({ description: '租户ID' }),
+  name: z.string().min(1, '商户名称不能为空').max(255, '商户名称最多255个字符').nullable().optional().openapi({
+    description: '商户名称',
+    example: '商户A'
+  }),
+  username: z.string().min(1, '用户名不能为空').max(20, '用户名最多20个字符').optional().openapi({
+    description: '用户名',
+    example: 'merchant001'
+  }),
+  password: z.string().min(6, '密码至少6位').max(255, '密码最多255位').optional().openapi({
+    description: '密码',
+    example: 'password123'
+  }),
+  phone: z.string().regex(/^1[3-9]\d{9}$/, '请输入正确的手机号').nullable().optional().openapi({
+    description: '手机号码',
+    example: '13800138000'
+  }),
+  realname: z.string().max(20, '姓名最多20个字符').nullable().optional().openapi({
+    description: '姓名',
+    example: '李四'
+  }),
+  state: z.number().int().min(1).max(2).optional().openapi({
+    description: '状态 1启用 2禁用',
+    example: 1
+  }),
+  rsaPublicKey: z.string().max(2000, '公钥最多2000个字符').nullable().optional().openapi({
+    description: '公钥',
+    example: '-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...\n-----END PUBLIC KEY-----'
+  }),
+  aesKey: z.string().max(32, 'aes秘钥最多32个字符').nullable().optional().openapi({
+    description: 'aes秘钥',
+    example: 'a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6'
+  })
+});

+ 1 - 0
packages/merchant-module-mt/src/services/index.ts

@@ -0,0 +1 @@
+export { MerchantServiceMt } from './merchant.mt.service';

+ 69 - 0
packages/merchant-module-mt/src/services/merchant.mt.service.ts

@@ -0,0 +1,69 @@
+import { GenericCrudService } from '@d8d/shared-crud';
+import { DataSource } from 'typeorm';
+import { MerchantMt } from '../entities/merchant.mt.entity';
+
+export class MerchantServiceMt extends GenericCrudService<MerchantMt> {
+  constructor(dataSource: DataSource) {
+    super(dataSource, MerchantMt, {
+      userTracking: {
+        createdByField: 'createdBy',
+        updatedByField: 'updatedBy'
+      },
+      tenantOptions: {
+        tenantIdField: 'tenantId'
+      }
+    });
+  }
+
+  /**
+   * 更新商户登录统计信息
+   */
+  async updateLoginStats(
+    merchantId: number,
+    loginTime: number,
+    loginIp: string
+  ): Promise<boolean> {
+    const merchant = await this.getById(merchantId);
+    if (!merchant) {
+      return false;
+    }
+
+    // 更新登录统计
+    const updateData = {
+      loginNum: merchant.loginNum + 1,
+      lastLoginTime: merchant.loginTime,
+      lastLoginIp: merchant.loginIp,
+      loginTime,
+      loginIp
+    };
+
+    await this.update(merchantId, updateData);
+    return true;
+  }
+
+  /**
+   * 根据用户名查找商户(包含租户过滤)
+   */
+  async findByUsername(username: string, tenantId?: number): Promise<MerchantMt | null> {
+    const where: any = { username };
+    if (tenantId !== undefined) {
+      where.tenantId = tenantId;
+    }
+    return this.repository.findOne({
+      where
+    });
+  }
+
+  /**
+   * 根据状态获取商户列表(包含租户过滤)
+   */
+  async getByState(state: number, tenantId?: number): Promise<MerchantMt[]> {
+    const where: any = { state };
+    if (tenantId !== undefined) {
+      where.tenantId = tenantId;
+    }
+    return this.repository.find({
+      where
+    });
+  }
+}

+ 8 - 0
packages/merchant-module-mt/src/types/index.ts

@@ -0,0 +1,8 @@
+export type {
+  MerchantState,
+  MerchantStateType,
+  MerchantLoginStats,
+  MerchantSecurity,
+} from './merchant.types';
+
+export { MERCHANT_STATE } from './merchant.types';

+ 26 - 0
packages/merchant-module-mt/src/types/merchant.types.ts

@@ -0,0 +1,26 @@
+import { z } from 'zod';
+
+export interface MerchantState {
+  ENABLED: 1;
+  DISABLED: 2;
+}
+
+export const MERCHANT_STATE: MerchantState = {
+  ENABLED: 1,
+  DISABLED: 2,
+} as const;
+
+export type MerchantStateType = typeof MERCHANT_STATE[keyof typeof MERCHANT_STATE];
+
+export interface MerchantLoginStats {
+  loginNum: number;
+  loginTime: number;
+  loginIp: string | null;
+  lastLoginTime: number;
+  lastLoginIp: string | null;
+}
+
+export interface MerchantSecurity {
+  rsaPublicKey: string | null;
+  aesKey: string | null;
+}

+ 117 - 0
packages/merchant-module-mt/tests/factories/merchant.factory.ts

@@ -0,0 +1,117 @@
+import { IntegrationTestDatabase } from '@d8d/shared-test-util';
+import { MerchantMt } from '../../src/entities/merchant.mt.entity';
+
+/**
+ * 商户测试数据工厂
+ * 用于创建商户测试数据,减少重复代码
+ */
+export class MerchantTestFactory {
+  /**
+   * 创建测试商户
+   */
+  static async createTestMerchant(
+    createdBy: number,
+    tenantId: number = 1,
+    overrides: Partial<MerchantMt> = {}
+  ): Promise<MerchantMt> {
+    const dataSource = await IntegrationTestDatabase.getDataSource();
+    const merchantRepository = dataSource.getRepository(MerchantMt);
+
+    const timestamp = Date.now();
+    const defaultMerchant = {
+      name: `测试商户_${timestamp}`,
+      username: `test_merchant_${timestamp}`,
+      password: 'password123',
+      phone: `13800138${timestamp.toString().slice(-4)}`,
+      realname: '测试商户',
+      state: 1,
+      loginNum: 0,
+      loginTime: 0,
+      loginIp: '',
+      lastLoginTime: 0,
+      lastLoginIp: '',
+      rsaPublicKey: '',
+      aesKey: '',
+      tenantId,
+      createdBy
+    };
+
+    const merchantData = { ...defaultMerchant, ...overrides };
+    const merchant = merchantRepository.create(merchantData);
+    return await merchantRepository.save(merchant);
+  }
+
+  /**
+   * 创建多个测试商户
+   */
+  static async createTestMerchants(
+    createdBy: number,
+    count: number,
+    tenantId: number = 1,
+    overrides: Partial<MerchantMt> = {}
+  ): Promise<MerchantMt[]> {
+    const merchants: MerchantMt[] = [];
+    for (let i = 0; i < count; i++) {
+      const merchant = await this.createTestMerchant(createdBy, tenantId, {
+        ...overrides,
+        name: `测试商户_${i + 1}`,
+        username: `test_merchant_${i + 1}_${Date.now()}`
+      });
+      merchants.push(merchant);
+    }
+    return merchants;
+  }
+
+  /**
+   * 创建不同租户的测试商户
+   */
+  static async createMultiTenantTestMerchants(
+    createdBy: number,
+    tenantIds: number[] = [1, 2]
+  ): Promise<Record<number, MerchantMt[]>> {
+    const result: Record<number, MerchantMt[]> = {};
+
+    for (const tenantId of tenantIds) {
+      const merchants = await this.createTestMerchants(createdBy, 2, tenantId, {
+        name: `租户${tenantId}商户`
+      });
+      result[tenantId] = merchants;
+    }
+
+    return result;
+  }
+
+  /**
+   * 创建测试商户数据集
+   */
+  static async createTestMerchantDataSet(
+    userId: number,
+    otherUserId: number,
+    tenantId: number = 1
+  ): Promise<{
+    userMerchants: MerchantMt[];
+    otherUserMerchants: MerchantMt[];
+  }> {
+    const userMerchants = await this.createTestMerchants(userId, 2, tenantId, {
+      name: '用户商户'
+    });
+
+    const otherUserMerchants = await this.createTestMerchants(otherUserId, 1, tenantId, {
+      name: '其他用户商户'
+    });
+
+    return {
+      userMerchants,
+      otherUserMerchants
+    };
+  }
+
+  /**
+   * 清理商户测试数据
+   */
+  static async cleanupMerchantTestData(): Promise<void> {
+    const dataSource = await IntegrationTestDatabase.getDataSource();
+    const merchantRepository = dataSource.getRepository(MerchantMt);
+    await merchantRepository.clear();
+  }
+}

+ 579 - 0
packages/merchant-module-mt/tests/integration/admin-routes.integration.test.ts

@@ -0,0 +1,579 @@
+import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
+import { testClient } from 'hono/testing';
+import { IntegrationTestDatabase, setupIntegrationDatabaseHooksWithEntities } from '@d8d/shared-test-util';
+import { JWTUtil } from '@d8d/shared-utils';
+import { UserEntityMt, RoleMt } from '@d8d/user-module-mt';
+import { FileMt } from '@d8d/file-module-mt';
+import { adminMerchantRoutes } from '../../src/routes/admin-routes.mt';
+import { MerchantMt } from '../../src/entities/merchant.mt.entity';
+import { MerchantTestUtils } from '../utils/test-utils';
+
+// 设置集成测试钩子
+setupIntegrationDatabaseHooksWithEntities([UserEntityMt, RoleMt, MerchantMt, FileMt])
+
+describe('管理员商户管理API集成测试', () => {
+  let client: ReturnType<typeof testClient<typeof adminMerchantRoutes>>;
+  let adminToken: string;
+  let testUser: UserEntityMt;
+  let testAdmin: UserEntityMt;
+
+  beforeEach(async () => {
+    // 创建测试客户端
+    client = testClient(adminMerchantRoutes);
+
+    // 获取数据源
+    const dataSource = await IntegrationTestDatabase.getDataSource();
+
+    // 创建测试用户
+    testUser = await MerchantTestUtils.createTestUser({ tenantId: 1 });
+
+    // 创建测试管理员用户
+    testAdmin = await MerchantTestUtils.createTestUser({
+      username: `test_admin_${Date.now()}`,
+      nickname: '测试管理员',
+      tenantId: 1
+    });
+
+    // 生成测试管理员的token
+    adminToken = JWTUtil.generateToken({
+      id: testAdmin.id,
+      username: testAdmin.username,
+      roles: [{name:'admin'}],
+      tenantId: 1
+    });
+  });
+
+  describe('GET /merchants', () => {
+    it('应该返回商户列表', async () => {
+      // 创建一些测试商户
+      await MerchantTestUtils.createTestMerchants(testUser.id, 3, 1);
+
+      const response = await client.index.$get({
+        query: {}
+      }, {
+        headers: {
+          'Authorization': `Bearer ${adminToken}`
+        }
+      });
+
+      console.debug('商户列表响应状态:', response.status);
+      expect(response.status).toBe(200);
+
+      if (response.status === 200) {
+        const data = await response.json();
+        expect(data).toHaveProperty('data');
+        expect(Array.isArray(data.data)).toBe(true);
+      }
+    });
+
+    it('应该拒绝未认证用户的访问', async () => {
+      const response = await client.index.$get({
+        query: {}
+      });
+      expect(response.status).toBe(401);
+    });
+  });
+
+  describe('POST /merchants', () => {
+    it('应该成功创建商户', async () => {
+      const createData = {
+        name: '新商户',
+        username: `new_${Date.now()}`,
+        password: 'password123',
+        phone: '13800138000',
+        realname: '张三',
+        state: 1,
+        tenantId: 1
+      };
+
+      const response = await client.index.$post({
+        json: createData
+      }, {
+        headers: {
+          'Authorization': `Bearer ${adminToken}`
+        }
+      });
+
+      console.debug('创建商户响应状态:', response.status);
+      if (response.status !== 201) {
+        const errorData = await response.json();
+        console.debug('创建商户错误响应:', errorData);
+      }
+      expect(response.status).toBe(201);
+
+      if (response.status === 201) {
+        const data = await response.json();
+        expect(data).toHaveProperty('id');
+        expect(data.name).toBe(createData.name);
+        expect(data.username).toBe(createData.username);
+        expect(data.phone).toBe(createData.phone);
+        expect(data.realname).toBe(createData.realname);
+        expect(data.state).toBe(createData.state);
+        expect(data.tenantId).toBe(1); // 自动使用租户ID
+      }
+    });
+
+    it('应该验证创建商户的必填字段', async () => {
+      const invalidData = {
+        // 缺少必填字段
+        name: '',
+        username: '',
+        password: ''
+      };
+
+      const response = await client.index.$post({
+        json: invalidData
+      }, {
+        headers: {
+          'Authorization': `Bearer ${adminToken}`
+        }
+      });
+
+      expect(response.status).toBe(400);
+    });
+  });
+
+  describe('GET /merchants/:id', () => {
+    it('应该返回指定商户的详情', async () => {
+      // 使用测试工具创建商户
+      const testMerchant = await MerchantTestUtils.createTestMerchant(testUser.id, 1);
+
+      const response = await client[':id'].$get({
+        param: { id: testMerchant.id }
+      }, {
+        headers: {
+          'Authorization': `Bearer ${adminToken}`
+        }
+      });
+
+      console.debug('商户详情响应状态:', response.status);
+      expect(response.status).toBe(200);
+
+      if (response.status === 200) {
+        const data = await response.json();
+        expect(data.id).toBe(testMerchant.id);
+        expect(data.name).toBe(testMerchant.name);
+        expect(data.username).toBe(testMerchant.username);
+        expect(data.phone).toBe(testMerchant.phone);
+        expect(data.realname).toBe(testMerchant.realname);
+        expect(data.tenantId).toBe(1);
+      }
+    });
+
+    it('应该处理不存在的商户', async () => {
+      const response = await client[':id'].$get({
+        param: { id: 999999 }
+      }, {
+        headers: {
+          'Authorization': `Bearer ${adminToken}`
+        }
+      });
+
+      expect(response.status).toBe(404);
+    });
+  });
+
+  describe('PUT /merchants/:id', () => {
+    it('应该成功更新商户', async () => {
+      // 使用测试工具创建商户
+      const testMerchant = await MerchantTestUtils.createTestMerchant(testUser.id, 1);
+
+      const updateData = {
+        name: '更新后的商户',
+        phone: '13900139000',
+        realname: '更新后的姓名',
+        state: 2
+      };
+
+      const response = await client[':id'].$put({
+        param: { id: testMerchant.id },
+        json: updateData
+      }, {
+        headers: {
+          'Authorization': `Bearer ${adminToken}`
+        }
+      });
+
+      console.debug('更新商户响应状态:', response.status);
+      expect(response.status).toBe(200);
+
+      if (response.status === 200) {
+        const data = await response.json();
+        expect(data.name).toBe(updateData.name);
+        expect(data.phone).toBe(updateData.phone);
+        expect(data.realname).toBe(updateData.realname);
+        expect(data.state).toBe(updateData.state);
+      }
+    });
+  });
+
+  describe('DELETE /merchants/:id', () => {
+    it('应该成功删除商户', async () => {
+      // 使用测试工具创建商户
+      const testMerchant = await MerchantTestUtils.createTestMerchant(testUser.id, 1);
+
+      const response = await client[':id'].$delete({
+        param: { id: testMerchant.id }
+      }, {
+        headers: {
+          'Authorization': `Bearer ${adminToken}`
+        }
+      });
+
+      console.debug('删除商户响应状态:', response.status);
+      expect(response.status).toBe(204);
+
+      // 验证商户确实被删除
+      const dataSource = await IntegrationTestDatabase.getDataSource();
+      const merchantRepository = dataSource.getRepository(MerchantMt);
+      const deletedMerchant = await merchantRepository.findOne({
+        where: { id: testMerchant.id }
+      });
+      expect(deletedMerchant).toBeNull();
+    });
+  });
+
+  describe('管理员权限测试', () => {
+    it('管理员应该可以为其他用户创建商户', async () => {
+      const createData = {
+        name: '其他用户商户',
+        username: `oum_${Date.now()}`,
+        password: 'password123',
+        phone: '13800138001',
+        realname: '李四',
+        state: 1,
+        tenantId: 1
+      };
+
+      const response = await client.index.$post({
+        json: createData
+      }, {
+        headers: {
+          'Authorization': `Bearer ${adminToken}`
+        }
+      });
+
+      console.debug('管理员为其他用户创建商户响应状态:', response.status);
+      expect(response.status).toBe(201);
+
+      if (response.status === 201) {
+        const data = await response.json();
+        expect(data.createdBy).toBe(testAdmin.id); // 管理员创建商户时使用管理员自己的ID
+        expect(data.name).toBe(createData.name);
+        expect(data.tenantId).toBe(1);
+      }
+    });
+
+    it('管理员应该可以访问所有用户的商户', async () => {
+      // 为测试用户创建一些商户
+      await MerchantTestUtils.createTestMerchants(testUser.id, 2, 1);
+
+      // 管理员应该能看到所有商户
+      const response = await client.index.$get({
+        query: {}
+      }, {
+        headers: {
+          'Authorization': `Bearer ${adminToken}`
+        }
+      });
+
+      expect(response.status).toBe(200);
+      const data = await response.json();
+      if (data && 'data' in data) {
+        expect(Array.isArray(data.data)).toBe(true);
+        expect(data.data.length).toBeGreaterThanOrEqual(2); // 至少包含我们创建的两个商户
+      }
+    });
+
+    it('管理员应该可以更新其他用户的商户', async () => {
+      // 先为测试用户创建一个商户
+      const testMerchant = await MerchantTestUtils.createTestMerchant(testUser.id, 1);
+
+      const updateData = {
+        name: '管理员更新的商户',
+        phone: '13900139000',
+        realname: '管理员更新的姓名'
+      };
+
+      const response = await client[':id'].$put({
+        param: { id: testMerchant.id },
+        json: updateData
+      }, {
+        headers: {
+          'Authorization': `Bearer ${adminToken}`
+        }
+      });
+
+      console.debug('管理员更新其他用户商户响应状态:', response.status);
+      expect(response.status).toBe(200);
+
+      if (response.status === 200) {
+        const data = await response.json();
+        expect(data.name).toBe(updateData.name);
+        expect(data.phone).toBe(updateData.phone);
+        expect(data.realname).toBe(updateData.realname);
+      }
+    });
+
+    it('管理员应该可以删除其他用户的商户', async () => {
+      // 先为测试用户创建一个商户
+      const testMerchant = await MerchantTestUtils.createTestMerchant(testUser.id, 1);
+
+      const response = await client[':id'].$delete({
+        param: { id: testMerchant.id }
+      }, {
+        headers: {
+          'Authorization': `Bearer ${adminToken}`
+        }
+      });
+
+      console.debug('管理员删除其他用户商户响应状态:', response.status);
+      expect(response.status).toBe(204);
+
+      // 验证商户确实被删除
+      const dataSource = await IntegrationTestDatabase.getDataSource();
+      const merchantRepository = dataSource.getRepository(MerchantMt);
+      const deletedMerchant = await merchantRepository.findOne({
+        where: { id: testMerchant.id }
+      });
+      expect(deletedMerchant).toBeNull();
+    });
+
+    it('管理员应该可以查询指定用户的商户', async () => {
+      // 为测试用户创建一些商户
+      const userMerchants = await MerchantTestUtils.createTestMerchants(testUser.id, 2, 1);
+
+      // 管理员可以查询指定用户的商户
+      const response = await client.index.$get({
+        query: { filters: JSON.stringify({ createdBy: testUser.id }) }
+      }, {
+        headers: {
+          'Authorization': `Bearer ${adminToken}`
+        }
+      });
+
+      expect(response.status).toBe(200);
+      const data = await response.json();
+      if (data && 'data' in data) {
+        expect(Array.isArray(data.data)).toBe(true);
+
+        // 验证返回的商户都属于指定用户
+        if (data.data.length > 0) {
+          data.data.forEach((merchant: any) => {
+            expect(merchant.createdBy).toBe(testUser.id);
+            expect(merchant.tenantId).toBe(1);
+          });
+        }
+      }
+    });
+  });
+
+  describe('商户状态管理测试', () => {
+    it('应该支持商户状态管理', async () => {
+      // 创建启用状态的商户
+      const createData = {
+        name: '状态测试商户',
+        username: `stm_${Date.now()}`,
+        password: 'password123',
+        phone: '13800138007',
+        realname: '状态测试',
+        state: 1, // 启用
+        tenantId: 1
+      };
+
+      const createResponse = await client.index.$post({
+        json: createData
+      }, {
+        headers: {
+          'Authorization': `Bearer ${adminToken}`
+        }
+      });
+
+      expect(createResponse.status).toBe(201);
+      if (createResponse.status === 201) {
+        const createdMerchant = await createResponse.json();
+        expect(createdMerchant.state).toBe(1);
+
+        // 更新为禁用状态
+        const updateResponse = await client[':id'].$put({
+          param: { id: createdMerchant.id },
+          json: { state: 2 } // 禁用
+        }, {
+          headers: {
+            'Authorization': `Bearer ${adminToken}`
+          }
+        });
+
+        expect(updateResponse.status).toBe(200);
+        if (updateResponse.status === 200) {
+          const updatedMerchant = await updateResponse.json();
+          expect(updatedMerchant.state).toBe(2);
+        }
+      }
+    });
+  });
+
+  describe('商户登录统计功能测试', () => {
+    it('应该支持商户登录统计字段', async () => {
+      // 创建商户
+      const createData = {
+        name: '登录统计商户',
+        username: `lsm_${Date.now()}`,
+        password: 'password123',
+        phone: '13800138008',
+        realname: '登录统计',
+        state: 1,
+        tenantId: 1
+      };
+
+      const createResponse = await client.index.$post({
+        json: createData
+      }, {
+        headers: {
+          'Authorization': `Bearer ${adminToken}`
+        }
+      });
+
+      expect(createResponse.status).toBe(201);
+      if (createResponse.status === 201) {
+        const createdMerchant = await createResponse.json();
+
+        // 验证登录统计字段存在
+        expect(createdMerchant).toHaveProperty('loginNum');
+        expect(createdMerchant).toHaveProperty('loginTime');
+        expect(createdMerchant).toHaveProperty('loginIp');
+        expect(createdMerchant).toHaveProperty('lastLoginTime');
+        expect(createdMerchant).toHaveProperty('lastLoginIp');
+
+        // 初始值应该为0或null
+        expect(createdMerchant.loginNum).toBe(0);
+        expect(createdMerchant.loginTime).toBe(0);
+        expect(createdMerchant.lastLoginTime).toBe(0);
+      }
+    });
+  });
+
+  describe('跨租户商户访问安全验证', () => {
+    let tenant2User: UserEntityMt;
+    let tenant2AdminToken: string;
+
+    beforeEach(async () => {
+      // 创建租户2的用户
+      tenant2User = await MerchantTestUtils.createTestUser({
+        username: `tenant2_user_${Date.now()}`,
+        nickname: '租户2用户',
+        tenantId: 2
+      });
+
+      // 生成租户2管理员的token
+      tenant2AdminToken = JWTUtil.generateToken({
+        id: tenant2User.id,
+        username: tenant2User.username,
+        roles: [{name:'admin'}],
+        tenantId: 2
+      });
+    });
+
+    it('管理员应该只能访问自己租户的商户', async () => {
+      // 为两个租户创建商户
+      const tenant1Merchant = await MerchantTestUtils.createTestMerchant(testUser.id, 1);
+      const tenant2Merchant = await MerchantTestUtils.createTestMerchant(tenant2User.id, 2);
+
+      // 租户1管理员应该只能看到租户1的商户
+      const tenant1Response = await client.index.$get({
+        query: {}
+      }, {
+        headers: {
+          'Authorization': `Bearer ${adminToken}`
+        }
+      });
+
+      expect(tenant1Response.status).toBe(200);
+      const tenant1Data = await tenant1Response.json();
+      if (tenant1Data && 'data' in tenant1Data) {
+        expect(Array.isArray(tenant1Data.data)).toBe(true);
+        // 应该只包含租户1的商户
+        tenant1Data.data.forEach((merchant: any) => {
+          expect(merchant.tenantId).toBe(1);
+        });
+      }
+
+      // 租户2管理员应该只能看到租户2的商户
+      const tenant2Response = await client.index.$get({
+        query: {}
+      }, {
+        headers: {
+          'Authorization': `Bearer ${tenant2AdminToken}`
+        }
+      });
+
+      expect(tenant2Response.status).toBe(200);
+      const tenant2Data = await tenant2Response.json();
+      if (tenant2Data && 'data' in tenant2Data) {
+        expect(Array.isArray(tenant2Data.data)).toBe(true);
+        // 应该只包含租户2的商户
+        tenant2Data.data.forEach((merchant: any) => {
+          expect(merchant.tenantId).toBe(2);
+        });
+      }
+    });
+
+    it('管理员应该无法访问其他租户的商户详情', async () => {
+      // 为租户2创建商户
+      const tenant2Merchant = await MerchantTestUtils.createTestMerchant(tenant2User.id, 2);
+
+      // 租户1管理员尝试访问租户2的商户
+      const response = await client[':id'].$get({
+        param: { id: tenant2Merchant.id }
+      }, {
+        headers: {
+          'Authorization': `Bearer ${adminToken}`
+        }
+      });
+
+      console.debug('跨租户管理员访问商户详情响应状态:', response.status);
+      expect(response.status).toBe(404); // 应该返回404,而不是403
+    });
+
+    it('管理员应该无法更新其他租户的商户', async () => {
+      // 为租户2创建商户
+      const tenant2Merchant = await MerchantTestUtils.createTestMerchant(tenant2User.id, 2);
+
+      const updateData = {
+        name: '尝试跨租户更新',
+        phone: '13900139011',
+        realname: '尝试跨租户更新'
+      };
+
+      // 租户1管理员尝试更新租户2的商户
+      const response = await client[':id'].$put({
+        param: { id: tenant2Merchant.id },
+        json: updateData
+      }, {
+        headers: {
+          'Authorization': `Bearer ${adminToken}`
+        }
+      });
+
+      console.debug('跨租户管理员更新商户响应状态:', response.status);
+      expect(response.status).toBe(404); // 应该返回404,而不是403
+    });
+
+    it('管理员应该无法删除其他租户的商户', async () => {
+      // 为租户2创建商户
+      const tenant2Merchant = await MerchantTestUtils.createTestMerchant(tenant2User.id, 2);
+
+      // 租户1管理员尝试删除租户2的商户
+      const response = await client[':id'].$delete({
+        param: { id: tenant2Merchant.id }
+      }, {
+        headers: {
+          'Authorization': `Bearer ${adminToken}`
+        }
+      });
+
+      console.debug('跨租户管理员删除商户响应状态:', response.status);
+      expect(response.status).toBe(404); // 应该返回404,而不是403
+    });
+  });
+});

+ 586 - 0
packages/merchant-module-mt/tests/integration/user-routes.integration.test.ts

@@ -0,0 +1,586 @@
+import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
+import { testClient } from 'hono/testing';
+import { IntegrationTestDatabase, setupIntegrationDatabaseHooksWithEntities } from '@d8d/shared-test-util';
+import { JWTUtil } from '@d8d/shared-utils';
+import { UserEntityMt, RoleMt } from '@d8d/user-module-mt';
+import { FileMt } from '@d8d/file-module-mt';
+import { userMerchantRoutes } from '../../src/routes/user-routes.mt';
+import { MerchantMt } from '../../src/entities/merchant.mt.entity';
+import { MerchantTestUtils } from '../utils/test-utils';
+
+// 设置集成测试钩子
+setupIntegrationDatabaseHooksWithEntities([UserEntityMt, RoleMt, MerchantMt, FileMt])
+
+describe('用户商户管理API集成测试', () => {
+  let client: ReturnType<typeof testClient<typeof userMerchantRoutes>>;
+  let userToken: string;
+  let otherUserToken: string;
+  let testUser: UserEntityMt;
+  let otherUser: UserEntityMt;
+
+  beforeEach(async () => {
+    // 创建测试客户端
+    client = testClient(userMerchantRoutes);
+
+    // 创建测试用户
+    testUser = await MerchantTestUtils.createTestUser({ tenantId: 1 });
+    otherUser = await MerchantTestUtils.createTestUser({
+      username: `other_user_${Date.now()}`,
+      nickname: '其他用户',
+      tenantId: 1
+    });
+
+    // 生成测试用户的token
+    userToken = JWTUtil.generateToken({
+      id: testUser.id,
+      username: testUser.username,
+      roles: [{name:'user'}],
+      tenantId: 1
+    });
+
+    // 生成其他用户的token
+    otherUserToken = JWTUtil.generateToken({
+      id: otherUser.id,
+      username: otherUser.username,
+      roles: [{name:'user'}],
+      tenantId: 1
+    });
+  });
+
+  describe('GET /merchants', () => {
+    it('应该返回当前用户的商户列表', async () => {
+      // 使用测试工具创建商户数据
+      const { userMerchants, otherUserMerchants } = await MerchantTestUtils.createTestMerchantDataSet(
+        testUser.id,
+        otherUser.id,
+        1
+      );
+
+      const response = await client.index.$get({
+        query: {}
+      }, {
+        headers: {
+          'Authorization': `Bearer ${userToken}`
+        }
+      });
+
+      console.debug('用户商户列表响应状态:', response.status);
+      expect(response.status).toBe(200);
+
+      if (response.status === 200) {
+        const data = await response.json();
+        if (data && 'data' in data) {
+          expect(Array.isArray(data.data)).toBe(true);
+          // 应该只返回当前用户的商户
+          data.data.forEach((merchant: any) => {
+            expect(merchant.createdBy).toBe(testUser.id);
+            expect(merchant.tenantId).toBe(1);
+          });
+        }
+      }
+    });
+
+    it('应该拒绝未认证用户的访问', async () => {
+      const response = await client.index.$get({
+        query: {}
+      });
+      expect(response.status).toBe(401);
+    });
+  });
+
+  describe('POST /merchants', () => {
+    it('应该成功创建商户并自动使用当前用户ID', async () => {
+      const createData = {
+        name: '新商户',
+        username: `new_${Date.now()}`,
+        password: 'password123',
+        phone: '13800138000',
+        realname: '张三',
+        state: 1,
+        tenantId: 1
+      };
+
+      const response = await client.index.$post({
+        json: createData
+      }, {
+        headers: {
+          'Authorization': `Bearer ${userToken}`
+        }
+      });
+
+      console.debug('用户创建商户响应状态:', response.status);
+      expect(response.status).toBe(201);
+
+      if (response.status === 201) {
+        const data = await response.json();
+        console.debug('用户创建商户响应数据:', JSON.stringify(data, null, 2));
+        expect(data).toHaveProperty('id');
+        expect(data.createdBy).toBe(testUser.id); // 自动使用当前用户ID
+        expect(data.tenantId).toBe(1); // 自动使用租户ID
+        expect(data.name).toBe(createData.name);
+        expect(data.username).toBe(createData.username);
+        expect(data.phone).toBe(createData.phone);
+        expect(data.realname).toBe(createData.realname);
+      }
+    });
+
+    it('应该验证创建商户的必填字段', async () => {
+      const invalidData = {
+        // 缺少必填字段
+        name: '',
+        username: '',
+        password: ''
+      };
+
+      const response = await client.index.$post({
+        json: invalidData
+      }, {
+        headers: {
+          'Authorization': `Bearer ${userToken}`
+        }
+      });
+
+      expect(response.status).toBe(400);
+    });
+  });
+
+  describe('GET /merchants/:id', () => {
+    it('应该返回当前用户的商户详情', async () => {
+      // 使用测试工具创建商户
+      const testMerchant = await MerchantTestUtils.createTestMerchant(testUser.id, 1);
+
+      const response = await client[':id'].$get({
+        param: { id: testMerchant.id }
+      }, {
+        headers: {
+          'Authorization': `Bearer ${userToken}`
+        }
+      });
+
+      console.debug('用户商户详情响应状态:', response.status);
+      expect(response.status).toBe(200);
+
+      if (response.status === 200) {
+        const data = await response.json();
+        expect(data.id).toBe(testMerchant.id);
+        expect(data.createdBy).toBe(testUser.id);
+        expect(data.tenantId).toBe(1);
+        expect(data.name).toBe(testMerchant.name);
+        expect(data.username).toBe(testMerchant.username);
+        expect(data.phone).toBe(testMerchant.phone);
+        expect(data.realname).toBe(testMerchant.realname);
+      }
+    });
+
+    it('应该拒绝访问其他用户的商户', async () => {
+      // 为其他用户创建商户
+      const otherUserMerchant = await MerchantTestUtils.createTestMerchant(otherUser.id, 1);
+
+      // 当前用户尝试访问其他用户的商户
+      const response = await client[':id'].$get({
+        param: { id: otherUserMerchant.id }
+      }, {
+        headers: {
+          'Authorization': `Bearer ${userToken}`
+        }
+      });
+
+      console.debug('用户访问其他用户商户响应状态:', response.status);
+      expect(response.status).toBe(404); // 应该返回404,而不是403
+    });
+
+    it('应该处理不存在的商户', async () => {
+      const response = await client[':id'].$get({
+        param: { id: 999999 }
+      }, {
+        headers: {
+          'Authorization': `Bearer ${userToken}`
+        }
+      });
+
+      expect(response.status).toBe(404);
+    });
+  });
+
+  describe('PUT /merchants/:id', () => {
+    it('应该成功更新当前用户的商户', async () => {
+      // 使用测试工具创建商户
+      const testMerchant = await MerchantTestUtils.createTestMerchant(testUser.id, 1);
+
+      const updateData = {
+        name: '更新后的商户',
+        phone: '13900139000',
+        realname: '更新后的姓名',
+        state: 2
+      };
+
+      const response = await client[':id'].$put({
+        param: { id: testMerchant.id },
+        json: updateData
+      }, {
+        headers: {
+          'Authorization': `Bearer ${userToken}`
+        }
+      });
+
+      console.debug('用户更新商户响应状态:', response.status);
+      expect(response.status).toBe(200);
+
+      if (response.status === 200) {
+        const data = await response.json();
+        expect(data.name).toBe(updateData.name);
+        expect(data.phone).toBe(updateData.phone);
+        expect(data.realname).toBe(updateData.realname);
+        expect(data.state).toBe(updateData.state);
+      }
+    });
+
+    it('应该拒绝更新其他用户的商户', async () => {
+      // 为其他用户创建商户
+      const otherUserMerchant = await MerchantTestUtils.createTestMerchant(otherUser.id, 1);
+
+      const updateData = {
+        name: '尝试更新的商户',
+        phone: '13900139001',
+        realname: '尝试更新的姓名'
+      };
+
+      // 当前用户尝试更新其他用户的商户
+      const response = await client[':id'].$put({
+        param: { id: otherUserMerchant.id },
+        json: updateData
+      }, {
+        headers: {
+          'Authorization': `Bearer ${userToken}`
+        }
+      });
+
+      console.debug('用户更新其他用户商户响应状态:', response.status);
+      expect(response.status).toBe(403); // 数据权限控制返回403
+    });
+  });
+
+  describe('DELETE /merchants/:id', () => {
+    it('应该成功删除当前用户的商户', async () => {
+      // 使用测试工具创建商户
+      const testMerchant = await MerchantTestUtils.createTestMerchant(testUser.id, 1);
+
+      const response = await client[':id'].$delete({
+        param: { id: testMerchant.id }
+      }, {
+        headers: {
+          'Authorization': `Bearer ${userToken}`
+        }
+      });
+
+      console.debug('用户删除商户响应状态:', response.status);
+      expect(response.status).toBe(204);
+
+      // 验证商户确实被删除
+      const dataSource = await IntegrationTestDatabase.getDataSource();
+      const merchantRepository = dataSource.getRepository(MerchantMt);
+      const deletedMerchant = await merchantRepository.findOne({
+        where: { id: testMerchant.id }
+      });
+      expect(deletedMerchant).toBeNull();
+    });
+
+    it('应该拒绝删除其他用户的商户', async () => {
+      // 为其他用户创建商户
+      const otherUserMerchant = await MerchantTestUtils.createTestMerchant(otherUser.id, 1);
+
+      // 当前用户尝试删除其他用户的商户
+      const response = await client[':id'].$delete({
+        param: { id: otherUserMerchant.id }
+      }, {
+        headers: {
+          'Authorization': `Bearer ${userToken}`
+        }
+      });
+
+      console.debug('用户删除其他用户商户响应状态:', response.status);
+      expect(response.status).toBe(403); // 数据权限控制返回403
+    });
+  });
+
+  describe('数据权限验证', () => {
+    it('用户应该只能访问和操作自己的数据', async () => {
+      // 使用测试工具创建商户数据集
+      const { userMerchants, otherUserMerchants } = await MerchantTestUtils.createTestMerchantDataSet(
+        testUser.id,
+        otherUser.id,
+        1
+      );
+
+      // 当前用户应该只能看到自己的商户
+      const listResponse = await client.index.$get({
+        query: {}
+      }, {
+        headers: {
+          'Authorization': `Bearer ${userToken}`
+        }
+      });
+
+      expect(listResponse.status).toBe(200);
+      const listData = await listResponse.json();
+      if (listData && 'data' in listData) {
+        expect(Array.isArray(listData.data)).toBe(true);
+        // 应该只包含当前用户的商户
+        listData.data.forEach((merchant: any) => {
+          expect(merchant.createdBy).toBe(testUser.id);
+        });
+      }
+
+      // 当前用户应该无法访问其他用户的商户详情
+      const getResponse = await client[':id'].$get({
+        param: { id: otherUserMerchants[0].id }
+      }, {
+        headers: {
+          'Authorization': `Bearer ${userToken}`
+        }
+      });
+      expect(getResponse.status).toBe(404);
+
+      // 当前用户应该无法更新其他用户的商户
+      const updateResponse = await client[':id'].$put({
+        param: { id: otherUserMerchants[0].id },
+        json: { name: '尝试更新' }
+      }, {
+        headers: {
+          'Authorization': `Bearer ${userToken}`
+        }
+      });
+      expect(updateResponse.status).toBe(403);
+
+      // 当前用户应该无法删除其他用户的商户
+      const deleteResponse = await client[':id'].$delete({
+        param: { id: otherUserMerchants[0].id }
+      }, {
+        headers: {
+          'Authorization': `Bearer ${userToken}`
+        }
+      });
+      expect(deleteResponse.status).toBe(403);
+    });
+  });
+
+  describe('商户状态管理测试', () => {
+    it('应该支持商户状态管理', async () => {
+      // 创建启用状态的商户
+      const createData = {
+        name: '状态测试商户',
+        username: `stm_${Date.now()}`,
+        password: 'password123',
+        phone: '13800138006',
+        realname: '状态测试',
+        state: 1, // 启用
+        tenantId: 1
+      };
+
+      const createResponse = await client.index.$post({
+        json: createData
+      }, {
+        headers: {
+          'Authorization': `Bearer ${userToken}`
+        }
+      });
+
+      expect(createResponse.status).toBe(201);
+      const createdMerchant = await createResponse.json();
+
+      // 检查响应是否为错误对象
+      if ('code' in createdMerchant && 'message' in createdMerchant) {
+        throw new Error(`创建商户失败: ${createdMerchant.message}`);
+      }
+
+      expect(createdMerchant.state).toBe(1);
+
+      // 更新为禁用状态
+      const updateResponse = await client[':id'].$put({
+        param: { id: createdMerchant.id },
+        json: { state: 2 } // 禁用
+      }, {
+        headers: {
+          'Authorization': `Bearer ${userToken}`
+        }
+      });
+
+      expect(updateResponse.status).toBe(200);
+      const updatedMerchant = await updateResponse.json();
+
+      // 检查响应是否为错误对象
+      if ('code' in updatedMerchant && 'message' in updatedMerchant) {
+        throw new Error(`更新商户失败: ${updatedMerchant.message}`);
+      }
+
+      expect(updatedMerchant.state).toBe(2);
+    });
+  });
+
+  describe('商户登录统计功能测试', () => {
+    it('应该支持商户登录统计字段', async () => {
+      // 创建商户
+      const createData = {
+        name: '登录统计商户',
+        username: `lsm_${Date.now()}`,
+        password: 'password123',
+        phone: '13800138007',
+        realname: '登录统计',
+        state: 1,
+        tenantId: 1
+      };
+
+      const createResponse = await client.index.$post({
+        json: createData
+      }, {
+        headers: {
+          'Authorization': `Bearer ${userToken}`
+        }
+      });
+
+      expect(createResponse.status).toBe(201);
+      const createdMerchant = await createResponse.json();
+
+      // 检查响应是否为错误对象
+      if ('code' in createdMerchant && 'message' in createdMerchant) {
+        throw new Error(`创建商户失败: ${createdMerchant.message}`);
+      }
+
+      // 验证登录统计字段存在
+      expect(createdMerchant).toHaveProperty('loginNum');
+      expect(createdMerchant).toHaveProperty('loginTime');
+      expect(createdMerchant).toHaveProperty('loginIp');
+      expect(createdMerchant).toHaveProperty('lastLoginTime');
+      expect(createdMerchant).toHaveProperty('lastLoginIp');
+
+      // 初始值应该为0或null
+      expect(createdMerchant.loginNum).toBe(0);
+      expect(createdMerchant.loginTime).toBe(0);
+      expect(createdMerchant.lastLoginTime).toBe(0);
+    });
+  });
+
+  describe('租户隔离测试', () => {
+    let tenant2UserToken: string;
+    let tenant2User: UserEntityMt;
+
+    beforeEach(async () => {
+      // 创建租户2的用户
+      tenant2User = await MerchantTestUtils.createTestUser({
+        username: `tenant2_user_${Date.now()}`,
+        nickname: '租户2用户',
+        tenantId: 2
+      });
+
+      // 生成租户2用户的token
+      tenant2UserToken = JWTUtil.generateToken({
+        id: tenant2User.id,
+        username: tenant2User.username,
+        roles: [{name:'user'}],
+        tenantId: 2
+      });
+    });
+
+    it('应该隔离不同租户的商户数据', async () => {
+      // 为两个租户创建商户
+      const tenant1Merchant = await MerchantTestUtils.createTestMerchant(testUser.id, 1);
+      const tenant2Merchant = await MerchantTestUtils.createTestMerchant(tenant2User.id, 2);
+
+      // 租户1用户应该只能看到租户1的商户
+      const tenant1Response = await client.index.$get({
+        query: {}
+      }, {
+        headers: {
+          'Authorization': `Bearer ${userToken}`
+        }
+      });
+
+      expect(tenant1Response.status).toBe(200);
+      const tenant1Data = await tenant1Response.json();
+      if (tenant1Data && 'data' in tenant1Data) {
+        expect(Array.isArray(tenant1Data.data)).toBe(true);
+        // 应该只包含租户1的商户
+        tenant1Data.data.forEach((merchant: any) => {
+          expect(merchant.tenantId).toBe(1);
+        });
+      }
+
+      // 租户2用户应该只能看到租户2的商户
+      const tenant2Response = await client.index.$get({
+        query: {}
+      }, {
+        headers: {
+          'Authorization': `Bearer ${tenant2UserToken}`
+        }
+      });
+
+      expect(tenant2Response.status).toBe(200);
+      const tenant2Data = await tenant2Response.json();
+      if (tenant2Data && 'data' in tenant2Data) {
+        expect(Array.isArray(tenant2Data.data)).toBe(true);
+        // 应该只包含租户2的商户
+        tenant2Data.data.forEach((merchant: any) => {
+          expect(merchant.tenantId).toBe(2);
+        });
+      }
+    });
+
+    it('应该拒绝跨租户访问商户详情', async () => {
+      // 为租户2创建商户
+      const tenant2Merchant = await MerchantTestUtils.createTestMerchant(tenant2User.id, 2);
+
+      // 租户1用户尝试访问租户2的商户
+      const response = await client[':id'].$get({
+        param: { id: tenant2Merchant.id }
+      }, {
+        headers: {
+          'Authorization': `Bearer ${userToken}`
+        }
+      });
+
+      console.debug('跨租户访问商户详情响应状态:', response.status);
+      expect(response.status).toBe(404); // 应该返回404,而不是403
+    });
+
+    it('应该拒绝跨租户更新商户', async () => {
+      // 为租户2创建商户
+      const tenant2Merchant = await MerchantTestUtils.createTestMerchant(tenant2User.id, 2);
+
+      const updateData = {
+        name: '尝试跨租户更新',
+        phone: '13900139011',
+        realname: '尝试跨租户更新'
+      };
+
+      // 租户1用户尝试更新租户2的商户
+      const response = await client[':id'].$put({
+        param: { id: tenant2Merchant.id },
+        json: updateData
+      }, {
+        headers: {
+          'Authorization': `Bearer ${userToken}`
+        }
+      });
+
+      console.debug('跨租户更新商户响应状态:', response.status);
+      expect(response.status).toBe(404); // 应该返回404,而不是403
+    });
+
+    it('应该拒绝跨租户删除商户', async () => {
+      // 为租户2创建商户
+      const tenant2Merchant = await MerchantTestUtils.createTestMerchant(tenant2User.id, 2);
+
+      // 租户1用户尝试删除租户2的商户
+      const response = await client[':id'].$delete({
+        param: { id: tenant2Merchant.id }
+      }, {
+        headers: {
+          'Authorization': `Bearer ${userToken}`
+        }
+      });
+
+      console.debug('跨租户删除商户响应状态:', response.status);
+      expect(response.status).toBe(404); // 应该返回404,而不是403
+    });
+  });
+});

+ 122 - 0
packages/merchant-module-mt/tests/utils/test-utils.ts

@@ -0,0 +1,122 @@
+import { IntegrationTestDatabase } from '@d8d/shared-test-util';
+import { UserEntityMt } from '@d8d/user-module-mt';
+import { MerchantMt } from '../../src/entities/merchant.mt.entity';
+
+/**
+ * 商户模块测试工具类
+ * 避免依赖共享测试数据工厂的复杂依赖关系
+ */
+export class MerchantTestUtils {
+  /**
+   * 创建测试用户
+   */
+  static async createTestUser(overrides: Partial<any> = {}): Promise<UserEntityMt> {
+    const dataSource = await IntegrationTestDatabase.getDataSource();
+    const userRepository = dataSource.getRepository(UserEntityMt);
+
+    const defaultUser = {
+      username: `test_user_${Date.now()}`,
+      password: 'test_password',
+      nickname: '测试用户',
+      registrationSource: 'web',
+      tenantId: 1
+    };
+
+    const userData = { ...defaultUser, ...overrides };
+    const user = userRepository.create(userData);
+    return await userRepository.save(user);
+  }
+
+  /**
+   * 创建测试商户
+   */
+  static async createTestMerchant(
+    createdBy: number,
+    tenantId: number = 1,
+    overrides: Partial<MerchantMt> = {}
+  ): Promise<MerchantMt> {
+    const dataSource = await IntegrationTestDatabase.getDataSource();
+    const merchantRepository = dataSource.getRepository(MerchantMt);
+
+    const timestamp = Date.now();
+    const defaultMerchant = {
+      name: `测试商户_${timestamp}`,
+      username: `test_m_${timestamp.toString().slice(-8)}`,
+      password: 'password123',
+      phone: `13800138${timestamp.toString().slice(-4, -2)}`,
+      realname: '测试商户',
+      state: 1,
+      loginNum: 0,
+      loginTime: 0,
+      loginIp: '',
+      lastLoginTime: 0,
+      lastLoginIp: '',
+      rsaPublicKey: '',
+      aesKey: '',
+      tenantId,
+      createdBy
+    };
+
+    const merchantData = { ...defaultMerchant, ...overrides };
+    const merchant = merchantRepository.create(merchantData);
+    return await merchantRepository.save(merchant);
+  }
+
+  /**
+   * 创建多个测试商户
+   */
+  static async createTestMerchants(
+    createdBy: number,
+    count: number,
+    tenantId: number = 1,
+    overrides: Partial<MerchantMt> = {}
+  ): Promise<MerchantMt[]> {
+    const merchants: MerchantMt[] = [];
+    for (let i = 0; i < count; i++) {
+      const merchant = await this.createTestMerchant(createdBy, tenantId, {
+        ...overrides,
+        name: `测试商户_${i + 1}`,
+        username: `test_m${i + 1}_${Date.now().toString().slice(-6)}`
+      });
+      merchants.push(merchant);
+    }
+    return merchants;
+  }
+
+  /**
+   * 创建测试商户数据集
+   */
+  static async createTestMerchantDataSet(
+    userId: number,
+    otherUserId: number,
+    tenantId: number = 1
+  ): Promise<{
+    userMerchants: MerchantMt[];
+    otherUserMerchants: MerchantMt[];
+  }> {
+    const userMerchants = await this.createTestMerchants(userId, 2, tenantId, {
+      name: '用户商户'
+    });
+
+    const otherUserMerchants = await this.createTestMerchants(otherUserId, 1, tenantId, {
+      name: '其他用户商户'
+    });
+
+    return {
+      userMerchants,
+      otherUserMerchants
+    };
+  }
+
+  /**
+   * 清理测试数据
+   */
+  static async cleanupTestData(): Promise<void> {
+    const dataSource = await IntegrationTestDatabase.getDataSource();
+    const merchantRepository = dataSource.getRepository(MerchantMt);
+    const userRepository = dataSource.getRepository(UserEntityMt);
+
+    await merchantRepository.clear();
+    await userRepository.clear();
+  }
+}

+ 16 - 0
packages/merchant-module-mt/tsconfig.json

@@ -0,0 +1,16 @@
+{
+  "extends": "../../tsconfig.json",
+  "compilerOptions": {
+    "composite": true,
+    "rootDir": ".",
+    "outDir": "dist"
+  },
+  "include": [
+    "src/**/*",
+    "tests/**/*"
+  ],
+  "exclude": [
+    "node_modules",
+    "dist"
+  ]
+}

+ 21 - 0
packages/merchant-module-mt/vitest.config.ts

@@ -0,0 +1,21 @@
+import { defineConfig } from 'vitest/config';
+
+export default defineConfig({
+  test: {
+    globals: true,
+    environment: 'node',
+    include: ['tests/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
+    coverage: {
+      provider: 'v8',
+      reporter: ['text', 'json', 'html'],
+      exclude: [
+        'tests/**',
+        '**/*.d.ts',
+        '**/*.config.*',
+        '**/dist/**'
+      ]
+    },
+    // 关闭并行测试以避免数据库连接冲突
+    fileParallelism: false
+  }
+});