0
0
Lập trình
Thaycacac
Thaycacac thaycacac

Thêm theo dõi nhấp chuột vào dịch vụ rút gọn link Nest.js

Đăng vào 7 tháng trước

• 8 phút đọc

Hướng Dẫn Thêm Theo Dõi Nhấp Chuột Vào Dịch Vụ Rút Gọn Link Nest.js

Trong bài viết trước, chúng ta đã xây dựng một dịch vụ rút gọn link cơ bản. Để trở thành một dịch vụ rút gọn link toàn diện, cần thiết phải thêm chức năng phân tích dữ liệu. Phân tích dữ liệu là một trong những giá trị cốt lõi của dịch vụ rút gọn link. Bằng cách theo dõi số lần nhấp vào link, chúng ta có thể hiểu được hiệu quả lan tỏa, hồ sơ người dùng và các thông tin khác.

Trong bài viết này, chúng ta sẽ thêm khả năng theo dõi nhấp chuột vào dịch vụ, ghi lại thời gian, địa chỉ IP và thông tin thiết bị cho mỗi lần nhấp.

1. Tạo Thực Thể Cơ Sở Dữ Liệu Cho Bản Ghi Nhấp

Đầu tiên, chúng ta cần một bảng dữ liệu mới để lưu trữ mỗi bản ghi nhấp. Tương tự, chúng ta sẽ xác định cấu trúc của nó bằng cách tạo một thực thể TypeORM.

Tạo một thư mục mới có tên click trong thư mục src, và bên trong đó, tạo một tệp click.entity.ts:

typescript Copy
// src/click/click.entity.ts

import { ShortLink } from '../short-link/short-link.entity';
import {
  Entity,
  PrimaryGeneratedColumn,
  Column,
  CreateDateColumn,
  ManyToOne,
  JoinColumn,
} from 'typeorm';

@Entity()
export class Click {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  // Thiết lập mối quan hệ nhiều-một với thực thể ShortLink
  // Điều này có nghĩa là một short link có thể tương ứng với nhiều bản ghi nhấp
  @ManyToOne(() => ShortLink, (shortLink) => shortLink.clicks)
  @JoinColumn({ name: 'shortLinkId' }) // Tạo một cột khóa ngoại trong cơ sở dữ liệu
  shortLink: ShortLink;

  @CreateDateColumn()
  clickedAt: Date;

  @Column({ type: 'varchar', length: 45 }) // Để lưu trữ địa chỉ IPv4 hoặc IPv6
  ipAddress: string;

  @Column({ type: 'text' })
  userAgent: string;
}

Giải thích:

  • @ManyToOne: Bộ trang trí này thiết lập mối quan hệ giữa ClickShortLink. Nhiều (Nhiều) lần nhấp có thể được liên kết với một (Một) short link.
  • @JoinColumn: Chỉ định tên của cột khóa ngoại, được sử dụng để liên kết hai bảng ở cấp độ cơ sở dữ liệu.

Để có thể truy vấn tất cả các bản ghi nhấp từ một short link, chúng ta cũng cần cập nhật tệp short-link.entity.ts để thiết lập một mối quan hệ hai chiều.

Mở src/short-link/short-link.entity.ts và thêm thuộc tính clicks:

typescript Copy
// src/short-link/short-link.entity.ts
import { Click } from '../click/click.entity'; // Nhập thực thể Click
import {
  Entity,
  Column,
  PrimaryGeneratedColumn,
  CreateDateColumn,
  Index,
  OneToMany,
} from 'typeorm';

@Entity()
export class ShortLink {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  // ... các trường hiện có khác ...
  @Column({ unique: true })
  @Index()
  shortCode: string;

  @Column({ type: 'text' })
  longUrl: string;

  @CreateDateColumn()
  createdAt: Date;

  // Thuộc tính mới
  @OneToMany(() => Click, (click) => click.shortLink)
  clicks: Click[];
}

Giải thích:

  • @OneToMany: Thiết lập một mối quan hệ một-nhiều từ ShortLink đến Click. Một (Một) short link có thể có nhiều (Nhiều) bản ghi nhấp.

3. Tạo Mô Đun và Logic Cho Dịch Vụ Nhấp

Giống như với ShortLink, chúng ta sẽ tạo một mô đun và dịch vụ dành riêng cho Click để xử lý logic liên quan và duy trì tính mô-đun của mã.

