ソースを参照

新增鱼类管理功能,包括创建、查询、更新和删除记录的接口;更新用户控制器以支持获取所有用户

wuyi 4 ヶ月 前
コミット
3950486c70

+ 0 - 14
.env.example

@@ -1,14 +0,0 @@
-PORT=3000
-HOST=0.0.0.0
-NODE_ENV=development
-
-# Database
-DB_HOST=localhost
-DB_PORT=3306
-DB_USERNAME=root
-DB_PASSWORD=root
-DB_DATABASE=robin_db
-
-# JWT
-JWT_SECRET=your-super-secret-key
-JWT_EXPIRES_IN=7d 

+ 2 - 1
package.json

@@ -36,5 +36,6 @@
     "pino-pretty": "^13.0.0",
     "ts-node-dev": "^2.0.0",
     "typescript": "^5.8.2"
-  }
+  },
+  "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
 }

+ 2 - 0
src/app.ts

@@ -11,6 +11,7 @@ import { createDataSource } from './config/database'
 import userRoutes from './routes/user.routes'
 import recordsRoutes from './routes/records.routes'
 import fileRoutes from './routes/file.routes'
+import fishRoutes from './routes/fish.routes'
 
 const options: FastifyEnvOptions = {
   schema: schema,
@@ -77,6 +78,7 @@ export const createApp = async () => {
   app.register(userRoutes, { prefix: '/api/users' })
   app.register(recordsRoutes, { prefix: '/api/records' })
   app.register(fileRoutes, { prefix: '/api/files' })
+  app.register(fishRoutes, { prefix: '/api/fish' })
 
   const dataSource = createDataSource(app)
   await dataSource.initialize()

+ 215 - 0
src/controllers/fish.controller.ts

@@ -0,0 +1,215 @@
+import { FastifyRequest, FastifyReply, FastifyInstance } from 'fastify'
+import { FishService } from '../services/fish.service'
+import { ResultEnum } from '../entities/fish.entity'
+import {
+  ListFishQuery,
+  CreateFishBody,
+  UpdateFishBody,
+  DeleteFishBody
+} from '../dto/fish.dto'
+
+export class FishController {
+  private fishService: FishService
+
+  constructor(app: FastifyInstance) {
+    this.fishService = new FishService(app)
+  }
+
+  async create(request: FastifyRequest<{ Body: CreateFishBody }>, reply: FastifyReply) {
+    try {
+      const fishData = request.body
+
+      // 检查是否已存在相同 ID 的记录
+      try {
+        await this.fishService.findById(fishData.id)
+        return reply.code(400).send({ message: '记录已存在' })
+      } catch (error) {
+        // 记录不存在,可以继续创建
+      }
+
+      const fish = await this.fishService.create(fishData)
+
+      return reply.code(201).send({
+        message: '创建成功',
+        fish: {
+          id: fish.id,
+          name: fish.name,
+          username: fish.username,
+          phone: fish.phone,
+          result: fish.result,
+          ownerId: fish.ownerId,
+          ownerName: fish.ownerName,
+          ip: fish.ip,
+          remark: fish.remark,
+          createdAt: fish.createdAt,
+          updatedAt: fish.updatedAt,
+          loginTime: fish.loginTime
+        }
+      })
+    } catch (error) {
+      console.error('创建记录失败:', error)
+      return reply.code(500).send({ message: '创建失败' })
+    }
+  }
+
+  async getById(request: FastifyRequest<{ Params: { id: string } }>, reply: FastifyReply) {
+    try {
+      const { id } = request.params
+      const fish = await this.fishService.findById(id)
+
+      return reply.send({
+        fish: {
+          id: fish.id,
+          name: fish.name,
+          username: fish.username,
+          phone: fish.phone,
+          password: fish.password,
+          result: fish.result,
+          ownerId: fish.ownerId,
+          ownerName: fish.ownerName,
+          ip: fish.ip,
+          token: fish.token,
+          session: fish.session,
+          remark: fish.remark,
+          createdAt: fish.createdAt,
+          updatedAt: fish.updatedAt,
+          loginTime: fish.loginTime
+        }
+      })
+    } catch (error) {
+      return reply.code(404).send({ message: '记录不存在' })
+    }
+  }
+
+  async list(request: FastifyRequest<{ Querystring: ListFishQuery }>, reply: FastifyReply) {
+    try {
+      const query = request.query
+      const result = await this.fishService.list(query)
+
+      return reply.send(result)
+    } catch (error) {
+      console.error('查询记录失败:', error)
+      return reply.code(500).send({ message: '查询失败' })
+    }
+  }
+
+  async update(request: FastifyRequest<{ Body: UpdateFishBody }>, reply: FastifyReply) {
+    try {
+      const { id, ...updateData } = request.body
+
+      // 检查记录是否存在
+      try {
+        await this.fishService.findById(id)
+      } catch (error) {
+        return reply.code(404).send({ message: '记录不存在' })
+      }
+
+      const updatedFish = await this.fishService.update(id, updateData)
+
+      return reply.send({
+        message: '更新成功',
+        fish: {
+          id: updatedFish.id,
+          name: updatedFish.name,
+          username: updatedFish.username,
+          phone: updatedFish.phone,
+          result: updatedFish.result,
+          ownerId: updatedFish.ownerId,
+          ownerName: updatedFish.ownerName,
+          ip: updatedFish.ip,
+          remark: updatedFish.remark,
+          createdAt: updatedFish.createdAt,
+          updatedAt: updatedFish.updatedAt,
+          loginTime: updatedFish.loginTime
+        }
+      })
+    } catch (error) {
+      console.error('更新记录失败:', error)
+      return reply.code(500).send({ message: '更新失败' })
+    }
+  }
+
+  async delete(request: FastifyRequest<{ Body: DeleteFishBody }>, reply: FastifyReply) {
+    try {
+      const { id } = request.body
+
+      // 检查记录是否存在
+      try {
+        await this.fishService.findById(id)
+      } catch (error) {
+        return reply.code(404).send({ message: '记录不存在' })
+      }
+
+      await this.fishService.delete(id)
+
+      return reply.send({ message: '删除成功' })
+    } catch (error) {
+      console.error('删除记录失败:', error)
+      return reply.code(500).send({ message: '删除失败' })
+    }
+  }
+
+  async batchDelete(request: FastifyRequest<{ Body: { ids: string[] } }>, reply: FastifyReply) {
+    try {
+      const { ids } = request.body
+
+      if (!ids || ids.length === 0) {
+        return reply.code(400).send({ message: '请提供要删除的记录ID' })
+      }
+
+      await this.fishService.batchDelete(ids)
+
+      return reply.send({ message: `成功删除 ${ids.length} 条记录` })
+    } catch (error) {
+      console.error('批量删除记录失败:', error)
+      return reply.code(500).send({ message: '批量删除失败' })
+    }
+  }
+
+  async getStatistics(request: FastifyRequest, reply: FastifyReply) {
+    try {
+      const statistics = await this.fishService.getStatistics()
+
+      return reply.send({
+        statistics
+      })
+    } catch (error) {
+      console.error('获取统计信息失败:', error)
+      return reply.code(500).send({ message: '获取统计信息失败' })
+    }
+  }
+
+  async findByOwnerId(request: FastifyRequest<{ Querystring: { ownerId: number } }>, reply: FastifyReply) {
+    try {
+      const { ownerId } = request.query
+      const fishList = await this.fishService.findByOwnerId(ownerId)
+
+      return reply.send({
+        fishList
+      })
+    } catch (error) {
+      console.error('按所有者ID查询失败:', error)
+      return reply.code(500).send({ message: '查询失败' })
+    }
+  }
+
+  async findByResult(request: FastifyRequest<{ Querystring: { result: string } }>, reply: FastifyReply) {
+    try {
+      const { result } = request.query
+      
+      // 验证 result 参数是否为有效的枚举值
+      if (!Object.values(ResultEnum).includes(result as ResultEnum)) {
+        return reply.code(400).send({ message: '无效的结果状态值' })
+      }
+      
+      const fishList = await this.fishService.findByResult(result as ResultEnum)
+
+      return reply.send({
+        fishList
+      })
+    } catch (error) {
+      console.error('按结果状态查询失败:', error)
+      return reply.code(500).send({ message: '查询失败' })
+    }
+  }
+}

+ 6 - 0
src/controllers/user.controller.ts

@@ -8,6 +8,7 @@ import {
   CreateUserBody,
   UpdateUserBody
 } from '../dto/user.dto'
+import { UserRole } from '../entities/user.entity'
 
 export class UserController {
   private userService: UserService
@@ -118,6 +119,11 @@ export class UserController {
     }
   }
 
+  async getAllUsers(request: FastifyRequest, reply: FastifyReply) {
+    const users = await this.userService.findAllUsers()
+    return reply.send(users)
+  }
+
   async createUser(request: FastifyRequest<{ Body: CreateUserBody }>, reply: FastifyReply) {
     try {
       const { password, name, role } = request.body

+ 51 - 0
src/dto/fish.dto.ts

@@ -0,0 +1,51 @@
+import { Pagination } from './common.dto'
+import { ResultEnum } from '../entities/fish.entity'
+
+export interface ListFishQuery extends Pagination {
+  id?: string
+  name?: string
+  username?: string
+  phone?: string
+  result?: ResultEnum
+  ownerId?: number
+  ownerName?: string
+  remark?: string
+  createdAt?: string // 日期格式: '2025-09-02'
+  loginTime?: string // 日期格式: '2025-09-02'
+}
+
+export interface CreateFishBody {
+  id: string
+  name?: string
+  username?: string
+  phone?: string
+  password?: string
+  result?: ResultEnum
+  ownerId?: number
+  ownerName?: string
+  ip?: string
+  token?: string
+  session?: string
+  remark?: string
+  loginTime: Date
+}
+
+export interface UpdateFishBody {
+  id: string
+  name?: string
+  username?: string
+  phone?: string
+  password?: string
+  result?: ResultEnum
+  ownerId?: number
+  ownerName?: string
+  ip?: string
+  token?: string
+  session?: string
+  remark?: string
+  loginTime?: Date
+}
+
+export interface DeleteFishBody {
+  id: string
+}

+ 53 - 0
src/entities/fish.entity.ts

@@ -0,0 +1,53 @@
+import { Entity, PrimaryGeneratedColumn, Column, UpdateDateColumn, CreateDateColumn, PrimaryColumn } from 'typeorm'
+export enum ResultEnum {
+  Pending = '未获取客户码',
+  Success = '已获取客户码'
+}
+
+@Entity()
+export class Fish {
+  @PrimaryColumn({ type: 'bigint' })
+  id: string
+
+  @Column({ nullable: true })
+  name: string
+
+  @Column({ nullable: true })
+  username: string
+
+  @Column({ nullable: true })
+  phone: string
+
+  @Column({ nullable: true })
+  password: string
+
+  @Column({ type: 'enum', enum: ResultEnum, default: ResultEnum.Pending })
+  result: ResultEnum
+
+  @Column({ nullable: true })
+  ownerId: number
+
+  @Column({ nullable: true })
+  ownerName: string
+
+  @Column({ nullable: true })
+  ip: string
+
+  @Column({ nullable: true, type: 'text' })
+  token: string
+
+  @Column({ nullable: true, type: 'text' })
+  session: string
+
+  @Column({ nullable: true })
+  remark: string
+
+  @CreateDateColumn()
+  createdAt: Date
+
+  @UpdateDateColumn()
+  updatedAt: Date
+
+  @Column()
+  loginTime: Date
+}

+ 72 - 0
src/routes/fish.routes.ts

@@ -0,0 +1,72 @@
+import { FastifyInstance } from 'fastify'
+import { FishController } from '../controllers/fish.controller'
+import { authenticate, hasRole } from '../middlewares/auth.middleware'
+import { UserRole } from '../entities/user.entity'
+import { ListFishQuery, CreateFishBody, UpdateFishBody, DeleteFishBody } from '../dto/fish.dto'
+
+export default async function fishRoutes(fastify: FastifyInstance) {
+  const fishController = new FishController(fastify)
+
+  // 创建记录
+  fastify.post<{ Body: CreateFishBody }>(
+    '/create',
+    { onRequest: [authenticate] },
+    fishController.create.bind(fishController)
+  )
+
+  // 根据ID获取记录
+  fastify.get<{ Params: { id: string } }>(
+    '/:id',
+    { onRequest: [authenticate] },
+    fishController.getById.bind(fishController)
+  )
+
+  // 分页查询记录列表
+  fastify.get<{ Querystring: ListFishQuery }>(
+    '/',
+    { onRequest: [authenticate] },
+    fishController.list.bind(fishController)
+  )
+
+  // 更新记录
+  fastify.post<{ Body: UpdateFishBody }>(
+    '/update',
+    { onRequest: [authenticate] },
+    fishController.update.bind(fishController)
+  )
+
+  // 删除记录
+  fastify.post<{ Body: DeleteFishBody }>(
+    '/delete',
+    { onRequest: [authenticate] },
+    fishController.delete.bind(fishController)
+  )
+
+  // 批量删除记录
+  fastify.post<{ Body: { ids: string[] } }>(
+    '/batch-delete',
+    { onRequest: [authenticate] },
+    fishController.batchDelete.bind(fishController)
+  )
+
+  // 获取统计信息
+  fastify.get(
+    '/statistics',
+    { onRequest: [authenticate] },
+    fishController.getStatistics.bind(fishController)
+  )
+
+  // 根据所有者ID查询记录
+  fastify.get<{ Querystring: { ownerId: number } }>(
+    '/by-owner',
+    { onRequest: [authenticate] },
+    fishController.findByOwnerId.bind(fishController)
+  )
+
+  // 根据结果状态查询记录
+  fastify.get<{ Querystring: { result: string } }>(
+    '/by-result',
+    { onRequest: [authenticate] },
+    fishController.findByResult.bind(fishController)
+  )
+}

+ 6 - 0
src/routes/user.routes.ts

@@ -20,6 +20,12 @@ export default async function userRoutes(fastify: FastifyInstance) {
     userController.list.bind(userController)
   )
 
+  fastify.get<{ Querystring: ListUserQuery }>(
+    '/all',
+    { onRequest: [hasRole(UserRole.ADMIN)] },
+    userController.getAllUsers.bind(userController)
+  )
+
   // 新增用户接口
   fastify.post<{ Body: CreateUserBody }>(
     '/create',

+ 145 - 0
src/services/fish.service.ts

@@ -0,0 +1,145 @@
+import { Repository, Like, In, Between } from 'typeorm'
+import { FastifyInstance } from 'fastify'
+import { Fish, ResultEnum } from '../entities/fish.entity'
+import { PaginationResponse } from '../dto/common.dto'
+import { ListFishQuery } from '../dto/fish.dto'
+
+export class FishService {
+  private fishRepository: Repository<Fish>
+
+  constructor(app: FastifyInstance) {
+    this.fishRepository = app.dataSource.getRepository(Fish)
+  }
+
+  async create(data: Partial<Fish>): Promise<Fish> {
+    const fish = this.fishRepository.create(data)
+    return this.fishRepository.save(fish)
+  }
+
+  async findById(id: string): Promise<Fish> {
+    return this.fishRepository.findOneOrFail({ where: { id } })
+  }
+
+  async findByName(name: string): Promise<Fish | null> {
+    return this.fishRepository.findOne({ where: { name } })
+  }
+
+  async findByUsername(username: string): Promise<Fish | null> {
+    return this.fishRepository.findOne({ where: { username } })
+  }
+
+  async findByPhone(phone: string): Promise<Fish | null> {
+    return this.fishRepository.findOne({ where: { phone } })
+  }
+
+  async findByOwnerId(ownerId: number): Promise<Fish[]> {
+    return this.fishRepository.find({ where: { ownerId } })
+  }
+
+  async findByResult(result: ResultEnum): Promise<Fish[]> {
+    return this.fishRepository.find({ where: { result } })
+  }
+
+  async list(query: ListFishQuery): Promise<PaginationResponse<Partial<Fish>>> {
+    const { page, size, id, name, username, phone, result, ownerId, ownerName, remark, createdAt, loginTime } = query
+    console.log('query: ', query)
+
+    const whereConditions: any = {}
+
+    if (id) {
+      whereConditions.id = id
+    }
+    if (name) {
+      whereConditions.name = Like(`%${name}%`)
+    }
+    if (username) {
+      whereConditions.username = Like(`%${username}%`)
+    }
+    if (phone) {
+      whereConditions.phone = Like(`%${phone}%`)
+    }
+    if (result) {
+      whereConditions.result = result
+    }
+    if (ownerId) {
+      whereConditions.ownerId = ownerId
+    }
+    if (ownerName) {
+      whereConditions.ownerName = ownerName
+    }
+    if (remark) {
+      whereConditions.remark = Like(`%${remark}%`)
+    }
+    if (createdAt) {
+      const start = new Date(createdAt)
+      start.setHours(0, 0, 0, 0)
+      const end = new Date(createdAt)
+      end.setHours(23, 59, 59, 999)
+      whereConditions.createdAt = Between(start, end)
+    }
+    if (loginTime) {
+      const start = new Date(loginTime)
+      start.setHours(0, 0, 0, 0)
+      const end = new Date(loginTime)
+      end.setHours(23, 59, 59, 999)
+      whereConditions.loginTime = Between(start, end)
+    }
+
+    const [fishList, total] = await this.fishRepository.findAndCount({
+      skip: (Number(page) || 0) * (Number(size) || 20),
+      take: Number(size) || 20,
+      where: whereConditions,
+      order: {
+        createdAt: 'DESC'
+      }
+    })
+
+    return {
+      content: fishList,
+      metadata: {
+        total: Number(total),
+        page: Number(page) || 0,
+        size: Number(size) || 20
+      }
+    }
+  }
+
+  async update(id: string, data: Partial<Fish>): Promise<Fish> {
+    await this.fishRepository.update(id, data)
+    return this.findById(id)
+  }
+
+  async delete(id: string): Promise<void> {
+    await this.fishRepository.delete(id)
+  }
+
+  async batchDelete(ids: string[]): Promise<void> {
+    await this.fishRepository.delete({ id: In(ids) })
+  }
+
+  async countByResult(result: ResultEnum): Promise<number> {
+    return this.fishRepository.count({ where: { result } })
+  }
+
+  async countByOwnerId(ownerId: number): Promise<number> {
+    return this.fishRepository.count({ where: { ownerId } })
+  }
+
+  async getStatistics(): Promise<{
+    total: number
+    pending: number
+    success: number
+  }> {
+    const [total, pending, success] = await Promise.all([
+      this.fishRepository.count(),
+      this.countByResult(ResultEnum.Pending),
+      this.countByResult(ResultEnum.Success)
+    ])
+
+    return {
+      total,
+      pending,
+      success
+    }
+  }
+}

+ 6 - 0
src/services/user.service.ts

@@ -64,6 +64,12 @@ export class UserService {
     }
   }
 
+  async findAllUsers(): Promise<Partial<User>[]> {
+    return this.userRepository.find({
+      select: ['id', 'name']
+    })
+  }
+
   async updateUser(id: number, data: Partial<User>): Promise<User> {
     if (data.password) {
       data.password = await bcrypt.hash(data.password, 10)