Stack: Go 1.22 · Fiber v2 · GORM · MongoDB Driver · Redis · JWT · Uber Dig (DI) · gocron
Module name:tool-search-api
Default port:33003
| Công cụ | Phiên bản tối thiểu |
|---|---|
| Go | 1.22+ |
| Docker & Docker Compose | Latest |
| Make (tùy chọn) | Any |
.env: Chỉnh sửa file .env theo hướng dẫn ở mục 1.3.GET http://localhost:33003/
# Trả về: {"message": "Hello, World API!"}.env)| Biến | Ví dụ | Mô tả |
|---|---|---|
APP_MODE | development | Chế độ chạy (development / production) |
AUTO_MIGRATE_DATABASE | true | Tự động migrate schema MySQL khi khởi động |
SERVER_PORT | 33003 | Port HTTP server lắng nghe |
API_KEY | B7j&Kq9G... | API Key xác thực nội bộ |
DB_MONGO_HOST | search_api_mongodb | Host MongoDB (tên container khi dùng Docker) |
DB_MONGO_PORT | 27017 | Port MongoDB |
DB_MONGO_USER | root | Username MongoDB |
DB_MONGO_PASS | PwDev123 | Password MongoDB |
DB_MONGO_NAME | search-api-mongodb | Tên database MongoDB |
DB_MYSQL_HOST | search_api_mysql | Host MySQL |
DB_MYSQL_PORT | 3306 | Port MySQL |
DB_MYSQL_USER | root | Username MySQL |
DB_MYSQL_PASS | PwDev123 | Password MySQL |
DB_MYSQL_NAME | search-api-mysql | Tên database MySQL |
REDIS_HOST | search_api_redis | Host Redis |
REDIS_PORT | 6379 | Port Redis |
REDIS_USER | (empty) | Username Redis |
REDIS_PASS | (empty) | Password Redis |
REDIS_DB | 0 | Index database Redis |
JWT_SECRET | secret-key | Secret key để ký JWT token |
ENABLE_PUSH_MESSAGE_ERROR_TO_TELEGRAM | true | Bật/tắt thông báo lỗi qua Telegram |
PROXY_USE_MAX | 2 | Số lần proxy được sử dụng tối đa |
PROXY_ROTATE_MAX_CONCURRENT | 1 | Số luồng rotate proxy đồng thời tối đa |
API_SEARCH_TOKEN | 6MuRot4V... | Token xác thực cho tool search gọi vào API |
WEBHOOK_HOST | http://127.0.0.1:33003 | Base URL của chính server này (dùng trong webhook) |
WEBHOOK_TASK_UPDATE_STATUS_URL | .../webhook/task/update-status | URL webhook cập nhật trạng thái task |
WEBHOOK_PROXY_GET_PROXY_URL | .../webhook/proxy/get-proxy | URL webhook lấy proxy |
WEBHOOK_KEY | 255d00cc... | Secret key để xác thực webhook request |
WEBHOOK_URL | .../webhook/task/update-process | URL webhook cập nhật tiến trình task |
WEBHOOK_UPDATE_SEARCH_RESULT | .../webhook/update-search-result | URL webhook cập nhật kết quả tìm kiếm |
WEBHOOK_UPDATE_SEARCH_ANCHOR_TEXT | .../webhook/update-search-anchor-text | URL webhook cập nhật anchor text |
TELEGRAM_BOT_TOKEN | (cần điền) | Token Telegram Bot |
TELEGRAM_CHAT_ID | (cần điền) | Chat ID nhận thông báo thông thường |
TELEGRAM_CHAT_ERROR_ID | (cần điền) | Chat ID nhận thông báo lỗi |
TELEGRAM_CHAT_REPORT_ID | (cần điền) | Chat ID nhận báo cáo |
SCHEDULE_RESET_TASK | (cron expr) | Cron để reset task tự động |
SCHEDULE_SEND_TASK | (cron expr) | Cron để gửi task tự động |
SCHEDULE_SEARCH_KEYWORD | (cron expr) | Cron để search keyword |
SCHEDULE_REPORT_TRAFFIC | (cron expr) | Cron để báo cáo traffic |
LIMIT_SEND_TASK_PER_SERVER | (số nguyên) | Giới hạn task gửi đến 1 server |
WHITE_LIST_IP_TOOL_SEARCH | (IP list) | Danh sách IP được phép truy cập search routes |
users, servers, activity_logskeywords, keyword_results, proxies, anchor_textsusersQuản lý tài khoản người dùng, phân quyền đăng nhập.
| Cột | Kiểu | Ràng buộc | Mô tả |
|---|---|---|---|
id | INT | PK, AUTO_INCREMENT | ID định danh người dùng |
name | VARCHAR(255) | NOT NULL | Tên hiển thị |
password | VARCHAR(255) | NOT NULL | M ật khẩu (đã hash bcrypt) |
email | VARCHAR(255) | UNIQUE, NOT NULL | Email dùng để đăng nhập |
first_name | VARCHAR(255) | NULL | Tên |
last_name | VARCHAR(255) | NULL | Họ |
role | INT | DEFAULT 1 | Phân quyền: 0 = Admin, 1 = User |
created_at | DATETIME | AUTO | Thời điểm tạo |
updated_at | DATETIME | AUTO | Thời điểm cập nhật cuối |
deleted_at | DATETIME | NULL | Soft delete (GORM) |
serversQuản lý danh sách server tool dùng để phân phối task tìm kiếm.
| Cột | Kiểu | Ràng buộc | Mô tả |
|---|---|---|---|
id | INT | PK, AUTO_INCREMENT | ID server |
name | VARCHAR(255) | NOT NULL | Tên server (ví dụ: "Server-01-HN") |
is_active | TINYINT(1) | DEFAULT 0 | Trạng thái hoạt động (1 = active) |
ip | VARCHAR(255) | NOT NULL | Địa chỉ IP của server tool |
is_priority | TINYINT(1) | DEFAULT 0 | Ưu tiên nhận task (1 = ưu tiên cao) |
limit_process | INT | Số process tối đa server có thể xử lý | |
current_process | INT | Số process đang chạy hiện tại | |
created_at | DATETIME | AUTO | Thời điểm tạo |
updated_at | DATETIME | AUTO | Thời điểm cập nhật cuối |
deleted_at | DATETIME | NULL | Soft delete (GORM) |
activity_logsGhi lại mọi hành động của người dùng (audit trail).
| Cột | Kiểu | Ràng buộc | Mô tả |
|---|---|---|---|
id | INT | PK, AUTO_INCREMENT | ID log |
user_id | INT | FK -> users.id, NULL | ID người dùng thực hiện hành động |
action | VARCHAR(255) | NOT NULL | Tên hành động (vd: "Đăng nhập", "Tạo keyword") |
parameter | JSON | NULL | Tham số yêu cầu đính kèm (body/query) |
model_name | VARCHAR(255) | NOT NULL | Tên model bị tác động (vd: "user", "keyword") |
model_id | INT | NOT NULL | ID của bản ghi bị tác động |
old_value | JSON | NULL | Giá trị trước khi thay đổi |
new_value | JSON | NULL | Giá trị sau khi thay đổi |
ip_address | VARCHAR(255) | IP của client gọi request | |
user_agent | VARCHAR(255) | User-Agent của client | |
created_at | DATETIME | AUTO | Thời điểm ghi log |
updated_at | DATETIME | AUTO | Cập nhật lần cuối |
keywordsLưu danh sách từ khoá cần theo dõi SEO ranking.
| Field | Kiểu | Bắt buộc | Mô tả |
|---|---|---|---|
_id | ObjectID | Yes | ID MongoDB tự sinh |
keyword | String | Yes | Từ khoá cần tìm kiếm |
status | Number | Yes | 0 = Disable, 1 = Enable |
device | Array[Number] | Yes | Thiết bị: 0 = Desktop, 1 = Mobile |
engine | Array[Number] | Yes | Công cụ tìm kiếm: 0 = GoogleVN, 1 = Google, 2 = Coc Coc |
result_num | Number | Yes | Số kết quả tìm kiếm cần lấy |
created_by | Number | No | ID user tạo keyword |
is_running | Boolean | No | true nếu đang trong quá trình tìm kiếm |
created_at | Datetime | No | Thời điểm tạo |
updated_at | Datetime | No | Thời điểm cập nhật |
0 = GoogleVN, 1 = Google, 2 = Coc Cockeyword_resultsLưu kết quả tìm kiếm thực tế của từng keyword theo từng lần chạy.
| Field | Kiểu | Bắt buộc | Mô tả |
|---|---|---|---|
_id | ObjectID | Yes | ID kết quả |
keyword_id | String | Yes | ID keyword cha |
keyword_schedule_id | ObjectID | Yes | ID schedule (lần chạy cụ thể) |
keyword | String | Yes | Từ khoá (denormalized để query nhanh) |
device | Number | Yes | Thiết bị: 0 = Desktop, 1 = Mobile |
device_os | Number | No | OS: 0 = Windows/Android, 1 = MacOS/iOS |
engine | Number | Yes | Công cụ: 0 = GoogleVN, 1 = Google, 2 = Coc Coc |
result_num | Number | No | Số kết quả tối đa |
is_scan_domain | Boolean | No | Có quét domain không |
search_data | Object | No | Dữ liệu kết quả (xem bên dưới) |
status | Number | No | 0 = Unknown, -1 = Error, -2 = Not Found, -3 = Captcha, 1 = Success |
created_at | Datetime | No | Thời điểm tạo |
updated_at | Datetime | No | Thời điểm cập nhật |
search_data:{
"ads": [
{ "url": "https://...", "rank": 1, "type": "company", "confirm_status": true, "label": "", "title": "...", "description": "..." }
],
"search": [
{ "url": "https://...", "rank": 1, "type": "competitors", "confirm_status": false, "label": "", "title": "...", "description": "..." }
]
}type: "company" = Domain của công ty, "competitors" = Domain đối thủproxiesQuản lý proxy trong hệ thống (MongoDB).
| Field | Kiểu | Bắt buộc | Mô tả |
|---|---|---|---|
_id | ObjectID | Yes | ID |
ip | String | Yes | Địa chỉ IP proxy |
port | Number | Yes | Port proxy |
username | String | No | Username xác thực proxy |
password | String | No | Password xác thực proxy |
rotate_url | String | Yes | URL để trigger rotate IP |
rotate_time | Number | No | Thời gian giữa mỗi lần rotate (giây), mặc định 60 |
next_rotate | Datetime | No | Thời điểm tiếp theo được phép rotate |
last_used_at | Datetime | No | Lần cuối proxy được sử dụng |
in_use | Number | No | Số task đang dùng proxy này |
proxy_used | Array | No | Danh sách task đã dùng: [{task_id, proxy_url}] |
used_count | Number | No | Tổng số lần đã sử dụng |
is_captcha | Boolean | No | true nếu proxy bị captcha |
is_error | Boolean | No | true nếu proxy đang bị lỗi |
is_success | Boolean | No | true nếu proxy vừa thành công |
error_count | Number | No | Số lỗi liên tiếp (reset về 0 khi thành công) |
is_active | Boolean | No | true nếu proxy còn hoạt động |
ip_public | String | No | IP public thực tế sau rotate |
region | String | No | Khu vực địa lý của proxy |
disable_auto_rotate | Boolean | No | true = tắt tự động rotate |
is_rotating | Boolean | No | true nếu đang trong quá trình rotate |
rotate_server | String | No | Tên server đang thực hiện rotate |
area | String | No | Khu vực proxy: "global" hoặc "vn" |
auth_token | String | No | Token xác thực nhà cung cấp proxy |
package_name | String | No | Tên gói dịch vụ proxy |
created_at | Datetime | No | Thời điểm tạo |
updated_at | Datetime | No | Thời điểm cập nhật |
anchor_textsLưu task tìm kiếm anchor text (backlink) theo domain.
| Field | Kiểu | Bắt buộc | Mô tả |
|---|---|---|---|
domain | String | Yes | Domain cần tìm anchor text (vd: example.com) |
anchor_key | Array[String] | Yes | Danh sách từ khoá anchor cần tìm |
webhook_url | String | Yes | URL để gửi kết quả về khi hoàn thành |
proxy_url | String | Yes | URL proxy để sử dụng khi tìm kiếm |
searched | Number | No | Số lần đã tìm kiếm |
status | Number | No | 0 = Unknown, -1 = Error, -2 = Not Found, -3 = Captcha, 1 = Success |
+------------------------------------------------------------------+
| CLIENTS |
| Admin Frontend (Vue 3) | Tool Search (Node.js/Bun) |
+-------------+------------+--------------+------------------------+
| JWT Auth | API Key / Webhook Key
v v
+------------------------------------------------------------------+
| SEARCH TOOL API (Go + Fiber) |
| Port: 33003 |
| |
| Middleware: CORS | Helmet | Logger | JWT | API Key | WH Key |
| |
| Route Groups: |
| /api/* (JWT Auth) - User, Proxy, Server, Keyword |
| /search/* (API Key) - KeywordPoolTask CRUD |
| /anchor-text/* (API Key) - AnchorText CRUD |
| /webhook/* (WH Key) - Proxy & Search result callbacks |
| /ws (Socket.IO) - Real-time updates |
| |
| Scheduler (gocron): |
| - Reset proxy errors | Send search tasks | Keyword search cron |
+---+------------------+------+-----+-----------------------------+
| | |
+---v---+ +--------v-------+ +-v---------+
| MySQL | | MongoDB | | Redis |
| GORM | | (Mongo Driver) | | (Cache) |
| | | | | |
|users | |keywords | |task cache |
|servers| |keyword_results | |locks |
|act_ | |proxies | | |
|logs | |anchor_texts | +-----------+
+-------+ |keyword_pool_ |
| tasks |
+----------------+keyword_pool_tasks làm buffer trung gian):[Cron SCHEDULE_SEARCH_KEYWORD - gocron]
-> keyword.service.SearchKeyword()
-> Lấy tất cả keyword status=Enable từ MongoDB (keywords)
-> Tạo SearchKeywordRequest cho từng tổ hợp (keyword x engine x device)
-> Lưu vào MongoDB: keyword_pool_tasks <-- buffer/queue
[Cron keyword_pool_task - goroutine, loop mỗi 60s]
-> Lấy tất cả task từ keyword_pool_tasks
-> Kiểm tra Redis cache (key: keyword_engine_device_os):
- Nếu có cache -> gọi hook_url trả kết quả luôn -> xóa task
- Nếu chưa có -> đưa vào filteredTasks
-> Đánh dấu filteredTasks: Status = PROCESSING
-> Phân phối task đến Tool Servers (servers table) theo weighted:
IsPriority=true: trọng số 5 | thường: trọng số 2
-> POST http://{server.IP}/api/searches (gửi batch đến Tool)
-> Nhận response IDs -> xóa task khỏi keyword_pool_tasks
-> Nếu lỗi: reset Status = NEW để retry vòng sau -> Lưu vào collection searches (MongoDB của Tools)
-> Cron (every 1s): pick PENDING -> Chrome launch -> search Google/CocCoc
-> Lưu kết quả vào searches.search_data
-> POST {webhook_url} = /webhook/update-search-result/:id
-> API cập nhật kết quả vào keyword_resultsanchor_texts làm buffer):[Caller] POST /anchor-text/search
-> Lưu vào MongoDB: anchor_texts <-- buffer
[Cron anchor_text - goroutine, loop mỗi 60s]
-> Lấy tất cả anchor text tasks
-> Kiểm tra Redis cache (key: domain_anchor_text):
- Nếu có cache -> gọi hook_url -> xóa task
- Nếu chưa -> phân phối đến Tool servers (weighted)
-> POST http://{server.IP}/api/search-anchor-texts
-> Tool crawl anchor text -> POST /webhook/update-search-anchor-text/:id[Tool cần proxy]
-> POST /webhook/proxy/get-proxy {task_id, proxy_url}
-> API tìm proxy khả dụng (is_active, in_use < max, không lỗi)
-> Đánh dấu in_use++, ghi task_id vào proxy.proxy_used
-> Trả về {host, port, username, password}
[Tool sau khi rotate xong]
-> POST /webhook/proxy-rotation-result/:id
-> API cập nhật ip_public, region, reset is_rotating=falseapi/
├── cmd/
│ └── main.go # Entry point
│
├── bootstrap/
│ ├── routes.go # Đăng ký toàn bộ routes + cron
│ ├── cron/ # Cron job configs (gocron)
│ ├── injection/ # Dependency Injection (Uber Dig)
│ └── socket/ # Socket.IO server setup
│
├── internal/ # Core business logic
│ ├── activity_log/
│ │ ├── activity_log.go # Model (MySQL)
│ │ ├── dto/
│ │ ├── handler/ # GET /activity-log (pagination)
│ │ ├── repository/
│ │ └── service/
│ │
│ ├── anchor_text/
│ │ ├── anchor_text.go # Model (MongoDB)
│ │ ├── cron/
│ │ ├── dto/
│ │ ├── handler/ # POST /search, GET /get-all, GET /reset-all-status
│ │ ├── repository/
│ │ └── service/
│ │
│ ├── keyword/
│ │ ├── keyword.go # Model (MongoDB) + Device/Engine/Status enums
│ │ ├── cron/
│ │ ├── dto/
│ │ ├── handler/ # CRUD + reset
│ │ ├── repository/
│ │ └── service/
│ │
│ ├── keyword_pool_task/
│ │ ├── handler/ # POST /, GET /get-all, GET /reset-all-status
│ │ ├── repository/
│ │ └── service/
│ │
│ ├── keyword_result/
│ │ ├── keyword_result.go # Model (MongoDB) + SearchData, SearchResult
│ │ ├── cron/
│ │ ├── dto/
│ │ ├── handler/ # POST (create), PUT /update-search-data/:id
│ │ ├── repository/
│ │ └── service/
│ │
│ ├── proxy/
│ │ ├── proxy.go # Model (MongoDB) + ProxyArea enum
│ │ ├── cron/
│ │ ├── dto/
│ │ ├── handler/ # CRUD + import Excel + change_status
│ │ ├── repository/
│ │ └── service/
│ │
│ ├── server/
│ │ ├── server.go # Model (MySQL)
│ │ ├── handler/ # CRUD
│ │ ├── repository/
│ │ └── service/
│ │
│ ├── user/
│ │ ├── user.go # Model (MySQL) + RoleType enum
│ │ ├── dto/
│ │ ├── handler/ # Login, Register, CRUD
│ │ ├── repository/
│ │ └── service/
│ │
│ └── webhook/
│ ├── dto/
│ ├── handler/ # get_proxy, proxy_rotation_result,
│ │ # update_search_result, update_search_anchor_text
│ └── service/
│
├── pkg/ # Shared utilities
│ ├── env.go # EnvStruct + LoadEnv()
│ ├── api/ # HTTP clients gọi external APIs
│ ├── apperror/ # Custom error types
│ ├── cache/ # Redis wrapper
│ ├── database/ # DB connections (MySQL + MongoDB)
│ ├── excel/ # Excel reader (import proxy)
│ ├── jwt/ # JWT create & parse
│ ├── logger/ # Zap logger + lumberjack rotation
│ ├── messages/ # Standard response format
│ ├── middleware/ # JWT, Webhook, Error middlewares
│ ├── scheduler/ # gocron wrapper
│ ├── telegram/ # Telegram notifications
│ └── validate/ # Request validation
│
├── common/
│ ├── const.go
│ └── paging.go # Pagination struct
│
├── go.mod # Go module
├── .env.example
└── docker-compose.yaml| Route Group | Auth Method |
|---|---|
GET / | Public |
POST /api/user/login | Public |
POST /api/user/register | Public |
**/api/** | JWT Bearer Token (Authorization: Bearer <token>) |
**/search/** | API Key Header (API_SEARCH_TOKEN) |
**/anchor-text/** | API Key Header (API_SEARCH_TOKEN) |
**/webhook/** | Webhook Key Header (WEBHOOK_KEY) |