Sử dụng Nest CLI để nhanh chóng tạo các tệp cần thiết:

bash Copy
nest generate resource click

Cấu Hình ClickModule

Mở src/click/click.module.ts, nhập TypeOrmModule, và đăng ký thực thể Click.

typescript Copy
// src/click/click.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ClickService } from './click.service';
import { Click } from './click.entity';

@Module({
  imports: [TypeOrmModule.forFeature([Click])],
  providers: [ClickService],
  exports: [ClickService], // Xuất ClickService để có thể sử dụng bởi các mô đun khác
})
export class ClickModule {}

Triển Khai ClickService

Mở src/click/click.service.ts và viết logic để ghi lại các lần nhấp.

typescript Copy
// src/click/click.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Click } from './click.entity';
import { ShortLink } from '../short-link/short-link.entity';

@Injectable()
export class ClickService {
  constructor(
    @InjectRepository(Click)
    private readonly clickRepository: Repository<Click>
  ) {}

  async recordClick(shortLink: ShortLink, ipAddress: string, userAgent: string): Promise<Click> {
    const newClick = this.clickRepository.create({
      shortLink,
      ipAddress,
      userAgent,
    });

    return this.clickRepository.save(newClick);
  }
}

4. Tích Hợp Theo Dõi Nhấp Trong Quá Trình Chuyển Hướng

Bây giờ, chúng ta sẽ tích hợp logic theo dõi vào phương thức redirect của ShortLinkController.

Cập Nhật ShortLinkModule

Đầu tiên, ShortLinkModule cần nhập ClickModule để có thể sử dụng ClickService.

typescript Copy
// src/short-link/short-link.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ShortLinkController } from './short-link.controller';
import { ShortLinkService } from './short-link.service';
import { ShortLink } from './short-link.entity';
import { ClickModule } from '../click/click.module'; // Nhập ClickModule

@Module({
  imports: [TypeOrmModule.forFeature([ShortLink]), ClickModule], // Thêm ClickModule
  controllers: [ShortLinkController],
  providers: [ShortLinkService],
})
export class ShortLinkModule {}

Sửa Đổi ShortLinkController

Mở src/short-link/short-link.controller.ts, tiêm ClickService, và gọi nó trong phương thức redirect.

typescript Copy
// src/short-link/short-link.controller.ts
import { Controller, Get, Post, Body, Param, Res, NotFoundException, Req } from '@nestjs/common';
import { Response, Request } from 'express';
import { ShortLinkService } from './short-link.service';
import { CreateShortLinkDto } from './dto/create-short-link.dto';
import { ClickService } from '../click/click.service'; // Nhập ClickService

@Controller()
export class ShortLinkController {
  constructor(
    private readonly shortLinkService: ShortLinkService,
    private readonly clickService: ClickService // Tiêm ClickService
  ) {}

  // ... phương thức createShortLink không thay đổi ...
  @Post('shorten')
  // ...
  @Get(':shortCode')
  async redirect(
    @Param('shortCode') shortCode: string,
    @Res() res: Response,
    @Req() req: Request // Tiêm đối tượng Request của Express
  ) {
    const link = await this.shortLinkService.findOneByCode(shortCode);

    if (!link) {
      throw new NotFoundException('Short link không tồn tại.');
    }

    // Ghi lại nhấp chuột bất đồng bộ mà không chờ đợi hoàn thành,
    // để tránh làm chậm quá trình chuyển hướng của người dùng
    this.clickService.recordClick(link, req.ip, req.get('user-agent') || '');

    // Thực hiện chuyển hướng
    return res.redirect(301, link.longUrl);
  }
}

Những Thay Đổi Chính:

  1. Chúng ta đã tiêm ClickService.
  2. Trong phương thức redirect, chúng ta đã lấy đối tượng express.Request thông qua bộ trang trí @Req().
  3. Từ đối tượng req, chúng ta đã lấy req.ip (địa chỉ IP) và req.get('user-agent') (thông tin thiết bị và trình duyệt).
  4. Chúng ta đã gọi this.clickService.recordClick() để lưu trữ bản ghi nhấp. Lưu ý, chúng ta không sử dụng await ở đây, để đảm bảo rằng phép ghi bản ghi nhấp không chặn quá trình chuyển hướng.

