mirror of
https://github.com/yusing/godoxy.git
synced 2026-04-10 10:53:36 +02:00
docs: add per package README for implementation details (AI generated with human review)
This commit is contained in:
337
internal/maxmind/README.md
Normal file
337
internal/maxmind/README.md
Normal file
@@ -0,0 +1,337 @@
|
||||
# MaxMind
|
||||
|
||||
The maxmind package provides MaxMind GeoIP database integration for IP geolocation, including automatic database downloading and updates.
|
||||
|
||||
## Overview
|
||||
|
||||
The maxmind package implements MaxMind GeoIP database management, providing IP geolocation lookups for country and city information. It supports automatic database downloading, scheduled updates, and thread-safe access.
|
||||
|
||||
### Key Features
|
||||
|
||||
- MaxMind GeoIP database loading
|
||||
- Automatic database downloading from MaxMind
|
||||
- Scheduled updates every 24 hours
|
||||
- City lookup with cache support
|
||||
- IP geolocation (country, city, timezone)
|
||||
- Thread-safe access
|
||||
|
||||
## Architecture
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
A[MaxMind Config] --> B[Load Database]
|
||||
B --> C{Exists?}
|
||||
C -->|No| D[Download]
|
||||
C -->|Yes| E[Load]
|
||||
D --> F[Extract from TarGz]
|
||||
E --> G[Open Reader]
|
||||
|
||||
H[IP Lookup] --> I[City Lookup]
|
||||
I --> J{Cache Hit?}
|
||||
J -->|Yes| K[Return Cached]
|
||||
J -->|No| L[Database Query]
|
||||
L --> M[Cache Result]
|
||||
M --> K
|
||||
|
||||
N[Update Scheduler] --> O[Check Daily]
|
||||
O --> P{Different?}
|
||||
P -->|Yes| Q[Download Update]
|
||||
P -->|No| O
|
||||
```
|
||||
|
||||
## Core Components
|
||||
|
||||
### MaxMind Structure
|
||||
|
||||
```go
|
||||
type MaxMind struct {
|
||||
*Config
|
||||
lastUpdate time.Time
|
||||
db struct {
|
||||
*maxminddb.Reader
|
||||
sync.RWMutex
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
```go
|
||||
type Config struct {
|
||||
Database string // Database type (GeoLite2 or GeoIP2)
|
||||
AccountID int
|
||||
LicenseKey Secret
|
||||
}
|
||||
```
|
||||
|
||||
### IP Information
|
||||
|
||||
```go
|
||||
type IPInfo struct {
|
||||
IP net.IP
|
||||
Str string
|
||||
Country *Country
|
||||
City *City
|
||||
Location *Location
|
||||
}
|
||||
|
||||
type Country struct {
|
||||
IsoCode string
|
||||
Name string
|
||||
}
|
||||
|
||||
type City struct {
|
||||
Country *Country
|
||||
Name string
|
||||
Location *Location
|
||||
}
|
||||
|
||||
type Location struct {
|
||||
TimeZone string
|
||||
Latitude float64
|
||||
Longitude float64
|
||||
}
|
||||
```
|
||||
|
||||
## Public API
|
||||
|
||||
### Initialization
|
||||
|
||||
```go
|
||||
// LoadMaxMindDB loads or downloads the MaxMind database.
|
||||
func (cfg *MaxMind) LoadMaxMindDB(parent task.Parent) gperr.Error
|
||||
```
|
||||
|
||||
### Lookup
|
||||
|
||||
```go
|
||||
// LookupCity looks up city information for an IP.
|
||||
func LookupCity(info *IPInfo) (city *City, ok bool)
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Setup
|
||||
|
||||
```go
|
||||
maxmindCfg := &maxmind.Config{
|
||||
Database: maxmind.MaxMindGeoLite,
|
||||
AccountID: 123456,
|
||||
LicenseKey: "your-license-key",
|
||||
}
|
||||
|
||||
err := maxmindCfg.LoadMaxMindDB(parent)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
```
|
||||
|
||||
### IP Lookup
|
||||
|
||||
```go
|
||||
// Create IP info
|
||||
ipInfo := &maxmind.IPInfo{
|
||||
IP: net.ParseIP("8.8.8.8"),
|
||||
Str: "8.8.8.8",
|
||||
}
|
||||
|
||||
// Lookup city
|
||||
city, ok := maxmind.LookupCity(ipInfo)
|
||||
if ok {
|
||||
fmt.Printf("Country: %s\n", city.Country.IsoCode)
|
||||
fmt.Printf("City: %s\n", city.Name)
|
||||
fmt.Printf("Timezone: %s\n", city.Location.TimeZone)
|
||||
}
|
||||
```
|
||||
|
||||
### Database Types
|
||||
|
||||
```go
|
||||
const (
|
||||
MaxMindGeoLite = "GeoLite2-Country"
|
||||
MaxMindGeoIP2 = "GeoIP2-Country"
|
||||
)
|
||||
```
|
||||
|
||||
## Data Flow
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Config
|
||||
participant MaxMind
|
||||
participant Database
|
||||
participant Cache
|
||||
participant UpdateScheduler
|
||||
|
||||
Config->>MaxMind: LoadMaxMindDB()
|
||||
MaxMind->>Database: Open()
|
||||
alt Database Missing
|
||||
MaxMind->>MaxMind: Download()
|
||||
MaxMind->>Database: Extract & Create
|
||||
end
|
||||
Database-->>MaxMind: Reader
|
||||
|
||||
Note over MaxMind: Start Update Scheduler
|
||||
|
||||
loop Every 24 Hours
|
||||
UpdateScheduler->>MaxMind: Check Update
|
||||
MaxMind->>MaxMind: Check Last-Modified
|
||||
alt Update Available
|
||||
MaxMind->>MaxMind: Download
|
||||
MaxMind->>Database: Replace
|
||||
end
|
||||
end
|
||||
|
||||
participant Lookup
|
||||
Lookup->>MaxMind: LookupCity(ip)
|
||||
MaxMind->>Cache: Check
|
||||
alt Cache Hit
|
||||
Cache-->>Lookup: City Info
|
||||
else Cache Miss
|
||||
MaxMind->>Database: Query
|
||||
Database-->>MaxMind: City Info
|
||||
MaxMind->>Cache: Store
|
||||
MaxMind-->>Lookup: City Info
|
||||
end
|
||||
```
|
||||
|
||||
## Database Download
|
||||
|
||||
### Download Process
|
||||
|
||||
```go
|
||||
func (cfg *MaxMind) download() error {
|
||||
resp, err := cfg.doReq(http.MethodGet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Read response
|
||||
databaseGZ, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Extract from tar.gz
|
||||
err = extractFileFromTarGz(databaseGZ, cfg.dbFilename(), tmpDBPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Validate
|
||||
db, err := maxmindDBOpen(tmpDBPath)
|
||||
if err != nil {
|
||||
os.Remove(tmpDBPath)
|
||||
return err
|
||||
}
|
||||
db.Close()
|
||||
|
||||
// Rename to final location
|
||||
os.Rename(tmpDBPath, dbFile)
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
### Security Checks
|
||||
|
||||
The download process includes tar bomb protection:
|
||||
|
||||
```go
|
||||
sumSize := int64(0)
|
||||
for {
|
||||
hdr, err := tr.Next()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
sumSize += hdr.Size
|
||||
if sumSize > 30*1024*1024 {
|
||||
return errors.New("file size exceeds 30MB")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Update Scheduling
|
||||
|
||||
```go
|
||||
func (cfg *MaxMind) scheduleUpdate(parent task.Parent) {
|
||||
task := parent.Subtask("maxmind_schedule_update", true)
|
||||
ticker := time.NewTicker(updateInterval) // 24 hours
|
||||
|
||||
cfg.loadLastUpdate()
|
||||
cfg.update()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-task.Context().Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
cfg.update()
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Thread Safety
|
||||
|
||||
The database uses a read-write mutex:
|
||||
|
||||
```go
|
||||
type MaxMind struct {
|
||||
*Config
|
||||
db struct {
|
||||
*maxminddb.Reader
|
||||
sync.RWMutex
|
||||
}
|
||||
}
|
||||
|
||||
// Lookups use RLock
|
||||
func (cfg *MaxMind) lookup(ip net.IP) (*maxminddb.City, error) {
|
||||
cfg.db.RLock()
|
||||
defer cfg.db.RUnlock()
|
||||
return cfg.db.Lookup(ip)
|
||||
}
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Environment Variables
|
||||
|
||||
| Variable | Description |
|
||||
| --------------------- | ------------------- |
|
||||
| `MAXMIND_ACCOUNT_ID` | MaxMind account ID |
|
||||
| `MAXMIND_LICENSE_KEY` | MaxMind license key |
|
||||
|
||||
### YAML Configuration
|
||||
|
||||
```yaml
|
||||
providers:
|
||||
maxmind:
|
||||
database: geolite2
|
||||
account_id: 123456
|
||||
license_key: your-license-key
|
||||
```
|
||||
|
||||
## Integration Points
|
||||
|
||||
The maxmind package integrates with:
|
||||
|
||||
- **ACL**: IP-based access control (country/timezone matching)
|
||||
- **Config**: Configuration management
|
||||
- **Logging**: Update notifications
|
||||
- **City Cache**: IP geolocation caching
|
||||
|
||||
## Error Handling
|
||||
|
||||
```go
|
||||
var (
|
||||
ErrResponseNotOK = gperr.New("response not OK")
|
||||
ErrDownloadFailure = gperr.New("download failure")
|
||||
)
|
||||
```
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
- 24-hour update interval reduces unnecessary downloads
|
||||
- Database size ~10-30MB
|
||||
- City lookup cache reduces database queries
|
||||
- RLock for concurrent reads
|
||||
Reference in New Issue
Block a user