瀏覽代碼

♻️ refactor(membership): 分离会员套餐的schema与entity

- 将MembershipPlanSchema、CreateMembershipPlanDto和UpdateMembershipPlanDto从entity文件移至独立的membership-plan.schema.ts
- 优化schema定义,增加字段验证规则和错误提示
- 调整导入路径,确保类型引用正确

🔒 fix(membership): 恢复会员套餐路由的认证中间件

- 重新添加authMiddleware到membershipPlanRoutes中间件配置
- 修复安全漏洞,确保会员套餐接口受认证保护
yourname 4 月之前
父節點
當前提交
48c0fdc77f

+ 2 - 2
src/server/api/membership-plans/index.ts

@@ -1,6 +1,6 @@
 import { createCrudRoutes } from '@/server/utils/generic-crud.routes';
 import { createCrudRoutes } from '@/server/utils/generic-crud.routes';
 import { MembershipPlan } from '@/server/modules/membership/membership-plan.entity';
 import { MembershipPlan } from '@/server/modules/membership/membership-plan.entity';
-import { MembershipPlanSchema, CreateMembershipPlanDto, UpdateMembershipPlanDto } from '@/server/modules/membership/membership-plan.entity';
+import { MembershipPlanSchema, CreateMembershipPlanDto, UpdateMembershipPlanDto } from '@/server/modules/membership/membership-plan.schema';
 import { authMiddleware } from '@/server/middleware/auth.middleware';
 import { authMiddleware } from '@/server/middleware/auth.middleware';
 
 
 const membershipPlanRoutes = createCrudRoutes({
 const membershipPlanRoutes = createCrudRoutes({
@@ -10,7 +10,7 @@ const membershipPlanRoutes = createCrudRoutes({
   getSchema: MembershipPlanSchema,
   getSchema: MembershipPlanSchema,
   listSchema: MembershipPlanSchema,
   listSchema: MembershipPlanSchema,
   searchFields: ['name', 'description'],
   searchFields: ['name', 'description'],
-  middleware: [] // 移除认证中间件,允许公开访问
+  middleware: [ authMiddleware ]
 });
 });
 
 
 export default membershipPlanRoutes;
 export default membershipPlanRoutes;

+ 1 - 45
src/server/modules/membership/membership-plan.entity.ts

@@ -1,12 +1,5 @@
 import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm';
 import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm';
-import { z } from '@hono/zod-openapi';
-
-export enum MembershipType {
-  SINGLE = 'single',
-  MONTHLY = 'monthly',
-  YEARLY = 'yearly',
-  LIFETIME = 'lifetime'
-}
+import { MembershipType } from './membership-plan.schema';
 
 
 @Entity('membership_plans')
 @Entity('membership_plans')
 export class MembershipPlan {
 export class MembershipPlan {
@@ -48,40 +41,3 @@ export class MembershipPlan {
   @UpdateDateColumn({ name: 'updated_at' })
   @UpdateDateColumn({ name: 'updated_at' })
   updatedAt!: Date;
   updatedAt!: Date;
 }
 }
-
-// Zod Schema定义
-export const MembershipPlanSchema = z.object({
-  id: z.number().int().positive().openapi({ description: '套餐ID', example: 1 }),
-  name: z.string().max(100).openapi({ description: '套餐名称', example: '单次会员' }),
-  type: z.nativeEnum(MembershipType).openapi({ description: '套餐类型', example: MembershipType.SINGLE }),
-  price: z.number().multipleOf(0.01).openapi({ description: '价格', example: 27.00 }),
-  durationDays: z.number().int().min(0).openapi({ description: '有效期天数', example: 1 }),
-  description: z.string().nullable().openapi({ description: '套餐描述', example: '24小时有效' }),
-  features: z.array(z.string()).openapi({ description: '套餐功能', example: ['基础功能', '24小时使用'] }),
-  isActive: z.number().int().min(0).max(1).openapi({ description: '是否启用', example: 1 }),
-  sortOrder: z.number().int().openapi({ description: '排序', example: 0 }),
-  createdAt: z.date().openapi({ description: '创建时间' }),
-  updatedAt: z.date().openapi({ description: '更新时间' })
-});
-
-export const CreateMembershipPlanDto = z.object({
-  name: z.string().max(100).openapi({ description: '套餐名称', example: '单次会员' }),
-  type: z.nativeEnum(MembershipType).openapi({ description: '套餐类型', example: MembershipType.SINGLE }),
-  price: z.number().multipleOf(0.01).positive().openapi({ description: '价格', example: 27.00 }),
-  durationDays: z.number().int().min(0).openapi({ description: '有效期天数', example: 1 }),
-  description: z.string().nullable().optional().openapi({ description: '套餐描述', example: '24小时有效' }),
-  features: z.array(z.string()).optional().default([]).openapi({ description: '套餐功能', example: ['基础功能'] }),
-  isActive: z.number().int().min(0).max(1).optional().default(1).openapi({ description: '是否启用', example: 1 }),
-  sortOrder: z.number().int().optional().default(0).openapi({ description: '排序', example: 0 })
-});
-
-export const UpdateMembershipPlanDto = z.object({
-  name: z.string().max(100).optional().openapi({ description: '套餐名称', example: '单次会员' }),
-  type: z.nativeEnum(MembershipType).optional().openapi({ description: '套餐类型', example: MembershipType.SINGLE }),
-  price: z.number().multipleOf(0.01).positive().optional().openapi({ description: '价格', example: 27.00 }),
-  durationDays: z.number().int().min(0).optional().openapi({ description: '有效期天数', example: 1 }),
-  description: z.string().nullable().optional().openapi({ description: '套餐描述', example: '24小时有效' }),
-  features: z.array(z.string()).optional().openapi({ description: '套餐功能', example: ['基础功能'] }),
-  isActive: z.number().int().min(0).max(1).optional().openapi({ description: '是否启用', example: 1 }),
-  sortOrder: z.number().int().optional().openapi({ description: '排序', example: 0 })
-});

+ 128 - 0
src/server/modules/membership/membership-plan.schema.ts

@@ -0,0 +1,128 @@
+import { z } from '@hono/zod-openapi';
+
+export enum MembershipType {
+  SINGLE = 'single',
+  MONTHLY = 'monthly',
+  YEARLY = 'yearly',
+  LIFETIME = 'lifetime'
+}
+
+// 基础会员套餐schema
+export const MembershipPlanSchema = z.object({
+  id: z.number().int().positive().openapi({
+    description: '套餐ID',
+    example: 1
+  }),
+  name: z.string().min(1, '套餐名称不能为空').max(100, '套餐名称最多100个字符').openapi({
+    description: '套餐名称',
+    example: '月度会员'
+  }),
+  type: z.enum(MembershipType).openapi({
+    description: '套餐类型:single-单次,monthly-单月,yearly-年,lifetime-永久',
+    example: MembershipType.MONTHLY
+  }),
+  price: z.coerce.number().multipleOf(0.01, '价格最多保留两位小数').min(0, '价格不能小于0').max(999999.99, '价格不能超过999999.99').openapi({
+    description: '价格',
+    example: 29.99
+  }),
+  durationDays: z.coerce.number().int('有效期天数必须是整数').min(0, '有效期天数不能小于0').openapi({
+    description: '有效期天数(永久为0)',
+    example: 30
+  }),
+  description: z.string().max(1000, '套餐描述最多1000个字符').nullable().openapi({
+    description: '套餐描述',
+    example: '月度会员套餐,包含所有基础功能'
+  }),
+  features: z.array(z.string()).openapi({
+    description: '套餐功能列表',
+    example: ['无限制文档处理', '高级AI功能', '优先客服支持']
+  }),
+  isActive: z.coerce.number().int().min(0).max(1).default(1).openapi({
+    description: '是否启用(0-禁用,1-启用)',
+    example: 1
+  }),
+  sortOrder: z.coerce.number().int().default(0).openapi({
+    description: '排序',
+    example: 0
+  }),
+  createdAt: z.coerce.date().openapi({
+    description: '创建时间',
+    example: '2025-01-01T00:00:00Z'
+  }),
+  updatedAt: z.coerce.date().openapi({
+    description: '更新时间',
+    example: '2025-01-01T00:00:00Z'
+  })
+});
+
+// 创建会员套餐DTO
+export const CreateMembershipPlanDto = z.object({
+  name: z.string().min(1, '套餐名称不能为空').max(100, '套餐名称最多100个字符').openapi({
+    description: '套餐名称',
+    example: '月度会员'
+  }),
+  type: z.nativeEnum(MembershipType).openapi({
+    description: '套餐类型:single-单次,monthly-单月,yearly-年,lifetime-永久',
+    example: MembershipType.MONTHLY
+  }),
+  price: z.coerce.number().multipleOf(0.01, '价格最多保留两位小数').min(0.01, '价格必须大于0').max(999999.99, '价格不能超过999999.99').openapi({
+    description: '价格',
+    example: 29.99
+  }),
+  durationDays: z.coerce.number().int('有效期天数必须是整数').min(0, '有效期天数不能小于0').openapi({
+    description: '有效期天数(永久为0)',
+    example: 30
+  }),
+  description: z.string().max(1000, '套餐描述最多1000个字符').nullable().optional().openapi({
+    description: '套餐描述(选填)',
+    example: '月度会员套餐,包含所有基础功能'
+  }),
+  features: z.array(z.string()).default([]).openapi({
+    description: '套餐功能列表',
+    example: ['无限制文档处理', '高级AI功能', '优先客服支持']
+  }),
+  isActive: z.coerce.number().int().min(0).max(1).default(1).optional().openapi({
+    description: '是否启用(0-禁用,1-启用)',
+    example: 1
+  }),
+  sortOrder: z.coerce.number().int().default(0).optional().openapi({
+    description: '排序',
+    example: 0
+  })
+});
+
+// 更新会员套餐DTO
+export const UpdateMembershipPlanDto = z.object({
+  name: z.string().min(1, '套餐名称不能为空').max(100, '套餐名称最多100个字符').optional().openapi({
+    description: '套餐名称',
+    example: '更新后的月度会员'
+  }),
+  type: z.enum(MembershipType).optional().openapi({
+    description: '套餐类型:single-单次,monthly-单月,yearly-年,lifetime-永久',
+    example: MembershipType.YEARLY
+  }),
+  price: z.coerce.number().multipleOf(0.01, '价格最多保留两位小数').min(0.01, '价格必须大于0').max(999999.99, '价格不能超过999999.99').optional().openapi({
+    description: '价格',
+    example: 39.99
+  }),
+  durationDays: z.coerce.number().int('有效期天数必须是整数').min(0, '有效期天数不能小于0').optional().openapi({
+    description: '有效期天数(永久为0)',
+    example: 365
+  }),
+  description: z.string().max(1000, '套餐描述最多1000个字符').nullable().optional().openapi({
+    description: '套餐描述',
+    example: '更新后的套餐描述'
+  }),
+  features: z.array(z.string()).optional().openapi({
+    description: '套餐功能列表',
+    example: ['更新后的功能1', '更新后的功能2']
+  }),
+  isActive: z.coerce.number().int().min(0).max(1).optional().openapi({
+    description: '是否启用(0-禁用,1-启用)',
+    example: 1
+  }),
+  sortOrder: z.coerce.number().int().optional().openapi({
+    description: '排序',
+    example: 1
+  })
+});