5. Xem Thống Kê

Sau khi dữ liệu được ghi lại, chúng ta cũng cần một endpoint để xem nó. Hãy thêm một endpoint thống kê trong ShortLinkController.

typescript Copy
// src/short-link/short-link.controller.ts
// ... (các import và constructor)

@Controller()
export class ShortLinkController {
  // ... (constructor và các phương thức khác)

  // Endpoint thống kê mới
  @Get('stats/:shortCode')
  async getStats(@Param('shortCode') shortCode: string) {
    const linkWithClicks = await this.shortLinkService.findOneByCodeWithClicks(shortCode);

    if (!linkWithClicks) {
      throw new NotFoundException('Short link không tồn tại.');
    }

    return {
      shortCode: linkWithClicks.shortCode,
      longUrl: linkWithClicks.longUrl,
      totalClicks: linkWithClicks.clicks.length,
      clicks: linkWithClicks.clicks.map((click) => ({
        clickedAt: click.clickedAt,
        ipAddress: click.ipAddress,
        userAgent: click.userAgent,
      })),
    };
  }
}

Để endpoint này hoạt động, chúng ta cũng cần thêm phương thức findOneByCodeWithClicks trong ShortLinkService.

Mở src/short-link/short-link.service.ts:

typescript Copy
// src/short-link/short-link.service.ts
// ...

@Injectable()
export class ShortLinkService {
  constructor(
    @InjectRepository(ShortLink)
    private readonly shortLinkRepository: Repository<ShortLink>
  ) {}

  // ... (các phương thức findOneByCode và create)

  // Phương thức mới sử dụng các quan hệ để tải các nhấp chuột liên quan
  async findOneByCodeWithClicks(shortCode: string): Promise<ShortLink | null> {
    return this.shortLinkRepository.findOne({
      where: { shortCode },
      relations: ['clicks'], // Khóa: Cho TypeORM biết để tải thực thể nhấp chuột liên quan
    });
  }
}

Bây giờ, hãy khởi động lại dịch vụ của bạn. Sau khi bạn truy cập vào một short link, bạn có thể thực hiện một yêu cầu GET đến http://localhost:3000/stats/your-short-code và bạn sẽ thấy một phản hồi JSON tương tự như dưới đây, bao gồm các bản ghi nhấp chi tiết:

json Copy
{
  "shortCode": "some-hash",
  "longUrl": "https://www.google.com/",
  "totalClicks": 1,
  "clicks": [
    {
      "clickedAt": "2025-09-17T23:00:00.123Z",
      "ipAddress": "::1",
      "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ..."
    }
  ]
}

Tại thời điểm này, dịch vụ rút gọn link của bạn đã có khả năng theo dõi nhấp chuột và phân tích thống kê!

Cập Nhật Đến Sản Xuất

Vì dịch vụ của bạn đã chạy trực tuyến, bước tiếp theo sau khi gỡ lỗi cục bộ là triển khai các bản cập nhật lên máy chủ sản xuất.

Đối với các ứng dụng triển khai trên Leapcell, bước này rất đơn giản: bạn chỉ cần đẩy mã của mình lên GitHub, và Leapcell sẽ tự động kéo mã mới nhất và cập nhật ứng dụng.

Hơn nữa, Leapcell tự nó có một tính năng phân tích lưu lượng mạnh mẽ. Ngay cả khi không thực hiện theo dõi nhấp chuột của riêng bạn, dữ liệu được trình bày trong Bảng điều khiển Leapcell có thể cung cấp cho bạn những hiểu biết sâu sắc về người dùng.


Theo dõi chúng tôi trên X: @LeapcellHQ


Đọc thêm trên blog của chúng tôi

Bài Viết Liên Quan:

  • Cách lưu trữ các dự án Golang miễn phí (Ví dụ về Gin)
  • Cách lưu trữ các dự án Rust trong đám mây miễn phí
  • Cách chạy Puppeteer trong đám mây miễn phí: So sánh các giải pháp
  • Giải pháp thay thế Vercel tốt nhất để lưu trữ các dự án Next.js
Gợi ý câu hỏi phỏng vấn
Không có dữ liệu

Không có dữ liệu

Bài viết được đề xuất
Bài viết cùng tác giả

Bình luận

Chưa có bình luận nào

Chưa có bình luận nào