|
|
@@ -0,0 +1,204 @@
|
|
|
+import { Repository, Between, LessThanOrEqual, MoreThanOrEqual } from 'typeorm'
|
|
|
+import { FastifyInstance } from 'fastify'
|
|
|
+import { QrCode, QrType } from '../entities/qr-code.entity'
|
|
|
+import { PersonInfo } from '../entities/person-info.entity'
|
|
|
+import { PetInfo } from '../entities/pet-info.entity'
|
|
|
+import { PaginationResponse } from '../dto/common.dto'
|
|
|
+import bcrypt from 'bcryptjs'
|
|
|
+import { randomBytes } from 'crypto'
|
|
|
+
|
|
|
+export class QrCodeService {
|
|
|
+ private qrCodeRepository: Repository<QrCode>
|
|
|
+ private personInfoRepository: Repository<PersonInfo>
|
|
|
+ private petInfoRepository: Repository<PetInfo>
|
|
|
+
|
|
|
+ constructor(app: FastifyInstance) {
|
|
|
+ this.qrCodeRepository = app.dataSource.getRepository(QrCode)
|
|
|
+ this.personInfoRepository = app.dataSource.getRepository(PersonInfo)
|
|
|
+ this.petInfoRepository = app.dataSource.getRepository(PetInfo)
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 生成唯一的二维码编号
|
|
|
+ */
|
|
|
+ private generateQrCode(): string {
|
|
|
+ const timestamp = Date.now().toString(36)
|
|
|
+ const random = randomBytes(8).toString('hex')
|
|
|
+ return `QR${timestamp}${random}`.toUpperCase()
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 生成维护码
|
|
|
+ */
|
|
|
+ private generateMaintenanceCode(): string {
|
|
|
+ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
|
|
|
+ let code = ''
|
|
|
+ for (let i = 0; i < 8; i++) {
|
|
|
+ code += chars.charAt(Math.floor(Math.random() * chars.length))
|
|
|
+ }
|
|
|
+ return code
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 生成二维码
|
|
|
+ */
|
|
|
+ async generateQrCodes(
|
|
|
+ qrType: QrType,
|
|
|
+ quantity: number = 1
|
|
|
+ ): Promise<Array<{ qrCode: string; maintenanceCode: string }>> {
|
|
|
+ const result = []
|
|
|
+
|
|
|
+ for (let i = 0; i < quantity; i++) {
|
|
|
+ const qrCode = this.generateQrCode()
|
|
|
+ const maintenanceCode = this.generateMaintenanceCode()
|
|
|
+ const hashedMaintenanceCode = await bcrypt.hash(maintenanceCode, 10)
|
|
|
+
|
|
|
+ const entity = this.qrCodeRepository.create({
|
|
|
+ qrCode,
|
|
|
+ maintenanceCode: hashedMaintenanceCode,
|
|
|
+ qrType,
|
|
|
+ isActivated: false,
|
|
|
+ scanCount: 0
|
|
|
+ })
|
|
|
+
|
|
|
+ await this.qrCodeRepository.save(entity)
|
|
|
+
|
|
|
+ result.push({
|
|
|
+ qrCode,
|
|
|
+ maintenanceCode
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ return result
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 验证维护码
|
|
|
+ */
|
|
|
+ async verifyMaintenanceCode(qrCode: string, maintenanceCode: string): Promise<boolean> {
|
|
|
+ const entity = await this.qrCodeRepository.findOne({ where: { qrCode } })
|
|
|
+ if (!entity) {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ return bcrypt.compare(maintenanceCode, entity.maintenanceCode)
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 根据二维码获取信息
|
|
|
+ */
|
|
|
+ async getQrCodeInfo(qrCode: string): Promise<any> {
|
|
|
+ const entity = await this.qrCodeRepository.findOne({ where: { qrCode } })
|
|
|
+ if (!entity) {
|
|
|
+ throw new Error('二维码不存在')
|
|
|
+ }
|
|
|
+
|
|
|
+ let info = null
|
|
|
+ if (entity.qrType === QrType.PERSON) {
|
|
|
+ info = await this.personInfoRepository.findOne({ where: { qrCodeId: entity.id } })
|
|
|
+ } else if (entity.qrType === QrType.PET) {
|
|
|
+ info = await this.petInfoRepository.findOne({ where: { qrCodeId: entity.id } })
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ id: entity.id,
|
|
|
+ qrCode: entity.qrCode,
|
|
|
+ qrType: entity.qrType,
|
|
|
+ isActivated: entity.isActivated,
|
|
|
+ scanCount: entity.scanCount,
|
|
|
+ info
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 增加扫描次数
|
|
|
+ */
|
|
|
+ async incrementScanCount(qrCodeId: number): Promise<void> {
|
|
|
+ await this.qrCodeRepository.increment({ id: qrCodeId }, 'scanCount', 1)
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 激活二维码
|
|
|
+ */
|
|
|
+ async activateQrCode(qrCodeId: number): Promise<void> {
|
|
|
+ await this.qrCodeRepository.update(qrCodeId, { isActivated: true })
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 查询二维码列表
|
|
|
+ */
|
|
|
+ async queryQrCodes(
|
|
|
+ qrType?: QrType,
|
|
|
+ isActivated?: boolean,
|
|
|
+ startDate?: string,
|
|
|
+ endDate?: string,
|
|
|
+ page: number = 0,
|
|
|
+ pageSize: number = 20
|
|
|
+ ): Promise<PaginationResponse<QrCode>> {
|
|
|
+ const queryBuilder = this.qrCodeRepository.createQueryBuilder('qrCode')
|
|
|
+
|
|
|
+ if (qrType) {
|
|
|
+ queryBuilder.andWhere('qrCode.qrType = :qrType', { qrType })
|
|
|
+ }
|
|
|
+
|
|
|
+ if (isActivated !== undefined) {
|
|
|
+ queryBuilder.andWhere('qrCode.isActivated = :isActivated', { isActivated })
|
|
|
+ }
|
|
|
+
|
|
|
+ if (startDate && endDate) {
|
|
|
+ queryBuilder.andWhere('DATE(qrCode.createdAt) BETWEEN :startDate AND :endDate', { startDate, endDate })
|
|
|
+ } else if (startDate) {
|
|
|
+ queryBuilder.andWhere('DATE(qrCode.createdAt) >= :startDate', { startDate })
|
|
|
+ } else if (endDate) {
|
|
|
+ queryBuilder.andWhere('DATE(qrCode.createdAt) <= :endDate', { endDate })
|
|
|
+ }
|
|
|
+
|
|
|
+ const [content, total] = await queryBuilder
|
|
|
+ .select([
|
|
|
+ 'qrCode.id',
|
|
|
+ 'qrCode.qrCode',
|
|
|
+ 'qrCode.qrType',
|
|
|
+ 'qrCode.isActivated',
|
|
|
+ 'qrCode.scanCount',
|
|
|
+ 'qrCode.createdAt',
|
|
|
+ 'qrCode.updatedAt'
|
|
|
+ ])
|
|
|
+ .skip(page * pageSize)
|
|
|
+ .take(pageSize)
|
|
|
+ .orderBy('qrCode.createdAt', 'DESC')
|
|
|
+ .getManyAndCount()
|
|
|
+
|
|
|
+ return {
|
|
|
+ content,
|
|
|
+ metadata: {
|
|
|
+ total: Number(total),
|
|
|
+ page: Number(page),
|
|
|
+ size: Number(pageSize)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 根据日期获取二维码列表(用于下载)
|
|
|
+ */
|
|
|
+ async getQrCodesByDate(date: string): Promise<QrCode[]> {
|
|
|
+ return this.qrCodeRepository
|
|
|
+ .createQueryBuilder('qrCode')
|
|
|
+ .where('DATE(qrCode.createdAt) = :date', { date })
|
|
|
+ .orderBy('qrCode.createdAt', 'ASC')
|
|
|
+ .getMany()
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 根据ID获取二维码
|
|
|
+ */
|
|
|
+ async findById(id: number): Promise<QrCode | null> {
|
|
|
+ return this.qrCodeRepository.findOne({ where: { id } })
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 根据qrCode获取实体
|
|
|
+ */
|
|
|
+ async findByQrCode(qrCode: string): Promise<QrCode | null> {
|
|
|
+ return this.qrCodeRepository.findOne({ where: { qrCode } })
|
|
|
+ }
|
|
|
+}
|