A high-performance Go-based resource distribution backend service for managing versioned software releases with support for both full and incremental updates, multi-region CDN distribution, and flexible version comparison strategies.
- Versioned Resource Management - Manage software resources with semantic versioning or datetime-based versioning
- Full & Incremental Updates - Support both complete package distribution and delta updates
- Multi-Region CDN - Weighted region-based distribution with configurable CDN/direct download ratios
- Release Channels - Separate stable/beta/alpha channels for gradual rollouts
- Platform Support - Multi-platform (OS/Arch) package management
- Caching Layer - Ristretto in-memory cache with singleflight pattern for high performance
- Async Task Queue - Asynq-based background job processing
- Health Monitoring - Prometheus metrics and health check endpoints
- Service Discovery - Optional Consul integration for cluster mode
- Framework: Fiber v2 (high-performance HTTP framework)
- Database: MySQL with Ent ORM (Facebook's entity framework)
- Cache: Redis + Ristretto (in-memory)
- Task Queue: Asynq (Redis-backed)
- DI: Google Wire (compile-time dependency injection)
- Logging: Uber Zap
- JSON: Sonic (ByteDance's high-performance JSON library)
- Version Parsing: SemVer, DateTime formats (pluggable parser system)
- Go 1.25.3+
- MySQL 8.0+
- Redis 6.0+
- (Optional) Consul for service discovery
git clone https://github.com/MirrorChyan/resource-backend.git
cd resource-backendEdit config/config.yaml:
instance:
port: 8000
only_local: true # Set to false for cluster mode
database:
host: "localhost"
port: 3306
username: "root"
password: "your_password"
name: "resource_db"
redis:
addr: "localhost:6379"
db: 0
asynq_db: 1For cluster mode, set environment variables:
export INSTANCE_IP="your_instance_ip"
export SERVICE_ID="unique_service_id"
export REGION_ID="region_name" # Optional, defaults to "default"# Generate Ent ORM code
make entgen
# Generate Wire dependency injection code
make wiregen# Build binary
make build
# Or run directly
go run .The service will start on http://localhost:8000
GET /resources/:rid/latest?channel=stable&system=windows&arch=amd64¤t=1.0.0Query Parameters:
channel- Release channel:stable,beta,alphasystem- Operating system:windows,linux,darwin, etc.arch- Architecture:amd64,arm64,386, etc.current- Current version (optional, for incremental updates)cdk- CDK token (optional, for authentication)
Response:
{
"code": 0,
"data": {
"resource_id": "my-app",
"version_name": "1.2.0",
"version_number": 120,
"channel": "stable",
"update_type": "incremental",
"download_url": "https://cdn.example.com/...",
"release_note": "What's new in 1.2.0...",
"file_size": 1048576,
"file_hash": "sha256:abc123..."
}
}GET /resources/download/:keyRedirects to the actual download URL (CDN or direct).
HEAD /resources/download/:keyReturns file metadata headers:
Content-LengthContent-Type
POST /admin/resources
{
"id": "my-app",
"name": "My Application",
"description": "Description here",
"update_type": "incremental"
}GET /admin/resources/:rid/platforms
PUT /admin/resources/:rid/platforms {"os": "android", "arch": "arm64", "update_type": "full"}
DELETE /admin/resources/:rid/platforms?os=android&arch=arm64A platform with its own update type uses it; any other platform uses the resource's update_type.
For a platform with neither a prior upload nor its own setting, the first upload automatically
selects incremental updates for .zip and .tar.gz (case-insensitive), and full updates for
all other filenames (e.g. .exe, .dmg, .apk). Only ZIP and TAR.GZ currently support incremental
processing; .7z, .tar, and .tgz are delivered as full packages.
The upload callback saves this selection as a platform setting after file-type validation;
requesting an upload URL alone does not save it. The setting is saved before the processing task
is queued and stays if queuing fails: a retried callback of the same package goes through, a
package that needs the other mode is rejected. Later uploads keep the selected mode, and
admins can override it as usual. Platforms with prior uploads keep their existing behavior,
including uploads in another channel or packages that have been purged.
Queued uploads retain the processing mode accepted by their callback. Changing a platform to
incremental updates does not retroactively unpack full-mode uploads; version pairs without
file hashes fall back to full downloads.
GET lists the platforms packages were uploaded for and the ones with their own update type,
each with the type in effect and its source (inherit / override). PUT and DELETE return the
platform's update type before and after the change; DELETE returns the platform to the resource's.
os / arch take the same aliases as the version endpoints (e.g. x64 → amd64).
POST /resources/:rid/versions
Authorization: Bearer <token>
Content-Type: multipart/form-data
version_name=1.0.0
channel=stable
system=windows
arch=amd64
file=@package.zip
old_version=0.9.0 # For incremental updatesPUT /resources/:rid/versions/release-note
Authorization: Bearer <token>
{
"version_name": "1.0.0",
"channel": "stable",
"content": "Release notes..."
}PUT /resources/:rid/versions/custom-data
Authorization: Bearer <token>
{
"version_name": "1.0.0",
"channel": "stable",
"content": "{\"custom\": \"json data\"}"
}GET /healthGET /metricsmain.go
↓
internal/application/app.go (Adapter pattern)
↓
internal/interfaces/rest (HTTP handlers)
↓
internal/logic (Business logic)
↓
internal/repo (Data access)
↓
internal/ent (ORM entities)
Resource (Top-level entity)
id- Resource identifiername- Display namedescription- Descriptionupdate_type- Default update strategy (full/incremental)
ResourcePlatformConfig (Per-platform settings of a resource)
resource_id,os,arch- The platform, unique per resource;""is the platform-independent package, not a wildcardupdate_type- Update strategy of the platform; a platform without a row uses the resource's
Version (Release versions)
channel- Release channel (stable/beta/alpha)name- Version string (e.g., "1.0.0")number- Numeric version for comparisonrelease_note- Changelogcustom_data- Arbitrary JSON data
Storage (Platform-specific packages)
update_type- Full or incrementalos- Operating systemarch- Architecturepackage_path- File locationpackage_hash_sha256- Package checksumfile_type- Archive format (zip, tgz, etc.)file_size- Size in bytesfile_hashes- Hash map of files (for full updates)old_version- Source version (for incremental updates)
- Single instance deployment
- Configuration from local YAML files
- No service discovery
- Multi-instance deployment
- Consul service discovery
- Remote configuration management
- Health check registration
- Automatic service deregistration on shutdown
# Run all tests
go test ./...
# Run specific test
go test ./internal/pkg/vercomp/vercomp_test.go
# Run with race detector
go test -race ./...After modifying schemas or dependencies:
# Regenerate Ent ORM code
make entgen
# Regenerate Wire DI code
make wiregen
# Build
make buildresource-backend/
├── cmd/ # (Optional) Command-line tools
├── config/ # Configuration files
│ ├── config.yaml # Default config
│ ├── config.local.yaml # Local overrides
│ └── config.cluster.yaml # Cluster config
├── internal/
│ ├── application/ # App lifecycle management
│ ├── cache/ # Cache implementations
│ ├── config/ # Configuration loading
│ ├── db/ # Database connections
│ ├── ent/ # Ent ORM (generated)
│ │ └── schema/ # Database schemas
│ ├── interfaces/ # External interfaces
│ │ └── rest/ # HTTP handlers & routing
│ ├── logic/ # Business logic
│ ├── middleware/ # HTTP middleware
│ ├── model/ # Data models & DTOs
│ ├── oss/ # Object storage integration
│ ├── pkg/ # Utility packages
│ │ ├── archiver/ # Archive operations
│ │ ├── filehash/ # File hashing
│ │ ├── fileops/ # File operations
│ │ ├── patcher/ # Incremental patching
│ │ ├── validator/ # Request validation
│ │ └── vercomp/ # Version comparison
│ ├── repo/ # Data repositories
│ ├── tasks/ # Async task queue
│ └── wire/ # Dependency injection
├── bin/ # Build output
├── main.go # Application entry point
├── Makefile # Build commands
└── CLAUDE.md # AI development guide
The service supports pluggable version parsers:
1.0.0 < 1.0.1 < 1.1.0 < 2.0.0
v1.0.0 (automatically strips 'v' prefix)
2024-01-15 < 2024-01-16
2024-01-15T10:30:00Z
20240115103000
Implement the Parser interface in internal/pkg/vercomp/:
type Parser interface {
Name() string
CanParse(version string) bool
Parse(version string) (interface{}, error)
Compare(a, b interface{}) int
}The service supports weighted multi-region CDN distribution:
extra:
cdn_prefix: "https://cdn.example.com"
distribute_cdn_ratio: 70 # 70% CDN, 30% direct
distribute_cdn_region: ["default", "hk", "kr"]
download_prefix_info:
default:
- url: "https://download1.example.com"
weight: 1
- url: "https://download2.example.com"
weight: 2
hk:
- url: "https://hk-download.example.com"
weight: 1- Client requests
/resources/:rid/latest - Server generates temporary download key (10-minute TTL)
- Server selects distribution method:
- CDN: Generates authenticated URL with MD5 token
- Direct: Selects region based on weighted random
- Client receives download URL
- Client uses HEAD request to check file size before download
Available at /metrics:
- HTTP request counts and durations
- Go runtime metrics (goroutines, memory, GC)
- Custom business metrics
Available at /health:
{
"status": "ok"
}Structured logging with Zap:
- Request/response logging (via fiberzap middleware)
- Error tracking with context
- Configurable log levels (debug/info/warn/error)
- Authentication: Bearer token validation via middleware
- Rate Limiting: Configurable download limits
- CDN Token: MD5-based authentication for CDN URLs
- Path Validation: Prevents directory traversal attacks
- CORS: Configurable cross-origin policies
- Connection Pooling: MySQL (100 max, 50 idle, 25min lifetime)
- In-Memory Cache: Ristretto with singleflight pattern
- Redis Cache: Multi-level caching for version metadata
- Distributed Locks: Redsync for concurrent operations
- Async Processing: Background tasks via Asynq
- JSON Performance: Sonic encoder/decoder
- HTTP Performance: Fiber framework with fasthttp
FROM golang:1.25-alpine AS builder
WORKDIR /app
COPY . .
RUN go mod download
RUN make entgen && make wiregen && make build
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /app/bin/app .
COPY --from=builder /app/config ./config
CMD ["./app"]INSTANCE_IP=10.0.1.5
SERVICE_ID=resource-backend-1
REGION_ID=us-westSchema auto-migration on startup:
if err := mysql.Schema.Create(context.Background()); err != nil {
// Handle error
}For production, consider using migration tools like atlas or migrate.
Q: Build fails with "package X is not in std" A: Ensure Go version 1.25.3+ is installed and GOROOT is set correctly.
Q: Wire generation fails
A: Run go install github.com/google/wire/cmd/wire@latest first.
Q: Database connection fails
A: Check MySQL is running and credentials in config/config.yaml are correct.
Q: Redis connection fails
A: Verify Redis is running on the configured address (redis.addr).
Q: Consul registration fails (cluster mode)
A: Ensure INSTANCE_IP and SERVICE_ID environment variables are set.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Run code generation if needed (
make entgen && make wiregen) - Run tests (
go test ./...) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is licensed under the GNU Affero General Public License v3.0 (AGPL-3.0) - see the LICENSE.md file for details.
- ✅ Commercial use
- ✅ Modification
- ✅ Distribution
- ✅ Private use
⚠️ Network use is distribution - If you run a modified version on a server, you must make the source code available to users⚠️ Disclose source⚠️ License and copyright notice⚠️ Same license (copyleft)⚠️ State changes
For commercial licensing or questions, please contact the project maintainers.
- GitHub: https://github.com/MirrorChyan/resource-backend
- Issues: GitHub Issues