Initial commit: Material Texture API service
- Go + Gin + GORM + PostgreSQL backend - RESTful API for material management - Docker deployment support - Database partitioning for billion-scale data - API documentation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
8
.env.example
Normal file
8
.env.example
Normal file
@@ -0,0 +1,8 @@
|
||||
# 服务器配置
|
||||
SERVER_PORT=8080
|
||||
|
||||
# 数据库配置
|
||||
DATABASE_URL=postgres://postgres:postgres@localhost:5432/material_db?sslmode=disable
|
||||
|
||||
# API认证Token
|
||||
API_TOKEN=seatons3d
|
||||
31
.gitignore
vendored
Normal file
31
.gitignore
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
# 二进制文件
|
||||
bin/
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# 测试覆盖
|
||||
*.out
|
||||
coverage.html
|
||||
|
||||
# 依赖目录
|
||||
vendor/
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# 环境变量
|
||||
.env
|
||||
.env.local
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
|
||||
# Docker
|
||||
*.log
|
||||
35
Dockerfile
Normal file
35
Dockerfile
Normal file
@@ -0,0 +1,35 @@
|
||||
# 构建阶段
|
||||
FROM golang:1.23-alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# 安装必要的构建工具
|
||||
RUN apk add --no-cache git
|
||||
|
||||
# 复制go.mod和go.sum
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
|
||||
# 复制源代码
|
||||
COPY . .
|
||||
|
||||
# 构建
|
||||
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o /app/server ./cmd/server
|
||||
|
||||
# 运行阶段
|
||||
FROM alpine:3.18
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# 安装必要的运行时依赖
|
||||
RUN apk add --no-cache ca-certificates tzdata
|
||||
|
||||
# 从构建阶段复制二进制文件
|
||||
COPY --from=builder /app/server /app/server
|
||||
|
||||
# 设置时区
|
||||
ENV TZ=Asia/Shanghai
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
CMD ["/app/server"]
|
||||
68
Makefile
Normal file
68
Makefile
Normal file
@@ -0,0 +1,68 @@
|
||||
.PHONY: build run dev docker-build docker-up docker-down docker-logs clean test
|
||||
|
||||
# 本地开发
|
||||
build:
|
||||
go build -o bin/server ./cmd/server
|
||||
|
||||
run: build
|
||||
./bin/server
|
||||
|
||||
dev:
|
||||
go run ./cmd/server
|
||||
|
||||
# 依赖管理
|
||||
deps:
|
||||
go mod tidy
|
||||
go mod download
|
||||
|
||||
# Docker 操作
|
||||
docker-build:
|
||||
docker compose build --no-cache
|
||||
|
||||
docker-up:
|
||||
docker compose up -d
|
||||
|
||||
docker-down:
|
||||
docker compose down
|
||||
|
||||
docker-logs:
|
||||
docker compose logs -f
|
||||
|
||||
docker-restart:
|
||||
docker compose down && docker compose up -d
|
||||
|
||||
# 清理
|
||||
clean:
|
||||
rm -rf bin/
|
||||
docker compose down -v
|
||||
|
||||
# 测试
|
||||
test:
|
||||
go test -v ./...
|
||||
|
||||
# 数据库迁移(本地开发用)
|
||||
migrate:
|
||||
go run ./cmd/server migrate
|
||||
|
||||
# 格式化
|
||||
fmt:
|
||||
go fmt ./...
|
||||
|
||||
# 检查
|
||||
lint:
|
||||
golangci-lint run
|
||||
|
||||
# 帮助
|
||||
help:
|
||||
@echo "Available commands:"
|
||||
@echo " make build - Build the binary"
|
||||
@echo " make run - Build and run locally"
|
||||
@echo " make dev - Run with go run (development)"
|
||||
@echo " make deps - Download dependencies"
|
||||
@echo " make docker-build - Build Docker image"
|
||||
@echo " make docker-up - Start Docker containers"
|
||||
@echo " make docker-down - Stop Docker containers"
|
||||
@echo " make docker-logs - View Docker logs"
|
||||
@echo " make clean - Clean build artifacts and volumes"
|
||||
@echo " make test - Run tests"
|
||||
@echo " make fmt - Format code"
|
||||
37
cmd/server/main.go
Normal file
37
cmd/server/main.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"material_texture/internal/config"
|
||||
"material_texture/internal/models"
|
||||
"material_texture/internal/router"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 加载配置
|
||||
cfg := config.Load()
|
||||
|
||||
// 连接数据库
|
||||
db := config.NewDatabase(cfg)
|
||||
|
||||
// 自动迁移表结构
|
||||
if err := db.AutoMigrate(&models.Material{}, &models.MaterialBinding{}); err != nil {
|
||||
log.Fatalf("Failed to migrate database: %v", err)
|
||||
}
|
||||
|
||||
// 添加唯一约束(GORM的uniqueIndex在某些情况下可能不生效)
|
||||
db.Exec(`CREATE UNIQUE INDEX IF NOT EXISTS idx_unique_material_group
|
||||
ON material_bindings(material_id, group_id)`)
|
||||
|
||||
log.Println("Database migrated successfully")
|
||||
|
||||
// 设置路由
|
||||
r := router.Setup(db, cfg)
|
||||
|
||||
// 启动服务器
|
||||
log.Printf("Server starting on port %s", cfg.ServerPort)
|
||||
if err := r.Run(":" + cfg.ServerPort); err != nil {
|
||||
log.Fatalf("Failed to start server: %v", err)
|
||||
}
|
||||
}
|
||||
190
deploy.sh
Executable file
190
deploy.sh
Executable file
@@ -0,0 +1,190 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Material Texture API 部署脚本
|
||||
# 用法: ./deploy.sh [命令]
|
||||
# 命令:
|
||||
# pack - 打包项目用于传输
|
||||
# server - 在服务器上执行部署
|
||||
# help - 显示帮助
|
||||
|
||||
set -e
|
||||
|
||||
PROJECT_NAME="material_texture"
|
||||
DEPLOY_DIR="deploy_package"
|
||||
ARCHIVE_NAME="${PROJECT_NAME}_deploy.tar.gz"
|
||||
|
||||
# 颜色输出
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
log_info() {
|
||||
echo -e "${GREEN}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
log_warn() {
|
||||
echo -e "${YELLOW}[WARN]${NC} $1"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# 本地打包(在开发机器上执行)
|
||||
pack() {
|
||||
log_info "开始打包项目..."
|
||||
|
||||
# 清理旧文件
|
||||
rm -rf "$DEPLOY_DIR" "$ARCHIVE_NAME"
|
||||
|
||||
# 创建部署目录
|
||||
mkdir -p "$DEPLOY_DIR"
|
||||
|
||||
# 复制必要文件(排除不需要的)
|
||||
rsync -av \
|
||||
--exclude='bin/' \
|
||||
--exclude='.git/' \
|
||||
--exclude='*.log' \
|
||||
--exclude='.env' \
|
||||
--exclude='.env.local' \
|
||||
--exclude='.DS_Store' \
|
||||
--exclude='deploy_package/' \
|
||||
--exclude='*.tar.gz' \
|
||||
. "$DEPLOY_DIR/"
|
||||
|
||||
# 打包
|
||||
tar -czvf "$ARCHIVE_NAME" "$DEPLOY_DIR"
|
||||
|
||||
# 清理临时目录
|
||||
rm -rf "$DEPLOY_DIR"
|
||||
|
||||
# 显示文件大小
|
||||
SIZE=$(du -h "$ARCHIVE_NAME" | cut -f1)
|
||||
log_info "打包完成: $ARCHIVE_NAME ($SIZE)"
|
||||
log_info ""
|
||||
log_info "下一步: 将文件传输到服务器"
|
||||
echo " scp $ARCHIVE_NAME user@your-server:/path/to/deploy/"
|
||||
log_info ""
|
||||
log_info "然后在服务器上执行:"
|
||||
echo " tar -xzvf $ARCHIVE_NAME"
|
||||
echo " cd $DEPLOY_DIR"
|
||||
echo " ./deploy.sh server"
|
||||
}
|
||||
|
||||
# 服务器部署(在服务器上执行)
|
||||
server_deploy() {
|
||||
log_info "开始服务器部署..."
|
||||
|
||||
# 检查 Docker
|
||||
if ! command -v docker &> /dev/null; then
|
||||
log_error "Docker 未安装,请先安装 Docker"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 检查 docker compose
|
||||
if ! docker compose version &> /dev/null; then
|
||||
log_error "Docker Compose 未安装或版本过低"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 创建 .env 文件(如果不存在)
|
||||
if [ ! -f .env ]; then
|
||||
log_info "创建 .env 配置文件..."
|
||||
cp .env.example .env
|
||||
log_warn "请编辑 .env 文件设置生产环境密钥"
|
||||
log_warn " vim .env"
|
||||
log_warn "然后重新运行: ./deploy.sh server"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# 停止旧容器(如果存在)
|
||||
log_info "停止旧容器..."
|
||||
docker compose down 2>/dev/null || true
|
||||
|
||||
# 构建并启动
|
||||
log_info "构建并启动服务..."
|
||||
docker compose up -d --build
|
||||
|
||||
# 等待服务启动
|
||||
log_info "等待服务启动..."
|
||||
sleep 5
|
||||
|
||||
# 健康检查
|
||||
log_info "执行健康检查..."
|
||||
if curl -s http://localhost:8081/health > /dev/null; then
|
||||
log_info "服务启动成功!"
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo " API 地址: http://localhost:8081"
|
||||
echo " 健康检查: http://localhost:8081/health"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
log_info "查看日志: docker compose logs -f"
|
||||
else
|
||||
log_error "健康检查失败,请查看日志: docker compose logs"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 显示帮助
|
||||
show_help() {
|
||||
echo "Material Texture API 部署脚本"
|
||||
echo ""
|
||||
echo "用法: ./deploy.sh [命令]"
|
||||
echo ""
|
||||
echo "命令:"
|
||||
echo " pack 在本地机器打包项目用于传输"
|
||||
echo " server 在服务器上执行部署"
|
||||
echo " logs 查看容器日志"
|
||||
echo " stop 停止服务"
|
||||
echo " restart 重启服务"
|
||||
echo " help 显示此帮助信息"
|
||||
echo ""
|
||||
echo "部署流程:"
|
||||
echo " 1. 本地: ./deploy.sh pack"
|
||||
echo " 2. 传输: scp material_texture_deploy.tar.gz user@server:/path/"
|
||||
echo " 3. 服务器: tar -xzvf material_texture_deploy.tar.gz"
|
||||
echo " 4. 服务器: cd deploy_package && ./deploy.sh server"
|
||||
}
|
||||
|
||||
# 日志
|
||||
logs() {
|
||||
docker compose logs -f
|
||||
}
|
||||
|
||||
# 停止
|
||||
stop() {
|
||||
log_info "停止服务..."
|
||||
docker compose down
|
||||
log_info "服务已停止"
|
||||
}
|
||||
|
||||
# 重启
|
||||
restart() {
|
||||
log_info "重启服务..."
|
||||
docker compose restart
|
||||
log_info "服务已重启"
|
||||
}
|
||||
|
||||
# 主入口
|
||||
case "${1:-help}" in
|
||||
pack)
|
||||
pack
|
||||
;;
|
||||
server)
|
||||
server_deploy
|
||||
;;
|
||||
logs)
|
||||
logs
|
||||
;;
|
||||
stop)
|
||||
stop
|
||||
;;
|
||||
restart)
|
||||
restart
|
||||
;;
|
||||
help|*)
|
||||
show_help
|
||||
;;
|
||||
esac
|
||||
44
docker-compose.yml
Normal file
44
docker-compose.yml
Normal file
@@ -0,0 +1,44 @@
|
||||
services:
|
||||
app:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
ports:
|
||||
- "8081:8080"
|
||||
environment:
|
||||
- SERVER_PORT=8080
|
||||
- DATABASE_URL=postgres://postgres:postgres@db:5432/material_db?sslmode=disable
|
||||
- API_TOKEN=${API_TOKEN:-seatons3d}
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- material-network
|
||||
|
||||
db:
|
||||
image: postgres:15-alpine
|
||||
environment:
|
||||
- POSTGRES_USER=postgres
|
||||
- POSTGRES_PASSWORD=postgres
|
||||
- POSTGRES_DB=material_db
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
- ./migrations:/docker-entrypoint-initdb.d:ro
|
||||
ports:
|
||||
- "5433:5432"
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- material-network
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
|
||||
networks:
|
||||
material-network:
|
||||
driver: bridge
|
||||
569
docs/API.md
Normal file
569
docs/API.md
Normal file
@@ -0,0 +1,569 @@
|
||||
# 材质管理系统 API 文档
|
||||
|
||||
## 基础信息
|
||||
|
||||
- **Base URL**: `http://10.99.98.248:8081/api/v1`
|
||||
- **认证方式**: API Token
|
||||
- **Content-Type**: `application/json`
|
||||
|
||||
## 认证
|
||||
|
||||
所有API请求需要在Header中携带Token:
|
||||
|
||||
```
|
||||
X-API-Token: seatons3d
|
||||
```
|
||||
|
||||
错误响应示例:
|
||||
```json
|
||||
{
|
||||
"code": 401,
|
||||
"message": "missing API token"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 通用响应格式
|
||||
|
||||
### 成功响应
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
"data": { ... }
|
||||
}
|
||||
```
|
||||
|
||||
### 分页响应
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"items": [...],
|
||||
"total": 303,
|
||||
"page": 1,
|
||||
"page_size": 20
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 错误响应
|
||||
```json
|
||||
{
|
||||
"code": 400,
|
||||
"message": "error description"
|
||||
}
|
||||
```
|
||||
|
||||
### 错误码
|
||||
| 状态码 | 说明 |
|
||||
|--------|------|
|
||||
| 200 | 成功 |
|
||||
| 201 | 创建成功 |
|
||||
| 400 | 请求参数错误 |
|
||||
| 401 | 未授权(Token无效) |
|
||||
| 404 | 资源不存在 |
|
||||
| 500 | 服务器内部错误 |
|
||||
|
||||
---
|
||||
|
||||
## 材质管理接口
|
||||
|
||||
### 1. 获取材质列表
|
||||
|
||||
**GET** `/materials`
|
||||
|
||||
#### 请求参数(Query)
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| page | int | 否 | 页码,默认1 |
|
||||
| page_size | int | 否 | 每页数量,默认20,最大100 |
|
||||
| name | string | 否 | 按名称模糊搜索 |
|
||||
|
||||
#### 请求示例
|
||||
```bash
|
||||
curl -H "X-API-Token: seatons3d" \
|
||||
"http://10.99.98.248:8081/api/v1/materials?page=1&page_size=10&name=red"
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"items": [
|
||||
{
|
||||
"id": 4,
|
||||
"name": "dgnColor4",
|
||||
"diffuse_r": 255,
|
||||
"diffuse_g": 0,
|
||||
"diffuse_b": 0,
|
||||
"alpha": 255,
|
||||
"shininess": 20,
|
||||
"specular_r": 230,
|
||||
"specular_g": 230,
|
||||
"specular_b": 230,
|
||||
"ambient_r": 50,
|
||||
"ambient_g": 50,
|
||||
"ambient_b": 50,
|
||||
"metallic": 0,
|
||||
"roughness": 0,
|
||||
"reflectance": 0,
|
||||
"created_at": "2025-12-09T09:53:33.209555Z",
|
||||
"updated_at": "2025-12-09T09:53:33.209555Z"
|
||||
}
|
||||
],
|
||||
"total": 303,
|
||||
"page": 1,
|
||||
"page_size": 10
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. 添加材质
|
||||
|
||||
**POST** `/materials`
|
||||
|
||||
#### 请求Body
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| name | string | 是 | 材质名称 |
|
||||
| diffuse_r | float | 否 | 漫反射-红 (0-255) |
|
||||
| diffuse_g | float | 否 | 漫反射-绿 (0-255) |
|
||||
| diffuse_b | float | 否 | 漫反射-蓝 (0-255) |
|
||||
| alpha | float | 否 | 透明度 (0-255) |
|
||||
| shininess | float | 否 | 光泽度 |
|
||||
| specular_r | float | 否 | 高光-红 (0-255) |
|
||||
| specular_g | float | 否 | 高光-绿 (0-255) |
|
||||
| specular_b | float | 否 | 高光-蓝 (0-255) |
|
||||
| ambient_r | float | 否 | 环境光-红 (0-255) |
|
||||
| ambient_g | float | 否 | 环境光-绿 (0-255) |
|
||||
| ambient_b | float | 否 | 环境光-蓝 (0-255) |
|
||||
| metallic | float | 否 | 金属度 |
|
||||
| roughness | float | 否 | 粗糙度 |
|
||||
| reflectance | float | 否 | 反射率 |
|
||||
|
||||
#### 请求示例
|
||||
```bash
|
||||
curl -X POST -H "X-API-Token: seatons3d" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "红色金属",
|
||||
"diffuse_r": 255,
|
||||
"diffuse_g": 50,
|
||||
"diffuse_b": 50,
|
||||
"alpha": 255,
|
||||
"metallic": 0.8,
|
||||
"roughness": 0.2
|
||||
}' \
|
||||
"http://10.99.98.248:8081/api/v1/materials"
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"message": "created",
|
||||
"data": {
|
||||
"id": 305,
|
||||
"name": "红色金属",
|
||||
"diffuse_r": 255,
|
||||
"diffuse_g": 50,
|
||||
"diffuse_b": 50,
|
||||
"alpha": 255,
|
||||
"shininess": 0,
|
||||
"specular_r": 0,
|
||||
"specular_g": 0,
|
||||
"specular_b": 0,
|
||||
"ambient_r": 0,
|
||||
"ambient_g": 0,
|
||||
"ambient_b": 0,
|
||||
"metallic": 0.8,
|
||||
"roughness": 0.2,
|
||||
"reflectance": 0.5,
|
||||
"created_at": "2025-12-09T18:02:22.846141Z",
|
||||
"updated_at": "2025-12-09T18:02:22.846141Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. 获取材质详情
|
||||
|
||||
**GET** `/materials/:id`
|
||||
|
||||
#### 路径参数
|
||||
| 参数 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | int | 材质ID |
|
||||
|
||||
#### 请求示例
|
||||
```bash
|
||||
curl -H "X-API-Token: seatons3d" \
|
||||
"http://10.99.98.248:8081/api/v1/materials/4"
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"id": 4,
|
||||
"name": "dgnColor4",
|
||||
"diffuse_r": 255,
|
||||
"diffuse_g": 0,
|
||||
"diffuse_b": 0,
|
||||
"alpha": 255,
|
||||
"shininess": 20,
|
||||
"specular_r": 230,
|
||||
"specular_g": 230,
|
||||
"specular_b": 230,
|
||||
"ambient_r": 50,
|
||||
"ambient_g": 50,
|
||||
"ambient_b": 50,
|
||||
"metallic": 0,
|
||||
"roughness": 0,
|
||||
"reflectance": 0,
|
||||
"created_at": "2025-12-09T09:53:33.209555Z",
|
||||
"updated_at": "2025-12-09T09:53:33.209555Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 错误响应
|
||||
```json
|
||||
{
|
||||
"code": 404,
|
||||
"message": "material not found"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4. 编辑材质
|
||||
|
||||
**PUT** `/materials/:id`
|
||||
|
||||
#### 路径参数
|
||||
| 参数 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | int | 材质ID |
|
||||
|
||||
#### 请求Body
|
||||
同"添加材质"接口
|
||||
|
||||
#### 请求示例
|
||||
```bash
|
||||
curl -X PUT -H "X-API-Token: seatons3d" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "红色金属-更新版",
|
||||
"diffuse_r": 200,
|
||||
"diffuse_g": 100,
|
||||
"diffuse_b": 50,
|
||||
"alpha": 255,
|
||||
"metallic": 0.9,
|
||||
"roughness": 0.1
|
||||
}' \
|
||||
"http://10.99.98.248:8081/api/v1/materials/305"
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"id": 305,
|
||||
"name": "红色金属-更新版",
|
||||
"diffuse_r": 200,
|
||||
"diffuse_g": 100,
|
||||
"diffuse_b": 50,
|
||||
"alpha": 255,
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5. 删除材质
|
||||
|
||||
**DELETE** `/materials/:id`
|
||||
|
||||
#### 路径参数
|
||||
| 参数 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | int | 材质ID |
|
||||
|
||||
#### 请求示例
|
||||
```bash
|
||||
curl -X DELETE -H "X-API-Token: seatons3d" \
|
||||
"http://10.99.98.248:8081/api/v1/materials/305"
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"id": 305
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> **注意**: 删除材质会级联删除所有相关的绑定关系
|
||||
|
||||
---
|
||||
|
||||
## 绑定管理接口
|
||||
|
||||
### 6. 绑定材质到多个Group
|
||||
|
||||
**POST** `/materials/:id/bindings`
|
||||
|
||||
将一个材质绑定到多个叶子节点(group_id),支持幂等操作(重复绑定不会报错)。
|
||||
|
||||
#### 路径参数
|
||||
| 参数 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | int | 材质ID |
|
||||
|
||||
#### 请求Body
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| group_ids | string[] | 是 | 叶子节点ID数组 |
|
||||
|
||||
#### 请求示例
|
||||
```bash
|
||||
curl -X POST -H "X-API-Token: seatons3d" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"group_ids": ["node_001", "node_002", "node_003"]
|
||||
}' \
|
||||
"http://10.99.98.248:8081/api/v1/materials/4/bindings"
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"material_id": 4,
|
||||
"group_ids": ["node_001", "node_002", "node_003"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7. 解绑材质与Group
|
||||
|
||||
**DELETE** `/materials/:id/bindings`
|
||||
|
||||
#### 路径参数
|
||||
| 参数 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | int | 材质ID |
|
||||
|
||||
#### 请求Body
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| group_ids | string[] | 是 | 要解绑的叶子节点ID数组 |
|
||||
|
||||
#### 请求示例
|
||||
```bash
|
||||
curl -X DELETE -H "X-API-Token: seatons3d" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"group_ids": ["node_001", "node_002"]
|
||||
}' \
|
||||
"http://10.99.98.248:8081/api/v1/materials/4/bindings"
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"material_id": 4,
|
||||
"unbound": ["node_001", "node_002"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 8. 获取材质关联的所有Group(分页)
|
||||
|
||||
**GET** `/materials/:id/groups`
|
||||
|
||||
#### 路径参数
|
||||
| 参数 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | int | 材质ID |
|
||||
|
||||
#### 请求参数(Query)
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| page | int | 否 | 页码,默认1 |
|
||||
| page_size | int | 否 | 每页数量,默认20,最大100 |
|
||||
|
||||
#### 请求示例
|
||||
```bash
|
||||
curl -H "X-API-Token: seatons3d" \
|
||||
"http://10.99.98.248:8081/api/v1/materials/4/groups?page=1&page_size=10"
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
"data": {
|
||||
"items": [
|
||||
"20251021105717834754",
|
||||
"20251021105717834758",
|
||||
"20251021105717834762"
|
||||
],
|
||||
"total": 22,
|
||||
"page": 1,
|
||||
"page_size": 10
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 9. 根据Group IDs批量查询关联材质
|
||||
|
||||
**POST** `/groups/materials`
|
||||
|
||||
根据一个或多个叶子节点ID,查询它们关联的材质详情。
|
||||
|
||||
#### 请求Body
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| group_ids | string[] | 是 | 叶子节点ID数组 |
|
||||
|
||||
#### 请求示例
|
||||
```bash
|
||||
curl -X POST -H "X-API-Token: seatons3d" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"group_ids": ["202510211057245681447", "aDsIXMkjxdm2CDUj9gTvf"]
|
||||
}' \
|
||||
"http://10.99.98.248:8081/api/v1/groups/materials"
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
```json
|
||||
{
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
"data": [
|
||||
{
|
||||
"group_id": "202510211057245681447",
|
||||
"material": {
|
||||
"id": 4,
|
||||
"name": "dgnColor4",
|
||||
"diffuse_r": 255,
|
||||
"diffuse_g": 0,
|
||||
"diffuse_b": 0,
|
||||
"alpha": 255,
|
||||
"shininess": 20,
|
||||
"specular_r": 230,
|
||||
"specular_g": 230,
|
||||
"specular_b": 230,
|
||||
"ambient_r": 50,
|
||||
"ambient_g": 50,
|
||||
"ambient_b": 50,
|
||||
"metallic": 0,
|
||||
"roughness": 0,
|
||||
"reflectance": 0,
|
||||
"created_at": "2025-12-09T09:53:33.209555Z",
|
||||
"updated_at": "2025-12-09T09:53:33.209555Z"
|
||||
}
|
||||
},
|
||||
{
|
||||
"group_id": "aDsIXMkjxdm2CDUj9gTvf",
|
||||
"material": {
|
||||
"id": -1,
|
||||
"name": "默认值",
|
||||
"diffuse_r": 192,
|
||||
"diffuse_g": 192,
|
||||
"diffuse_b": 192,
|
||||
...
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
> **注意**: 如果某个group_id没有绑定材质,则不会出现在返回结果中
|
||||
|
||||
---
|
||||
|
||||
## 健康检查
|
||||
|
||||
**GET** `/health`
|
||||
|
||||
此接口无需认证。
|
||||
|
||||
#### 请求示例
|
||||
```bash
|
||||
curl http://10.99.98.248:8081/health
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
```json
|
||||
{
|
||||
"status": "ok"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 数据模型
|
||||
|
||||
### Material(材质)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | int64 | 材质ID(主键) |
|
||||
| name | string | 材质名称 |
|
||||
| diffuse_r | float64 | 漫反射-红 (0-255) |
|
||||
| diffuse_g | float64 | 漫反射-绿 (0-255) |
|
||||
| diffuse_b | float64 | 漫反射-蓝 (0-255) |
|
||||
| alpha | float64 | 透明度 (0-255) |
|
||||
| shininess | float64 | 光泽度 |
|
||||
| specular_r | float64 | 高光-红 (0-255) |
|
||||
| specular_g | float64 | 高光-绿 (0-255) |
|
||||
| specular_b | float64 | 高光-蓝 (0-255) |
|
||||
| ambient_r | float64 | 环境光-红 (0-255) |
|
||||
| ambient_g | float64 | 环境光-绿 (0-255) |
|
||||
| ambient_b | float64 | 环境光-蓝 (0-255) |
|
||||
| metallic | float64 | 金属度 |
|
||||
| roughness | float64 | 粗糙度 |
|
||||
| reflectance | float64 | 反射率 |
|
||||
| created_at | timestamp | 创建时间 |
|
||||
| updated_at | timestamp | 更新时间 |
|
||||
|
||||
### MaterialBinding(材质绑定)
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| id | int64 | 绑定ID(主键) |
|
||||
| material_id | int64 | 材质ID(外键) |
|
||||
| group_id | string | 叶子节点ID |
|
||||
| created_at | timestamp | 创建时间 |
|
||||
257
docs/DATABASE.md
Normal file
257
docs/DATABASE.md
Normal file
@@ -0,0 +1,257 @@
|
||||
# 数据库设计文档
|
||||
|
||||
## 概述
|
||||
|
||||
Material Texture 使用 PostgreSQL 数据库存储材质信息和绑定关系。
|
||||
|
||||
- **数据库**: PostgreSQL 15+
|
||||
- **ORM**: GORM v1.31
|
||||
- **特性**: 分区表、pg_trgm 模糊搜索
|
||||
|
||||
---
|
||||
|
||||
## ER 图
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
materials ||--o{ material_bindings : "has"
|
||||
|
||||
materials {
|
||||
bigint id PK "自增主键"
|
||||
varchar name "材质名称"
|
||||
float diffuse_r "漫反射-红"
|
||||
float diffuse_g "漫反射-绿"
|
||||
float diffuse_b "漫反射-蓝"
|
||||
float alpha "透明度"
|
||||
float shininess "光泽度"
|
||||
float specular_r "高光-红"
|
||||
float specular_g "高光-绿"
|
||||
float specular_b "高光-蓝"
|
||||
float ambient_r "环境光-红"
|
||||
float ambient_g "环境光-绿"
|
||||
float ambient_b "环境光-蓝"
|
||||
float metallic "金属度(PBR)"
|
||||
float roughness "粗糙度(PBR)"
|
||||
float reflectance "反射率(PBR)"
|
||||
bigint version "乐观锁版本"
|
||||
timestamp created_at "创建时间"
|
||||
timestamp updated_at "更新时间"
|
||||
}
|
||||
|
||||
material_bindings {
|
||||
bigint id PK "绑定ID"
|
||||
bigint material_id FK "材质ID"
|
||||
varchar group_id "叶子节点ID"
|
||||
timestamp created_at "创建时间"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 表结构详情
|
||||
|
||||
### 1. materials 表(材质表)
|
||||
|
||||
存储 3D 材质的属性信息,包括传统渲染属性和 PBR 属性。
|
||||
|
||||
| 字段 | 类型 | 约束 | 默认值 | 说明 |
|
||||
|------|------|------|--------|------|
|
||||
| id | BIGSERIAL | PRIMARY KEY | 自增 | 材质唯一标识 |
|
||||
| name | VARCHAR(255) | NOT NULL | - | 材质名称 |
|
||||
| diffuse_r | FLOAT | NOT NULL | 0 | 漫反射颜色-红 (0-255) |
|
||||
| diffuse_g | FLOAT | NOT NULL | 0 | 漫反射颜色-绿 (0-255) |
|
||||
| diffuse_b | FLOAT | NOT NULL | 0 | 漫反射颜色-蓝 (0-255) |
|
||||
| alpha | FLOAT | NOT NULL | 1 | 透明度 (0-255) |
|
||||
| shininess | FLOAT | NOT NULL | 0 | 光泽度/高光强度 |
|
||||
| specular_r | FLOAT | NOT NULL | 0 | 高光颜色-红 (0-255) |
|
||||
| specular_g | FLOAT | NOT NULL | 0 | 高光颜色-绿 (0-255) |
|
||||
| specular_b | FLOAT | NOT NULL | 0 | 高光颜色-蓝 (0-255) |
|
||||
| ambient_r | FLOAT | NOT NULL | 0 | 环境光颜色-红 (0-255) |
|
||||
| ambient_g | FLOAT | NOT NULL | 0 | 环境光颜色-绿 (0-255) |
|
||||
| ambient_b | FLOAT | NOT NULL | 0 | 环境光颜色-蓝 (0-255) |
|
||||
| metallic | FLOAT | NOT NULL | 0 | PBR 金属度 (0-1) |
|
||||
| roughness | FLOAT | NOT NULL | 0.5 | PBR 粗糙度 (0-1) |
|
||||
| reflectance | FLOAT | NOT NULL | 0.5 | PBR 反射率 (0-1) |
|
||||
| version | BIGINT | NOT NULL | 0 | 乐观锁版本号 |
|
||||
| created_at | TIMESTAMP | - | CURRENT_TIMESTAMP | 创建时间 |
|
||||
| updated_at | TIMESTAMP | - | CURRENT_TIMESTAMP | 更新时间 |
|
||||
|
||||
#### 索引
|
||||
|
||||
| 索引名 | 类型 | 字段 | 用途 |
|
||||
|--------|------|------|------|
|
||||
| materials_pkey | PRIMARY KEY | id | 主键 |
|
||||
| idx_materials_name_trgm | GIN | name | 模糊搜索 (ILIKE '%keyword%') |
|
||||
| idx_materials_created_at | BTREE | created_at DESC | 按创建时间排序 |
|
||||
| idx_materials_updated_at | BTREE | updated_at DESC | 按更新时间排序 |
|
||||
|
||||
---
|
||||
|
||||
### 2. material_bindings 表(材质绑定表)
|
||||
|
||||
存储材质与叶子节点(group_id)的多对多绑定关系。
|
||||
|
||||
**重要**: 此表使用 HASH 分区,按 `material_id` 分为 16 个分区,支持亿级数据规模。
|
||||
|
||||
| 字段 | 类型 | 约束 | 默认值 | 说明 |
|
||||
|------|------|------|--------|------|
|
||||
| id | BIGSERIAL | 复合主键 | 自增 | 绑定记录ID |
|
||||
| material_id | BIGINT | NOT NULL, FK | - | 关联的材质ID |
|
||||
| group_id | VARCHAR(255) | NOT NULL | - | 叶子节点ID |
|
||||
| created_at | TIMESTAMP | - | CURRENT_TIMESTAMP | 创建时间 |
|
||||
|
||||
#### 约束
|
||||
|
||||
- **主键**: `(material_id, id)` - 复合主键,支持分区
|
||||
- **外键**: `material_id` → `materials(id)` ON DELETE CASCADE
|
||||
- **唯一约束**: `(material_id, group_id)` - 防止重复绑定
|
||||
|
||||
#### 分区策略
|
||||
|
||||
```
|
||||
material_bindings (父表)
|
||||
├── material_bindings_p0 (material_id % 16 = 0)
|
||||
├── material_bindings_p1 (material_id % 16 = 1)
|
||||
├── material_bindings_p2 (material_id % 16 = 2)
|
||||
│ ...
|
||||
└── material_bindings_p15 (material_id % 16 = 15)
|
||||
```
|
||||
|
||||
#### 每个分区的索引
|
||||
|
||||
| 索引名 | 类型 | 字段 | 用途 |
|
||||
|--------|------|------|------|
|
||||
| idx_bindings_pX_unique | UNIQUE | (material_id, group_id) | 防止重复绑定 |
|
||||
| idx_bindings_pX_group | BTREE | group_id | 按 group_id 查询 |
|
||||
|
||||
---
|
||||
|
||||
## 迁移文件
|
||||
|
||||
| 文件 | 版本 | 说明 |
|
||||
|------|------|------|
|
||||
| 001_init.sql | v1.0 | 初始化基础表结构 |
|
||||
| 002_partition_bindings.sql | v1.1 | 将 bindings 表转为 16 分区 |
|
||||
| 003_add_indexes.sql | v1.2 | 添加 pg_trgm 和时间索引 |
|
||||
| 004_add_version_column.sql | v1.3 | 添加乐观锁版本字段 |
|
||||
|
||||
### 执行迁移
|
||||
|
||||
```bash
|
||||
# 进入数据库容器
|
||||
docker compose exec db psql -U postgres -d material_db
|
||||
|
||||
# 或直接执行
|
||||
psql -h localhost -p 5433 -U postgres -d material_db -f migrations/001_init.sql
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 性能优化说明
|
||||
|
||||
### 1. 分区表
|
||||
|
||||
- **目的**: 支持亿级 binding 记录
|
||||
- **策略**: HASH 分区,按 material_id 分 16 片
|
||||
- **优势**:
|
||||
- 单分区最大约 600-1000 万行
|
||||
- 查询时自动路由到相关分区
|
||||
- 可在线添加新分区
|
||||
|
||||
### 2. pg_trgm 扩展
|
||||
|
||||
```sql
|
||||
CREATE EXTENSION IF NOT EXISTS pg_trgm;
|
||||
```
|
||||
|
||||
- **目的**: 支持模糊搜索 `ILIKE '%keyword%'`
|
||||
- **原理**: 将字符串拆分为三元组 (trigram),建立 GIN 倒排索引
|
||||
|
||||
### 3. 连接池配置
|
||||
|
||||
| 参数 | 值 | 说明 |
|
||||
|------|-----|------|
|
||||
| MaxIdleConns | 50 | 最大空闲连接数 |
|
||||
| MaxOpenConns | 200 | 最大连接数 |
|
||||
| ConnMaxLifetime | 5min | 连接最大生命周期 |
|
||||
| ConnMaxIdleTime | 2min | 空闲连接最大生命周期 |
|
||||
|
||||
---
|
||||
|
||||
## 数据示例
|
||||
|
||||
### Material 示例
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 4,
|
||||
"name": "红色金属",
|
||||
"diffuse_r": 255,
|
||||
"diffuse_g": 0,
|
||||
"diffuse_b": 0,
|
||||
"alpha": 255,
|
||||
"shininess": 80,
|
||||
"specular_r": 255,
|
||||
"specular_g": 200,
|
||||
"specular_b": 200,
|
||||
"ambient_r": 50,
|
||||
"ambient_g": 0,
|
||||
"ambient_b": 0,
|
||||
"metallic": 0.9,
|
||||
"roughness": 0.2,
|
||||
"reflectance": 0.8,
|
||||
"version": 0,
|
||||
"created_at": "2025-12-09T09:53:33Z",
|
||||
"updated_at": "2025-12-09T09:53:33Z"
|
||||
}
|
||||
```
|
||||
|
||||
### Binding 示例
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"material_id": 4,
|
||||
"group_id": "20251021105716587535",
|
||||
"created_at": "2025-12-09T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 常用查询
|
||||
|
||||
### 1. 按名称模糊搜索材质
|
||||
|
||||
```sql
|
||||
SELECT * FROM materials
|
||||
WHERE name ILIKE '%金属%'
|
||||
ORDER BY id DESC
|
||||
LIMIT 20;
|
||||
```
|
||||
|
||||
### 2. 获取材质关联的所有 group_id
|
||||
|
||||
```sql
|
||||
SELECT group_id FROM material_bindings
|
||||
WHERE material_id = 4
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 20 OFFSET 0;
|
||||
```
|
||||
|
||||
### 3. 根据 group_id 批量查询材质
|
||||
|
||||
```sql
|
||||
SELECT mb.group_id, m.*
|
||||
FROM material_bindings mb
|
||||
JOIN materials m ON mb.material_id = m.id
|
||||
WHERE mb.group_id IN ('group1', 'group2', 'group3');
|
||||
```
|
||||
|
||||
### 4. 统计各分区数据量
|
||||
|
||||
```sql
|
||||
SELECT tableoid::regclass AS partition, COUNT(*)
|
||||
FROM material_bindings
|
||||
GROUP BY tableoid;
|
||||
```
|
||||
242
docs/Material_API.postman_collection.json
Normal file
242
docs/Material_API.postman_collection.json
Normal file
@@ -0,0 +1,242 @@
|
||||
{
|
||||
"info": {
|
||||
"_postman_id": "material-texture-api",
|
||||
"name": "材质管理系统 API",
|
||||
"description": "材质管理后端服务API集合",
|
||||
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
|
||||
},
|
||||
"auth": {
|
||||
"type": "apikey",
|
||||
"apikey": [
|
||||
{
|
||||
"key": "key",
|
||||
"value": "X-API-Token",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"key": "value",
|
||||
"value": "{{api_token}}",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"key": "in",
|
||||
"value": "header",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"variable": [
|
||||
{
|
||||
"key": "base_url",
|
||||
"value": "http://localhost:8081",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"key": "api_token",
|
||||
"value": "seatons3d",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"item": [
|
||||
{
|
||||
"name": "健康检查",
|
||||
"item": [
|
||||
{
|
||||
"name": "Health Check",
|
||||
"request": {
|
||||
"auth": {
|
||||
"type": "noauth"
|
||||
},
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{base_url}}/health",
|
||||
"host": ["{{base_url}}"],
|
||||
"path": ["health"]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "材质管理",
|
||||
"item": [
|
||||
{
|
||||
"name": "1. 获取材质列表",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{base_url}}/api/v1/materials?page=1&page_size=20&name=",
|
||||
"host": ["{{base_url}}"],
|
||||
"path": ["api", "v1", "materials"],
|
||||
"query": [
|
||||
{
|
||||
"key": "page",
|
||||
"value": "1",
|
||||
"description": "页码"
|
||||
},
|
||||
{
|
||||
"key": "page_size",
|
||||
"value": "20",
|
||||
"description": "每页数量"
|
||||
},
|
||||
{
|
||||
"key": "name",
|
||||
"value": "",
|
||||
"description": "按名称搜索"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "2. 添加材质",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"name\": \"新材质\",\n \"diffuse_r\": 255,\n \"diffuse_g\": 128,\n \"diffuse_b\": 64,\n \"alpha\": 255,\n \"shininess\": 20,\n \"specular_r\": 230,\n \"specular_g\": 230,\n \"specular_b\": 230,\n \"ambient_r\": 50,\n \"ambient_g\": 50,\n \"ambient_b\": 50,\n \"metallic\": 0.5,\n \"roughness\": 0.3,\n \"reflectance\": 0.5\n}"
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{base_url}}/api/v1/materials",
|
||||
"host": ["{{base_url}}"],
|
||||
"path": ["api", "v1", "materials"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "3. 获取材质详情",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{base_url}}/api/v1/materials/4",
|
||||
"host": ["{{base_url}}"],
|
||||
"path": ["api", "v1", "materials", "4"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "4. 编辑材质",
|
||||
"request": {
|
||||
"method": "PUT",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"name\": \"更新后的材质\",\n \"diffuse_r\": 200,\n \"diffuse_g\": 100,\n \"diffuse_b\": 50,\n \"alpha\": 255,\n \"shininess\": 30,\n \"specular_r\": 200,\n \"specular_g\": 200,\n \"specular_b\": 200,\n \"ambient_r\": 60,\n \"ambient_g\": 60,\n \"ambient_b\": 60,\n \"metallic\": 0.8,\n \"roughness\": 0.2,\n \"reflectance\": 0.6\n}"
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{base_url}}/api/v1/materials/4",
|
||||
"host": ["{{base_url}}"],
|
||||
"path": ["api", "v1", "materials", "4"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "5. 删除材质",
|
||||
"request": {
|
||||
"method": "DELETE",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{base_url}}/api/v1/materials/999",
|
||||
"host": ["{{base_url}}"],
|
||||
"path": ["api", "v1", "materials", "999"]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "绑定管理",
|
||||
"item": [
|
||||
{
|
||||
"name": "6. 绑定材质到Groups",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"group_ids\": [\"node_001\", \"node_002\", \"node_003\"]\n}"
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{base_url}}/api/v1/materials/4/bindings",
|
||||
"host": ["{{base_url}}"],
|
||||
"path": ["api", "v1", "materials", "4", "bindings"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "7. 解绑材质与Groups",
|
||||
"request": {
|
||||
"method": "DELETE",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"group_ids\": [\"node_001\", \"node_002\"]\n}"
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{base_url}}/api/v1/materials/4/bindings",
|
||||
"host": ["{{base_url}}"],
|
||||
"path": ["api", "v1", "materials", "4", "bindings"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "8. 获取材质关联的Groups",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"header": [],
|
||||
"url": {
|
||||
"raw": "{{base_url}}/api/v1/materials/4/groups",
|
||||
"host": ["{{base_url}}"],
|
||||
"path": ["api", "v1", "materials", "4", "groups"]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "9. 根据Groups批量查询材质",
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [
|
||||
{
|
||||
"key": "Content-Type",
|
||||
"value": "application/json"
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"mode": "raw",
|
||||
"raw": "{\n \"group_ids\": [\"202510211057245681447\", \"aDsIXMkjxdm2CDUj9gTvf\"]\n}"
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{base_url}}/api/v1/groups/materials",
|
||||
"host": ["{{base_url}}"],
|
||||
"path": ["api", "v1", "groups", "materials"]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
757537
export_tables/asset.csv
Normal file
757537
export_tables/asset.csv
Normal file
File diff suppressed because it is too large
Load Diff
304
export_tables/color.csv
Normal file
304
export_tables/color.csv
Normal file
@@ -0,0 +1,304 @@
|
||||
ColorId,Name,DiffuseRed,DiffuseGreen,DiffuseBlue,Alpha,Shininess,SpecularRed,SpecularGreen,SpecularBlue,AmbientRed,AmbientGreen,AmbientBlue,Metallic,Roughness,Reflectance
|
||||
-1,默认值,192,192,192,255,20,230,230,230,50,50,50,3,6,0,
|
||||
-9,红色,255,0,0,255,20,230,230,230,50,50,50,3,6,0,
|
||||
-8,定位颜色,254,254,101,100,20,230,230,230,50,50,50,3,6,0,
|
||||
0,dgnColor0,0,0,0,255,20,230,230,230,50,50,50,0,0,0,
|
||||
1,dgnColor1,204,204,204,255,20,230,230,230,50,50,50,0,0,0,
|
||||
2,dgnColor2,105,150,255,255,20,230,230,230,50,50,50,0,0,0,
|
||||
3,dgnColor3,0,204,0,255,20,230,230,230,50,50,50,0,0,0,
|
||||
4,dgnColor4,255,0,0,255,20,230,230,230,50,50,50,0,0,0,
|
||||
5,dgnColor5,255,255,0,255,20,230,230,230,50,50,50,0,0,0,
|
||||
6,dgnColor6,255,0,255,255,20,230,230,230,50,50,50,0,0,0,
|
||||
7,dgnColor7,255,155,0,255,20,230,230,230,50,50,50,0,0,0,
|
||||
8,dgnColor8,138,128,128,255,20,230,230,230,50,50,50,0,0,0,
|
||||
9,dgnColor9,196,196,196,255,20,230,230,230,50,50,50,0,0,0,
|
||||
10,dgnColor10,104,146,244,255,20,230,230,230,50,50,50,0,0,0,
|
||||
11,dgnColor11,6,196,6,255,20,230,230,230,50,50,50,0,0,0,
|
||||
12,dgnColor12,244,6,6,255,20,230,230,230,50,50,50,0,0,0,
|
||||
13,dgnColor13,244,244,6,255,20,230,230,230,50,50,50,0,0,0,
|
||||
14,dgnColor14,244,6,244,255,20,230,230,230,50,50,50,0,0,0,
|
||||
15,dgnColor15,244,150,6,255,20,230,230,230,50,50,50,0,0,0,
|
||||
16,dgnColor16,129,119,119,255,20,230,230,230,50,50,50,0,0,0,
|
||||
17,dgnColor17,188,188,188,255,20,230,230,230,50,50,50,0,0,0,
|
||||
18,dgnColor18,102,141,232,255,20,230,230,230,50,50,50,0,0,0,
|
||||
19,dgnColor19,11,188,11,255,20,230,230,230,50,50,50,0,0,0,
|
||||
20,dgnColor20,232,11,11,255,20,230,230,230,50,50,50,0,0,0,
|
||||
21,dgnColor21,232,232,11,255,20,230,230,230,50,50,50,0,0,0,
|
||||
22,dgnColor22,232,11,232,255,20,230,230,230,50,50,50,0,0,0,
|
||||
23,dgnColor23,232,146,11,255,20,230,230,230,50,50,50,0,0,0,
|
||||
24,dgnColor24,119,111,111,255,20,230,230,230,50,50,50,0,0,0,
|
||||
25,dgnColor25,180,180,180,255,20,230,230,230,50,50,50,0,0,0,
|
||||
26,dgnColor26,101,137,221,255,20,230,230,230,50,50,50,0,0,0,
|
||||
27,dgnColor27,17,180,17,255,20,230,230,230,50,50,50,0,0,0,
|
||||
28,dgnColor28,221,17,17,255,20,230,230,230,50,50,50,0,0,0,
|
||||
29,dgnColor29,221,221,17,255,20,230,230,230,50,50,50,0,0,0,
|
||||
30,dgnColor30,221,17,221,255,20,230,230,230,50,50,50,0,0,0,
|
||||
31,dgnColor31,221,141,17,255,20,230,230,230,50,50,50,0,0,0,
|
||||
32,dgnColor32,110,102,102,255,20,230,230,230,50,50,50,0,0,0,
|
||||
33,dgnColor33,172,172,172,255,20,230,230,230,50,50,50,0,0,0,
|
||||
34,dgnColor34,100,133,210,255,20,230,230,230,50,50,50,0,0,0,
|
||||
35,dgnColor35,23,172,23,255,20,230,230,230,50,50,50,0,0,0,
|
||||
36,dgnColor36,210,23,23,255,20,230,230,230,50,50,50,0,0,0,
|
||||
37,dgnColor37,210,210,23,255,20,230,230,230,50,50,50,0,0,0,
|
||||
38,dgnColor38,210,23,210,255,20,230,230,230,50,50,50,0,0,0,
|
||||
39,dgnColor39,210,136,23,255,20,230,230,230,50,50,50,0,0,0,
|
||||
40,dgnColor40,101,94,94,255,20,230,230,230,50,50,50,0,0,0,
|
||||
41,dgnColor41,164,164,164,255,20,230,230,230,50,50,50,0,0,0,
|
||||
42,dgnColor42,98,128,198,255,20,230,230,230,50,50,50,0,0,0,
|
||||
43,dgnColor43,28,164,28,255,20,230,230,230,50,50,50,0,0,0,
|
||||
44,dgnColor44,198,28,28,255,20,230,230,230,50,50,50,0,0,0,
|
||||
45,dgnColor45,198,198,28,255,20,230,230,230,50,50,50,0,0,0,
|
||||
46,dgnColor46,198,28,198,255,20,230,230,230,50,50,50,0,0,0,
|
||||
47,dgnColor47,198,132,28,255,20,230,230,230,50,50,50,0,0,0,
|
||||
48,dgnColor48,92,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
49,dgnColor49,156,156,156,255,20,230,230,230,50,50,50,0,0,0,
|
||||
50,dgnColor50,97,124,187,255,20,230,230,230,50,50,50,0,0,0,
|
||||
51,dgnColor51,34,156,34,255,20,230,230,230,50,50,50,0,0,0,
|
||||
52,dgnColor52,187,34,34,255,20,230,230,230,50,50,50,0,0,0,
|
||||
53,dgnColor53,187,187,34,255,20,230,230,230,50,50,50,0,0,0,
|
||||
54,dgnColor54,187,34,187,255,20,230,230,230,50,50,50,0,0,0,
|
||||
55,dgnColor55,187,127,34,255,20,230,230,230,50,50,50,0,0,0,
|
||||
56,dgnColor56,83,77,77,255,20,230,230,230,50,50,50,0,0,0,
|
||||
57,dgnColor57,148,148,148,255,20,230,230,230,50,50,50,0,0,0,
|
||||
58,dgnColor58,96,120,176,255,20,230,230,230,50,50,50,0,0,0,
|
||||
59,dgnColor59,40,148,40,255,20,230,230,230,50,50,50,0,0,0,
|
||||
60,dgnColor60,176,40,40,255,20,230,230,230,50,50,50,0,0,0,
|
||||
61,dgnColor61,176,176,40,255,20,230,230,230,50,50,50,0,0,0,
|
||||
62,dgnColor62,176,40,176,255,20,230,230,230,50,50,50,0,0,0,
|
||||
63,dgnColor63,176,122,40,255,20,230,230,230,50,50,50,0,0,0,
|
||||
64,dgnColor64,73,68,68,255,20,230,230,230,50,50,50,0,0,0,
|
||||
65,dgnColor65,141,141,141,255,20,230,230,230,50,50,50,0,0,0,
|
||||
66,dgnColor66,94,115,164,255,20,230,230,230,50,50,50,0,0,0,
|
||||
67,dgnColor67,45,141,45,255,20,230,230,230,50,50,50,0,0,0,
|
||||
68,dgnColor68,164,45,45,255,20,230,230,230,50,50,50,0,0,0,
|
||||
69,dgnColor69,164,164,45,255,20,230,230,230,50,50,50,0,0,0,
|
||||
70,dgnColor70,164,45,164,255,20,230,230,230,50,50,50,0,0,0,
|
||||
71,dgnColor71,164,118,45,255,20,230,230,230,50,50,50,0,0,0,
|
||||
72,dgnColor72,64,60,60,255,20,230,230,230,50,50,50,0,0,0,
|
||||
73,dgnColor73,133,133,133,255,20,230,230,230,50,50,50,0,0,0,
|
||||
74,dgnColor74,93,111,153,255,20,230,230,230,50,50,50,0,0,0,
|
||||
75,dgnColor75,51,133,51,255,20,230,230,230,50,50,50,0,0,0,
|
||||
76,dgnColor76,153,51,51,255,20,230,230,230,50,50,50,0,0,0,
|
||||
77,dgnColor77,153,153,51,255,20,230,230,230,50,50,50,0,0,0,
|
||||
78,dgnColor78,153,51,153,255,20,230,230,230,50,50,50,0,0,0,
|
||||
79,dgnColor79,153,113,51,255,20,230,230,230,50,50,50,0,0,0,
|
||||
80,dgnColor80,55,51,51,255,20,230,230,230,50,50,50,0,0,0,
|
||||
81,dgnColor81,125,125,125,255,20,230,230,230,50,50,50,0,0,0,
|
||||
82,dgnColor82,92,107,142,255,20,230,230,230,50,50,50,0,0,0,
|
||||
83,dgnColor83,57,125,57,255,20,230,230,230,50,50,50,0,0,0,
|
||||
84,dgnColor84,142,57,57,255,20,230,230,230,50,50,50,0,0,0,
|
||||
85,dgnColor85,142,142,57,255,20,230,230,230,50,50,50,0,0,0,
|
||||
86,dgnColor86,142,57,142,255,20,230,230,230,50,50,50,0,0,0,
|
||||
87,dgnColor87,142,108,57,255,20,230,230,230,50,50,50,0,0,0,
|
||||
88,dgnColor88,46,43,43,255,20,230,230,230,50,50,50,0,0,0,
|
||||
89,dgnColor89,117,117,117,255,20,230,230,230,50,50,50,0,0,0,
|
||||
90,dgnColor90,90,102,130,255,20,230,230,230,50,50,50,0,0,0,
|
||||
91,dgnColor91,62,117,62,255,20,230,230,230,50,50,50,0,0,0,
|
||||
92,dgnColor92,130,62,62,255,20,230,230,230,50,50,50,0,0,0,
|
||||
93,dgnColor93,130,130,62,255,20,230,230,230,50,50,50,0,0,0,
|
||||
94,dgnColor94,130,62,130,255,20,230,230,230,50,50,50,0,0,0,
|
||||
95,dgnColor95,130,104,62,255,20,230,230,230,50,50,50,0,0,0,
|
||||
96,dgnColor96,37,34,34,255,20,230,230,230,50,50,50,0,0,0,
|
||||
97,dgnColor97,109,109,109,255,20,230,230,230,50,50,50,0,0,0,
|
||||
98,dgnColor98,89,98,119,255,20,230,230,230,50,50,50,0,0,0,
|
||||
99,dgnColor99,68,109,68,255,20,230,230,230,50,50,50,0,0,0,
|
||||
100,dgnColor100,119,68,68,255,20,230,230,230,50,50,50,0,0,0,
|
||||
101,dgnColor101,119,119,68,255,20,230,230,230,50,50,50,0,0,0,
|
||||
102,dgnColor102,119,68,119,255,20,230,230,230,50,50,50,0,0,0,
|
||||
103,dgnColor103,119,99,68,255,20,230,230,230,50,50,50,0,0,0,
|
||||
104,dgnColor104,28,26,26,255,20,230,230,230,50,50,50,0,0,0,
|
||||
105,dgnColor105,101,101,101,255,20,230,230,230,50,50,50,0,0,0,
|
||||
106,dgnColor106,88,94,108,255,20,230,230,230,50,50,50,0,0,0,
|
||||
107,dgnColor107,74,101,74,255,20,230,230,230,50,50,50,0,0,0,
|
||||
108,dgnColor108,108,74,74,255,20,230,230,230,50,50,50,0,0,0,
|
||||
109,dgnColor109,108,108,74,255,20,230,230,230,50,50,50,0,0,0,
|
||||
110,dgnColor110,108,74,108,255,20,230,230,230,50,50,50,0,0,0,
|
||||
111,dgnColor111,108,94,74,255,20,230,230,230,50,50,50,0,0,0,
|
||||
112,dgnColor112,18,17,17,255,20,230,230,230,50,50,50,0,0,0,
|
||||
113,dgnColor113,93,93,93,255,20,230,230,230,50,50,50,0,0,0,
|
||||
114,dgnColor114,86,89,96,255,20,230,230,230,50,50,50,0,0,0,
|
||||
115,dgnColor115,79,93,79,255,20,230,230,230,50,50,50,0,0,0,
|
||||
116,dgnColor116,96,79,79,255,20,230,230,230,50,50,50,0,0,0,
|
||||
117,dgnColor117,96,96,79,255,20,230,230,230,50,50,50,0,0,0,
|
||||
118,dgnColor118,96,79,96,255,20,230,230,230,50,50,50,0,0,0,
|
||||
119,dgnColor119,96,90,79,255,20,230,230,230,50,50,50,0,0,0,
|
||||
120,dgnColor120,9,9,9,255,20,230,230,230,50,50,50,0,0,0,
|
||||
121,dgnColor121,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
122,dgnColor122,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
123,dgnColor123,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
124,dgnColor124,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
125,dgnColor125,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
126,dgnColor126,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
127,dgnColor127,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
128,dgnColor128,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
129,dgnColor129,141,141,141,255,20,230,230,230,50,50,50,0,0,0,
|
||||
130,dgnColor130,94,115,164,255,20,230,230,230,50,50,50,0,0,0,
|
||||
131,dgnColor131,45,141,45,255,20,230,230,230,50,50,50,0,0,0,
|
||||
132,dgnColor132,164,45,45,255,20,230,230,230,50,50,50,0,0,0,
|
||||
133,dgnColor133,164,164,45,255,20,230,230,230,50,50,50,0,0,0,
|
||||
134,dgnColor134,164,45,164,255,20,230,230,230,50,50,50,0,0,0,
|
||||
135,dgnColor135,164,118,45,255,20,230,230,230,50,50,50,0,0,0,
|
||||
136,dgnColor136,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
137,dgnColor137,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
138,dgnColor138,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
139,dgnColor139,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
140,dgnColor140,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
141,dgnColor141,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
142,dgnColor142,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
143,dgnColor143,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
144,dgnColor144,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
145,dgnColor145,133,133,133,255,20,230,230,230,50,50,50,0,0,0,
|
||||
146,dgnColor146,93,111,153,255,20,230,230,230,50,50,50,0,0,0,
|
||||
147,dgnColor147,51,133,51,255,20,230,230,230,50,50,50,0,0,0,
|
||||
148,dgnColor148,153,51,51,255,20,230,230,230,50,50,50,0,0,0,
|
||||
149,dgnColor149,153,153,51,255,20,230,230,230,50,50,50,0,0,0,
|
||||
150,dgnColor150,153,51,153,255,20,230,230,230,50,50,50,0,0,0,
|
||||
151,dgnColor151,153,113,51,255,20,230,230,230,50,50,50,0,0,0,
|
||||
152,dgnColor152,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
153,dgnColor153,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
154,dgnColor154,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
155,dgnColor155,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
156,dgnColor156,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
157,dgnColor157,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
158,dgnColor158,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
159,dgnColor159,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
160,dgnColor160,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
161,dgnColor161,125,125,125,255,20,230,230,230,50,50,50,0,0,0,
|
||||
162,dgnColor162,92,107,142,255,20,230,230,230,50,50,50,0,0,0,
|
||||
163,dgnColor163,57,125,57,255,20,230,230,230,50,50,50,0,0,0,
|
||||
164,dgnColor164,142,57,57,255,20,230,230,230,50,50,50,0,0,0,
|
||||
165,dgnColor165,142,142,57,255,20,230,230,230,50,50,50,0,0,0,
|
||||
166,dgnColor166,142,57,142,255,20,230,230,230,50,50,50,0,0,0,
|
||||
167,dgnColor167,142,108,57,255,20,230,230,230,50,50,50,0,0,0,
|
||||
168,dgnColor168,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
169,dgnColor169,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
170,dgnColor170,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
171,dgnColor171,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
172,dgnColor172,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
173,dgnColor173,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
174,dgnColor174,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
175,dgnColor175,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
176,dgnColor176,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
177,dgnColor177,117,117,117,255,20,230,230,230,50,50,50,0,0,0,
|
||||
178,dgnColor178,90,102,130,255,20,230,230,230,50,50,50,0,0,0,
|
||||
179,dgnColor179,62,117,62,255,20,230,230,230,50,50,50,0,0,0,
|
||||
180,dgnColor180,130,62,62,255,20,230,230,230,50,50,50,0,0,0,
|
||||
181,dgnColor181,130,130,62,255,20,230,230,230,50,50,50,0,0,0,
|
||||
182,dgnColor182,130,62,130,255,20,230,230,230,50,50,50,0,0,0,
|
||||
183,dgnColor183,130,104,62,255,20,230,230,230,50,50,50,0,0,0,
|
||||
184,dgnColor184,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
185,dgnColor185,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
186,dgnColor186,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
187,dgnColor187,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
188,dgnColor188,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
189,dgnColor189,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
190,dgnColor190,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
191,dgnColor191,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
192,dgnColor192,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
193,dgnColor193,109,109,109,255,20,230,230,230,50,50,50,0,0,0,
|
||||
194,dgnColor194,89,98,119,255,20,230,230,230,50,50,50,0,0,0,
|
||||
195,dgnColor195,68,109,68,255,20,230,230,230,50,50,50,0,0,0,
|
||||
196,dgnColor196,119,68,68,255,20,230,230,230,50,50,50,0,0,0,
|
||||
197,dgnColor197,119,119,68,255,20,230,230,230,50,50,50,0,0,0,
|
||||
198,dgnColor198,119,68,119,255,20,230,230,230,50,50,50,0,0,0,
|
||||
199,dgnColor199,119,99,68,255,20,230,230,230,50,50,50,0,0,0,
|
||||
200,dgnColor200,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
201,dgnColor201,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
202,dgnColor202,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
203,dgnColor203,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
204,dgnColor204,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
205,dgnColor205,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
206,dgnColor206,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
207,dgnColor207,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
208,dgnColor208,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
209,dgnColor209,101,101,101,255,20,230,230,230,50,50,50,0,0,0,
|
||||
210,dgnColor210,88,94,108,255,20,230,230,230,50,50,50,0,0,0,
|
||||
211,dgnColor211,74,101,74,255,20,230,230,230,50,50,50,0,0,0,
|
||||
212,dgnColor212,108,74,74,255,20,230,230,230,50,50,50,0,0,0,
|
||||
213,dgnColor213,108,108,74,255,20,230,230,230,50,50,50,0,0,0,
|
||||
214,dgnColor214,108,74,108,255,20,230,230,230,50,50,50,0,0,0,
|
||||
215,dgnColor215,108,94,74,255,20,230,230,230,50,50,50,0,0,0,
|
||||
216,dgnColor216,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
217,dgnColor217,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
218,dgnColor218,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
219,dgnColor219,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
220,dgnColor220,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
221,dgnColor221,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
222,dgnColor222,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
223,dgnColor223,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
224,dgnColor224,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
225,dgnColor225,93,93,93,255,20,230,230,230,50,50,50,0,0,0,
|
||||
226,dgnColor226,86,89,96,255,20,230,230,230,50,50,50,0,0,0,
|
||||
227,dgnColor227,79,93,79,255,20,230,230,230,50,50,50,0,0,0,
|
||||
228,dgnColor228,96,79,79,255,20,230,230,230,50,50,50,0,0,0,
|
||||
229,dgnColor229,96,96,79,255,20,230,230,230,50,50,50,0,0,0,
|
||||
230,dgnColor230,96,79,96,255,20,230,230,230,50,50,50,0,0,0,
|
||||
231,dgnColor231,96,90,79,255,20,230,230,230,50,50,50,0,0,0,
|
||||
232,dgnColor232,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
233,dgnColor233,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
234,dgnColor234,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
235,dgnColor235,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
236,dgnColor236,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
237,dgnColor237,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
238,dgnColor238,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
239,dgnColor239,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
240,dgnColor240,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
241,dgnColor241,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
242,dgnColor242,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
243,dgnColor243,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
244,dgnColor244,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
245,dgnColor245,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
246,dgnColor246,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
247,dgnColor247,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
248,dgnColor248,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
249,dgnColor249,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
250,dgnColor250,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
251,dgnColor251,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
252,dgnColor252,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
253,dgnColor253,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
254,dgnColor254,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
255,dgnColor255,255,255,255,255,20,230,230,230,50,50,50,0,0,0,
|
||||
256,银白色,220,220,220,255,20,230,230,230,50,50,50,0,0,0,
|
||||
257,艳绿色,0,200,70,255,20,230,230,230,50,50,50,0,0,0,
|
||||
258,粉红色,250,128,128,255,20,230,230,230,50,50,50,0,0,0,
|
||||
259,浅蓝色,157,215,255,255,20,230,230,230,50,50,50,0,0,0,
|
||||
260,橙黄色,255,120,30,255,20,230,230,230,50,50,50,0,0,0,
|
||||
261,银灰色,190,205,210,255,20,230,230,230,50,50,50,0,0,0,
|
||||
265,dgnColor0,0,0,0,255,20,230,230,230,50,50,50,0,0,0,
|
||||
266,dgnColor1,204,204,204,255,20,230,230,230,50,50,50,0,0,0,
|
||||
267,dgnColor2,105,150,255,255,20,230,230,230,50,50,50,0,0,0,
|
||||
268,dgnColor3,0,204,0,255,20,230,230,230,50,50,50,0,0,0,
|
||||
269,dgnColor4,255,0,0,255,20,230,230,230,50,50,50,0,0,0,
|
||||
270,dgnColor5,255,255,0,255,20,230,230,230,50,50,50,0,0,0,
|
||||
271,dgnColor7,255,155,0,255,20,230,230,230,50,50,50,0,0,0,
|
||||
272,dgnColor14,244,6,244,255,20,230,230,230,50,50,50,0,0,0,
|
||||
273,dgnColor16,129,119,119,255,20,230,230,230,50,50,50,0,0,0,
|
||||
274,dgnColor17,188,188,188,255,20,230,230,230,50,50,50,0,0,0,
|
||||
275,dgnColor18,102,141,232,255,20,230,230,230,50,50,50,0,0,0,
|
||||
276,dgnColor21,232,232,11,255,20,230,230,230,50,50,50,0,0,0,
|
||||
277,dgnColor24,119,111,111,255,20,230,230,230,50,50,50,0,0,0,
|
||||
278,dgnColor32,110,102,102,255,20,230,230,230,50,50,50,0,0,0,
|
||||
279,dgnColor34,100,133,210,255,20,230,230,230,50,50,50,0,0,0,
|
||||
280,dgnColor35,23,172,23,255,20,230,230,230,50,50,50,0,0,0,
|
||||
281,dgnColor36,210,23,23,255,20,230,230,230,50,50,50,0,0,0,
|
||||
282,dgnColor37,210,210,23,255,20,230,230,230,50,50,50,0,0,0,
|
||||
283,dgnColor39,210,136,23,255,20,230,230,230,50,50,50,0,0,0,
|
||||
284,dgnColor40,101,94,94,255,20,230,230,230,50,50,50,0,0,0,
|
||||
285,dgnColor41,164,164,164,255,20,230,230,230,50,50,50,0,0,0,
|
||||
286,dgnColor42,98,128,198,255,20,230,230,230,50,50,50,0,0,0,
|
||||
287,dgnColor43,28,164,28,255,20,230,230,230,50,50,50,0,0,0,
|
||||
288,dgnColor44,198,28,28,255,20,230,230,230,50,50,50,0,0,0,
|
||||
289,dgnColor45,198,198,28,255,20,230,230,230,50,50,50,0,0,0,
|
||||
290,dgnColor46,198,28,198,255,20,230,230,230,50,50,50,0,0,0,
|
||||
291,dgnColor47,198,132,28,255,20,230,230,230,50,50,50,0,0,0,
|
||||
292,dgnColor49,156,156,156,255,20,230,230,230,50,50,50,0,0,0,
|
||||
293,dgnColor50,97,124,187,255,20,230,230,230,50,50,50,0,0,0,
|
||||
294,dgnColor51,34,156,34,255,20,230,230,230,50,50,50,0,0,0,
|
||||
295,dgnColor54,187,34,187,255,20,230,230,230,50,50,50,0,0,0,
|
||||
296,dgnColor55,187,127,34,255,20,230,230,230,50,50,50,0,0,0,
|
||||
297,dgnColor108,108,74,74,255,20,230,230,230,50,50,50,0,0,0,
|
||||
298,dgnColor115,79,93,79,255,20,230,230,230,50,50,50,0,0,0,
|
||||
299,dgnColor119,96,90,79,255,20,230,230,230,50,50,50,0,0,0,
|
||||
300,dgnColor148,153,51,51,255,20,230,230,230,50,50,50,0,0,0,
|
||||
301,dgnColor249,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
302,dgnColor252,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
|
||||
|
757537
export_tables/map.csv
Normal file
757537
export_tables/map.csv
Normal file
File diff suppressed because it is too large
Load Diff
49
go.mod
Normal file
49
go.mod
Normal file
@@ -0,0 +1,49 @@
|
||||
module material_texture
|
||||
|
||||
go 1.23.0
|
||||
|
||||
require (
|
||||
github.com/gin-gonic/gin v1.11.0
|
||||
gorm.io/driver/postgres v1.6.0
|
||||
gorm.io/gorm v1.31.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/bytedance/sonic v1.14.0 // indirect
|
||||
github.com/bytedance/sonic/loader v0.3.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
||||
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.27.0 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/goccy/go-yaml v1.18.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jackc/pgx/v5 v5.6.0 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/quic-go/qpack v0.5.1 // indirect
|
||||
github.com/quic-go/quic-go v0.54.0 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.3.0 // indirect
|
||||
go.uber.org/mock v0.5.0 // indirect
|
||||
golang.org/x/arch v0.20.0 // indirect
|
||||
golang.org/x/crypto v0.40.0 // indirect
|
||||
golang.org/x/mod v0.25.0 // indirect
|
||||
golang.org/x/net v0.42.0 // indirect
|
||||
golang.org/x/sync v0.16.0 // indirect
|
||||
golang.org/x/sys v0.35.0 // indirect
|
||||
golang.org/x/text v0.27.0 // indirect
|
||||
golang.org/x/tools v0.34.0 // indirect
|
||||
google.golang.org/protobuf v1.36.9 // indirect
|
||||
)
|
||||
105
go.sum
Normal file
105
go.sum
Normal file
@@ -0,0 +1,105 @@
|
||||
github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ=
|
||||
github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA=
|
||||
github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
|
||||
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
|
||||
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
||||
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
||||
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
||||
github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk=
|
||||
github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
|
||||
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
|
||||
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY=
|
||||
github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw=
|
||||
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
|
||||
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
|
||||
github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg=
|
||||
github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
|
||||
github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
||||
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
|
||||
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
|
||||
golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c=
|
||||
golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
|
||||
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
|
||||
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
|
||||
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
|
||||
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
|
||||
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
|
||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
||||
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
||||
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
|
||||
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
|
||||
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
|
||||
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4=
|
||||
gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo=
|
||||
gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg=
|
||||
gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
|
||||
26
internal/config/config.go
Normal file
26
internal/config/config.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
ServerPort string
|
||||
DatabaseURL string
|
||||
APIToken string
|
||||
}
|
||||
|
||||
func Load() *Config {
|
||||
return &Config{
|
||||
ServerPort: getEnv("SERVER_PORT", "8080"),
|
||||
DatabaseURL: getEnv("DATABASE_URL", "postgres://postgres:postgres@localhost:5432/material_db?sslmode=disable"),
|
||||
APIToken: getEnv("API_TOKEN", "seatons3d"),
|
||||
}
|
||||
}
|
||||
|
||||
func getEnv(key, defaultValue string) string {
|
||||
if value := os.Getenv(key); value != "" {
|
||||
return value
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
33
internal/config/database.go
Normal file
33
internal/config/database.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"gorm.io/driver/postgres"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
)
|
||||
|
||||
func NewDatabase(cfg *Config) *gorm.DB {
|
||||
db, err := gorm.Open(postgres.Open(cfg.DatabaseURL), &gorm.Config{
|
||||
Logger: logger.Default.LogMode(logger.Info),
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to connect to database: %v", err)
|
||||
}
|
||||
|
||||
sqlDB, err := db.DB()
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to get database instance: %v", err)
|
||||
}
|
||||
|
||||
// 连接池配置 (优化: 支持更高并发)
|
||||
sqlDB.SetMaxIdleConns(50) // 空闲连接数 (原 10)
|
||||
sqlDB.SetMaxOpenConns(200) // 最大连接数 (原 100)
|
||||
sqlDB.SetConnMaxLifetime(5 * time.Minute) // 连接最大生命周期
|
||||
sqlDB.SetConnMaxIdleTime(2 * time.Minute) // 空闲连接最大生命周期
|
||||
|
||||
log.Println("Database connected successfully")
|
||||
return db
|
||||
}
|
||||
157
internal/handlers/binding.go
Normal file
157
internal/handlers/binding.go
Normal file
@@ -0,0 +1,157 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"material_texture/internal/models"
|
||||
"material_texture/internal/repository"
|
||||
"material_texture/pkg/response"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type BindingHandler struct {
|
||||
bindingRepo *repository.BindingRepository
|
||||
materialRepo *repository.MaterialRepository
|
||||
}
|
||||
|
||||
func NewBindingHandler(bindingRepo *repository.BindingRepository, materialRepo *repository.MaterialRepository) *BindingHandler {
|
||||
return &BindingHandler{
|
||||
bindingRepo: bindingRepo,
|
||||
materialRepo: materialRepo,
|
||||
}
|
||||
}
|
||||
|
||||
// BindMaterial 绑定材质到多个group_id
|
||||
// POST /api/v1/materials/:id/bindings
|
||||
// Body: {"group_ids": ["g1", "g2", "g3"]}
|
||||
func (h *BindingHandler) BindMaterial(c *gin.Context) {
|
||||
materialID, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
response.BadRequest(c, "invalid material id")
|
||||
return
|
||||
}
|
||||
|
||||
// 检查材质是否存在
|
||||
exists, err := h.materialRepo.Exists(materialID)
|
||||
if err != nil {
|
||||
response.InternalError(c, "failed to check material")
|
||||
return
|
||||
}
|
||||
if !exists {
|
||||
response.NotFound(c, "material not found")
|
||||
return
|
||||
}
|
||||
|
||||
var req models.BindingRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "invalid request body: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.bindingRepo.BindMaterial(materialID, req.GroupIDs); err != nil {
|
||||
response.InternalError(c, "failed to bind material")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"material_id": materialID,
|
||||
"group_ids": req.GroupIDs,
|
||||
})
|
||||
}
|
||||
|
||||
// UnbindMaterial 解绑材质与指定group_id
|
||||
// DELETE /api/v1/materials/:id/bindings
|
||||
// Body: {"group_ids": ["g1", "g2"]}
|
||||
func (h *BindingHandler) UnbindMaterial(c *gin.Context) {
|
||||
materialID, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
response.BadRequest(c, "invalid material id")
|
||||
return
|
||||
}
|
||||
|
||||
var req models.UnbindRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "invalid request body: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.bindingRepo.UnbindMaterial(materialID, req.GroupIDs); err != nil {
|
||||
response.InternalError(c, "failed to unbind material")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"material_id": materialID,
|
||||
"unbound": req.GroupIDs,
|
||||
})
|
||||
}
|
||||
|
||||
// GetGroupsByMaterial 获取材质关联的所有group_id (分页版本)
|
||||
// GET /api/v1/materials/:id/groups?page=1&page_size=20
|
||||
func (h *BindingHandler) GetGroupsByMaterial(c *gin.Context) {
|
||||
materialID, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
response.BadRequest(c, "invalid material id")
|
||||
return
|
||||
}
|
||||
|
||||
// 解析分页参数
|
||||
var query models.GroupListQuery
|
||||
if err := c.ShouldBindQuery(&query); err != nil {
|
||||
response.BadRequest(c, "invalid query parameters")
|
||||
return
|
||||
}
|
||||
|
||||
// 设置默认值
|
||||
if query.Page < 1 {
|
||||
query.Page = 1
|
||||
}
|
||||
if query.PageSize < 1 || query.PageSize > 100 {
|
||||
query.PageSize = 20
|
||||
}
|
||||
|
||||
// 检查材质是否存在
|
||||
exists, err := h.materialRepo.Exists(materialID)
|
||||
if err != nil {
|
||||
response.InternalError(c, "failed to check material")
|
||||
return
|
||||
}
|
||||
if !exists {
|
||||
response.NotFound(c, "material not found")
|
||||
return
|
||||
}
|
||||
|
||||
groupIDs, total, err := h.bindingRepo.GetGroupsByMaterialID(materialID, query.Page, query.PageSize)
|
||||
if err != nil {
|
||||
response.InternalError(c, "failed to get groups")
|
||||
return
|
||||
}
|
||||
|
||||
// 返回分页格式
|
||||
response.Success(c, models.GroupListResponse{
|
||||
Items: groupIDs,
|
||||
Total: total,
|
||||
Page: query.Page,
|
||||
PageSize: query.PageSize,
|
||||
})
|
||||
}
|
||||
|
||||
// GetMaterialsByGroups 根据多个group_id获取关联的材质
|
||||
// POST /api/v1/groups/materials
|
||||
// Body: {"group_ids": ["g1", "g2", "g3"]}
|
||||
func (h *BindingHandler) GetMaterialsByGroups(c *gin.Context) {
|
||||
var req models.GroupMaterialsRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "invalid request body: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
results, err := h.bindingRepo.GetMaterialsByGroupIDs(req.GroupIDs)
|
||||
if err != nil {
|
||||
response.InternalError(c, "failed to get materials")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, results)
|
||||
}
|
||||
178
internal/handlers/material.go
Normal file
178
internal/handlers/material.go
Normal file
@@ -0,0 +1,178 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
|
||||
"material_texture/internal/models"
|
||||
"material_texture/internal/repository"
|
||||
"material_texture/pkg/response"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type MaterialHandler struct {
|
||||
repo *repository.MaterialRepository
|
||||
}
|
||||
|
||||
func NewMaterialHandler(repo *repository.MaterialRepository) *MaterialHandler {
|
||||
return &MaterialHandler{repo: repo}
|
||||
}
|
||||
|
||||
// List 获取材质列表
|
||||
// GET /api/v1/materials?page=1&page_size=20&name=xxx
|
||||
func (h *MaterialHandler) List(c *gin.Context) {
|
||||
var query models.MaterialListQuery
|
||||
if err := c.ShouldBindQuery(&query); err != nil {
|
||||
response.BadRequest(c, "invalid query parameters")
|
||||
return
|
||||
}
|
||||
|
||||
// 设置默认值
|
||||
if query.Page < 1 {
|
||||
query.Page = 1
|
||||
}
|
||||
if query.PageSize < 1 || query.PageSize > 100 {
|
||||
query.PageSize = 20
|
||||
}
|
||||
|
||||
materials, total, err := h.repo.List(query)
|
||||
if err != nil {
|
||||
response.InternalError(c, "failed to fetch materials")
|
||||
return
|
||||
}
|
||||
|
||||
response.SuccessPaged(c, materials, total, query.Page, query.PageSize)
|
||||
}
|
||||
|
||||
// GetByID 获取单个材质详情
|
||||
// GET /api/v1/materials/:id
|
||||
func (h *MaterialHandler) GetByID(c *gin.Context) {
|
||||
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
response.BadRequest(c, "invalid material id")
|
||||
return
|
||||
}
|
||||
|
||||
material, err := h.repo.GetByID(id)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
response.NotFound(c, "material not found")
|
||||
return
|
||||
}
|
||||
response.InternalError(c, "failed to fetch material")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, material)
|
||||
}
|
||||
|
||||
// Create 创建材质
|
||||
// POST /api/v1/materials
|
||||
func (h *MaterialHandler) Create(c *gin.Context) {
|
||||
var req models.MaterialRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "invalid request body: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
material := &models.Material{
|
||||
Name: req.Name,
|
||||
DiffuseR: req.DiffuseR,
|
||||
DiffuseG: req.DiffuseG,
|
||||
DiffuseB: req.DiffuseB,
|
||||
Alpha: req.Alpha,
|
||||
Shininess: req.Shininess,
|
||||
SpecularR: req.SpecularR,
|
||||
SpecularG: req.SpecularG,
|
||||
SpecularB: req.SpecularB,
|
||||
AmbientR: req.AmbientR,
|
||||
AmbientG: req.AmbientG,
|
||||
AmbientB: req.AmbientB,
|
||||
Metallic: req.Metallic,
|
||||
Roughness: req.Roughness,
|
||||
Reflectance: req.Reflectance,
|
||||
}
|
||||
|
||||
if err := h.repo.Create(material); err != nil {
|
||||
response.InternalError(c, "failed to create material")
|
||||
return
|
||||
}
|
||||
|
||||
response.Created(c, material)
|
||||
}
|
||||
|
||||
// Update 更新材质 (优化: 单次查询)
|
||||
// PUT /api/v1/materials/:id
|
||||
func (h *MaterialHandler) Update(c *gin.Context) {
|
||||
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
response.BadRequest(c, "invalid material id")
|
||||
return
|
||||
}
|
||||
|
||||
var req models.MaterialRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "invalid request body: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 直接更新,通过 RowsAffected 判断是否存在
|
||||
updates := map[string]interface{}{
|
||||
"name": req.Name,
|
||||
"diffuse_r": req.DiffuseR,
|
||||
"diffuse_g": req.DiffuseG,
|
||||
"diffuse_b": req.DiffuseB,
|
||||
"alpha": req.Alpha,
|
||||
"shininess": req.Shininess,
|
||||
"specular_r": req.SpecularR,
|
||||
"specular_g": req.SpecularG,
|
||||
"specular_b": req.SpecularB,
|
||||
"ambient_r": req.AmbientR,
|
||||
"ambient_g": req.AmbientG,
|
||||
"ambient_b": req.AmbientB,
|
||||
"metallic": req.Metallic,
|
||||
"roughness": req.Roughness,
|
||||
"reflectance": req.Reflectance,
|
||||
}
|
||||
|
||||
rowsAffected, err := h.repo.UpdateByID(id, updates)
|
||||
if err != nil {
|
||||
response.InternalError(c, "failed to update material")
|
||||
return
|
||||
}
|
||||
|
||||
if rowsAffected == 0 {
|
||||
response.NotFound(c, "material not found")
|
||||
return
|
||||
}
|
||||
|
||||
// 返回更新后的数据
|
||||
material, _ := h.repo.GetByID(id)
|
||||
response.Success(c, material)
|
||||
}
|
||||
|
||||
// Delete 删除材质 (优化: 单次查询,通过 RowsAffected 判断)
|
||||
// DELETE /api/v1/materials/:id
|
||||
func (h *MaterialHandler) Delete(c *gin.Context) {
|
||||
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
response.BadRequest(c, "invalid material id")
|
||||
return
|
||||
}
|
||||
|
||||
// 直接删除,通过 RowsAffected 判断是否存在
|
||||
rowsAffected, err := h.repo.DeleteByID(id)
|
||||
if err != nil {
|
||||
response.InternalError(c, "failed to delete material")
|
||||
return
|
||||
}
|
||||
|
||||
if rowsAffected == 0 {
|
||||
response.NotFound(c, "material not found")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{"id": id})
|
||||
}
|
||||
32
internal/middleware/auth.go
Normal file
32
internal/middleware/auth.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"material_texture/pkg/response"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
const (
|
||||
HeaderAPIToken = "X-API-Token"
|
||||
)
|
||||
|
||||
// TokenAuth 简单Token认证中间件
|
||||
func TokenAuth(expectedToken string) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
token := c.GetHeader(HeaderAPIToken)
|
||||
|
||||
if token == "" {
|
||||
response.Unauthorized(c, "missing API token")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
if token != expectedToken {
|
||||
response.Unauthorized(c, "invalid API token")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
54
internal/models/binding.go
Normal file
54
internal/models/binding.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type MaterialBinding struct {
|
||||
ID int64 `json:"id" gorm:"primaryKey;autoIncrement"`
|
||||
MaterialID int64 `json:"material_id" gorm:"not null;index:idx_bindings_material_id"`
|
||||
GroupID string `json:"group_id" gorm:"size:255;not null;index:idx_bindings_group_id"`
|
||||
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
|
||||
|
||||
// 关联的材质(用于预加载)
|
||||
Material *Material `json:"material,omitempty" gorm:"foreignKey:MaterialID;constraint:OnDelete:CASCADE"`
|
||||
}
|
||||
|
||||
func (MaterialBinding) TableName() string {
|
||||
return "material_bindings"
|
||||
}
|
||||
|
||||
// 绑定请求 - 一个材质绑定到多个group_id
|
||||
type BindingRequest struct {
|
||||
GroupIDs []string `json:"group_ids" binding:"required,min=1,max=10000"`
|
||||
}
|
||||
|
||||
// 解绑请求
|
||||
type UnbindRequest struct {
|
||||
GroupIDs []string `json:"group_ids" binding:"required,min=1,max=10000"`
|
||||
}
|
||||
|
||||
// 批量查询group_id关联材质的请求
|
||||
type GroupMaterialsRequest struct {
|
||||
GroupIDs []string `json:"group_ids" binding:"required,min=1,max=10000"`
|
||||
}
|
||||
|
||||
// 批量查询结果 - 包含材质详情
|
||||
type GroupMaterialResult struct {
|
||||
GroupID string `json:"group_id"`
|
||||
Material *Material `json:"material"`
|
||||
}
|
||||
|
||||
// GetGroups 分页查询参数
|
||||
type GroupListQuery struct {
|
||||
Page int `form:"page"`
|
||||
PageSize int `form:"page_size"`
|
||||
}
|
||||
|
||||
// GetGroups 分页响应
|
||||
type GroupListResponse struct {
|
||||
Items []string `json:"items"`
|
||||
Total int64 `json:"total"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
}
|
||||
68
internal/models/material.go
Normal file
68
internal/models/material.go
Normal file
@@ -0,0 +1,68 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type Material struct {
|
||||
ID int64 `json:"id" gorm:"primaryKey;autoIncrement"`
|
||||
Name string `json:"name" gorm:"size:255;not null"`
|
||||
|
||||
// 漫反射颜色 (Diffuse)
|
||||
DiffuseR float64 `json:"diffuse_r" gorm:"not null;default:0"`
|
||||
DiffuseG float64 `json:"diffuse_g" gorm:"not null;default:0"`
|
||||
DiffuseB float64 `json:"diffuse_b" gorm:"not null;default:0"`
|
||||
Alpha float64 `json:"alpha" gorm:"not null;default:1"`
|
||||
|
||||
// 高光 (Specular)
|
||||
Shininess float64 `json:"shininess" gorm:"not null;default:0"`
|
||||
SpecularR float64 `json:"specular_r" gorm:"not null;default:0"`
|
||||
SpecularG float64 `json:"specular_g" gorm:"not null;default:0"`
|
||||
SpecularB float64 `json:"specular_b" gorm:"not null;default:0"`
|
||||
|
||||
// 环境光 (Ambient)
|
||||
AmbientR float64 `json:"ambient_r" gorm:"not null;default:0"`
|
||||
AmbientG float64 `json:"ambient_g" gorm:"not null;default:0"`
|
||||
AmbientB float64 `json:"ambient_b" gorm:"not null;default:0"`
|
||||
|
||||
// PBR属性
|
||||
Metallic float64 `json:"metallic" gorm:"not null;default:0"`
|
||||
Roughness float64 `json:"roughness" gorm:"not null;default:0.5"`
|
||||
Reflectance float64 `json:"reflectance" gorm:"not null;default:0.5"`
|
||||
|
||||
// 乐观锁版本号 (用于防止并发覆盖)
|
||||
Version int64 `json:"version" gorm:"not null;default:0"`
|
||||
|
||||
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
|
||||
UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
|
||||
}
|
||||
|
||||
func (Material) TableName() string {
|
||||
return "materials"
|
||||
}
|
||||
|
||||
// 创建/更新材质的请求结构
|
||||
type MaterialRequest struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
DiffuseR float64 `json:"diffuse_r"`
|
||||
DiffuseG float64 `json:"diffuse_g"`
|
||||
DiffuseB float64 `json:"diffuse_b"`
|
||||
Alpha float64 `json:"alpha"`
|
||||
Shininess float64 `json:"shininess"`
|
||||
SpecularR float64 `json:"specular_r"`
|
||||
SpecularG float64 `json:"specular_g"`
|
||||
SpecularB float64 `json:"specular_b"`
|
||||
AmbientR float64 `json:"ambient_r"`
|
||||
AmbientG float64 `json:"ambient_g"`
|
||||
AmbientB float64 `json:"ambient_b"`
|
||||
Metallic float64 `json:"metallic"`
|
||||
Roughness float64 `json:"roughness"`
|
||||
Reflectance float64 `json:"reflectance"`
|
||||
}
|
||||
|
||||
// 列表查询参数
|
||||
type MaterialListQuery struct {
|
||||
Page int `form:"page,default=1"`
|
||||
PageSize int `form:"page_size,default=20"`
|
||||
Name string `form:"name"`
|
||||
}
|
||||
104
internal/repository/binding.go
Normal file
104
internal/repository/binding.go
Normal file
@@ -0,0 +1,104 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"material_texture/internal/models"
|
||||
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
type BindingRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewBindingRepository(db *gorm.DB) *BindingRepository {
|
||||
return &BindingRepository{db: db}
|
||||
}
|
||||
|
||||
// BindMaterial 绑定材质到多个group_id(幂等操作,使用upsert)
|
||||
// 优化: 分批处理,避免单条 SQL 过大
|
||||
func (r *BindingRepository) BindMaterial(materialID int64, groupIDs []string) error {
|
||||
const batchSize = 1000 // 每批最多 1000 条
|
||||
|
||||
for i := 0; i < len(groupIDs); i += batchSize {
|
||||
end := i + batchSize
|
||||
if end > len(groupIDs) {
|
||||
end = len(groupIDs)
|
||||
}
|
||||
|
||||
batch := groupIDs[i:end]
|
||||
bindings := make([]models.MaterialBinding, len(batch))
|
||||
for j, groupID := range batch {
|
||||
bindings[j] = models.MaterialBinding{
|
||||
MaterialID: materialID,
|
||||
GroupID: groupID,
|
||||
}
|
||||
}
|
||||
|
||||
// 使用 ON CONFLICT DO NOTHING 实现幂等
|
||||
if err := r.db.Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "material_id"}, {Name: "group_id"}},
|
||||
DoNothing: true,
|
||||
}).Create(&bindings).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnbindMaterial 解绑材质与指定的group_id
|
||||
func (r *BindingRepository) UnbindMaterial(materialID int64, groupIDs []string) error {
|
||||
return r.db.Where("material_id = ? AND group_id IN ?", materialID, groupIDs).
|
||||
Delete(&models.MaterialBinding{}).Error
|
||||
}
|
||||
|
||||
// GetGroupsByMaterialID 根据材质ID获取所有关联的group_id (分页版本)
|
||||
func (r *BindingRepository) GetGroupsByMaterialID(materialID int64, page, pageSize int) ([]string, int64, error) {
|
||||
var groupIDs []string
|
||||
var total int64
|
||||
|
||||
db := r.db.Model(&models.MaterialBinding{}).Where("material_id = ?", materialID)
|
||||
|
||||
// 获取总数
|
||||
if err := db.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 分页查询
|
||||
offset := (page - 1) * pageSize
|
||||
err := db.Order("created_at DESC").
|
||||
Offset(offset).
|
||||
Limit(pageSize).
|
||||
Pluck("group_id", &groupIDs).Error
|
||||
|
||||
return groupIDs, total, err
|
||||
}
|
||||
|
||||
// GetMaterialsByGroupIDs 根据多个group_id获取关联的材质(含材质详情)
|
||||
func (r *BindingRepository) GetMaterialsByGroupIDs(groupIDs []string) ([]models.GroupMaterialResult, error) {
|
||||
var bindings []models.MaterialBinding
|
||||
|
||||
err := r.db.Preload("Material").
|
||||
Where("group_id IN ?", groupIDs).
|
||||
Find(&bindings).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
results := make([]models.GroupMaterialResult, len(bindings))
|
||||
for i, binding := range bindings {
|
||||
results[i] = models.GroupMaterialResult{
|
||||
GroupID: binding.GroupID,
|
||||
Material: binding.Material,
|
||||
}
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// DeleteByMaterialID 删除材质的所有绑定(材质删除时级联调用)
|
||||
func (r *BindingRepository) DeleteByMaterialID(materialID int64) error {
|
||||
return r.db.Where("material_id = ?", materialID).
|
||||
Delete(&models.MaterialBinding{}).Error
|
||||
}
|
||||
99
internal/repository/material.go
Normal file
99
internal/repository/material.go
Normal file
@@ -0,0 +1,99 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"material_texture/internal/models"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type MaterialRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewMaterialRepository(db *gorm.DB) *MaterialRepository {
|
||||
return &MaterialRepository{db: db}
|
||||
}
|
||||
|
||||
// List 获取材质列表(支持分页和名称搜索)
|
||||
func (r *MaterialRepository) List(query models.MaterialListQuery) ([]models.Material, int64, error) {
|
||||
var materials []models.Material
|
||||
var total int64
|
||||
|
||||
db := r.db.Model(&models.Material{})
|
||||
|
||||
// 名称模糊搜索
|
||||
if query.Name != "" {
|
||||
db = db.Where("name ILIKE ?", "%"+query.Name+"%")
|
||||
}
|
||||
|
||||
// 获取总数
|
||||
if err := db.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 分页查询
|
||||
offset := (query.Page - 1) * query.PageSize
|
||||
if err := db.Order("id DESC").Offset(offset).Limit(query.PageSize).Find(&materials).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return materials, total, nil
|
||||
}
|
||||
|
||||
// GetByID 根据ID获取单个材质
|
||||
func (r *MaterialRepository) GetByID(id int64) (*models.Material, error) {
|
||||
var material models.Material
|
||||
if err := r.db.First(&material, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &material, nil
|
||||
}
|
||||
|
||||
// Create 创建材质
|
||||
func (r *MaterialRepository) Create(material *models.Material) error {
|
||||
return r.db.Create(material).Error
|
||||
}
|
||||
|
||||
// Update 更新材质 (优化: 直接 UPDATE,返回影响行数)
|
||||
func (r *MaterialRepository) Update(material *models.Material) error {
|
||||
return r.db.Save(material).Error
|
||||
}
|
||||
|
||||
// UpdateByID 根据 ID 直接更新 (优化版本,减少查询)
|
||||
func (r *MaterialRepository) UpdateByID(id int64, updates map[string]interface{}) (int64, error) {
|
||||
result := r.db.Model(&models.Material{}).Where("id = ?", id).Updates(updates)
|
||||
return result.RowsAffected, result.Error
|
||||
}
|
||||
|
||||
// UpdateByIDWithVersion 带乐观锁的更新 (防止并发覆盖)
|
||||
// 只有当 version 匹配时才更新,并自动递增 version
|
||||
func (r *MaterialRepository) UpdateByIDWithVersion(id int64, version int64, updates map[string]interface{}) (int64, error) {
|
||||
// 在更新中自动递增 version
|
||||
updates["version"] = gorm.Expr("version + 1")
|
||||
|
||||
result := r.db.Model(&models.Material{}).
|
||||
Where("id = ? AND version = ?", id, version).
|
||||
Updates(updates)
|
||||
|
||||
return result.RowsAffected, result.Error
|
||||
}
|
||||
|
||||
// Delete 删除材质
|
||||
func (r *MaterialRepository) Delete(id int64) error {
|
||||
return r.db.Delete(&models.Material{}, id).Error
|
||||
}
|
||||
|
||||
// DeleteByID 删除材质并返回影响行数 (优化版本)
|
||||
func (r *MaterialRepository) DeleteByID(id int64) (int64, error) {
|
||||
result := r.db.Delete(&models.Material{}, id)
|
||||
return result.RowsAffected, result.Error
|
||||
}
|
||||
|
||||
// Exists 检查材质是否存在
|
||||
func (r *MaterialRepository) Exists(id int64) (bool, error) {
|
||||
var count int64
|
||||
if err := r.db.Model(&models.Material{}).Where("id = ?", id).Count(&count).Error; err != nil {
|
||||
return false, err
|
||||
}
|
||||
return count > 0, nil
|
||||
}
|
||||
53
internal/router/router.go
Normal file
53
internal/router/router.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"material_texture/internal/config"
|
||||
"material_texture/internal/handlers"
|
||||
"material_texture/internal/middleware"
|
||||
"material_texture/internal/repository"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func Setup(db *gorm.DB, cfg *config.Config) *gin.Engine {
|
||||
r := gin.Default()
|
||||
|
||||
// 健康检查(无需认证)
|
||||
r.GET("/health", func(c *gin.Context) {
|
||||
c.JSON(200, gin.H{"status": "ok"})
|
||||
})
|
||||
|
||||
// 初始化仓库
|
||||
materialRepo := repository.NewMaterialRepository(db)
|
||||
bindingRepo := repository.NewBindingRepository(db)
|
||||
|
||||
// 初始化处理器
|
||||
materialHandler := handlers.NewMaterialHandler(materialRepo)
|
||||
bindingHandler := handlers.NewBindingHandler(bindingRepo, materialRepo)
|
||||
|
||||
// API v1 路由组
|
||||
v1 := r.Group("/api/v1")
|
||||
v1.Use(middleware.TokenAuth(cfg.APIToken))
|
||||
{
|
||||
// 材质 CRUD
|
||||
materials := v1.Group("/materials")
|
||||
{
|
||||
materials.GET("", materialHandler.List)
|
||||
materials.POST("", materialHandler.Create)
|
||||
materials.GET("/:id", materialHandler.GetByID)
|
||||
materials.PUT("/:id", materialHandler.Update)
|
||||
materials.DELETE("/:id", materialHandler.Delete)
|
||||
|
||||
// 绑定管理
|
||||
materials.POST("/:id/bindings", bindingHandler.BindMaterial)
|
||||
materials.DELETE("/:id/bindings", bindingHandler.UnbindMaterial)
|
||||
materials.GET("/:id/groups", bindingHandler.GetGroupsByMaterial)
|
||||
}
|
||||
|
||||
// 根据group_id查询材质
|
||||
v1.POST("/groups/materials", bindingHandler.GetMaterialsByGroups)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
37
migrations/001_init.sql
Normal file
37
migrations/001_init.sql
Normal file
@@ -0,0 +1,37 @@
|
||||
-- 材质管理系统初始化脚本
|
||||
-- 此脚本由GORM AutoMigrate自动执行,仅作参考
|
||||
|
||||
-- 材质表
|
||||
CREATE TABLE IF NOT EXISTS materials (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
diffuse_r FLOAT NOT NULL DEFAULT 0,
|
||||
diffuse_g FLOAT NOT NULL DEFAULT 0,
|
||||
diffuse_b FLOAT NOT NULL DEFAULT 0,
|
||||
alpha FLOAT NOT NULL DEFAULT 1,
|
||||
shininess FLOAT NOT NULL DEFAULT 0,
|
||||
specular_r FLOAT NOT NULL DEFAULT 0,
|
||||
specular_g FLOAT NOT NULL DEFAULT 0,
|
||||
specular_b FLOAT NOT NULL DEFAULT 0,
|
||||
ambient_r FLOAT NOT NULL DEFAULT 0,
|
||||
ambient_g FLOAT NOT NULL DEFAULT 0,
|
||||
ambient_b FLOAT NOT NULL DEFAULT 0,
|
||||
metallic FLOAT NOT NULL DEFAULT 0,
|
||||
roughness FLOAT NOT NULL DEFAULT 0.5,
|
||||
reflectance FLOAT NOT NULL DEFAULT 0.5,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- 材质绑定表
|
||||
CREATE TABLE IF NOT EXISTS material_bindings (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
material_id BIGINT NOT NULL REFERENCES materials(id) ON DELETE CASCADE,
|
||||
group_id VARCHAR(255) NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- 索引
|
||||
CREATE INDEX IF NOT EXISTS idx_bindings_material_id ON material_bindings(material_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_bindings_group_id ON material_bindings(group_id);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_unique_material_group ON material_bindings(material_id, group_id);
|
||||
113
migrations/002_partition_bindings.sql
Normal file
113
migrations/002_partition_bindings.sql
Normal file
@@ -0,0 +1,113 @@
|
||||
-- ============================================
|
||||
-- Migration: 002_partition_bindings.sql
|
||||
-- Purpose: 将 material_bindings 表改为 HASH 分区表
|
||||
-- 支持亿级数据规模
|
||||
-- ============================================
|
||||
|
||||
-- 注意: 此迁移需要在维护窗口执行,会有短暂锁表
|
||||
-- 预估执行时间: 取决于现有数据量
|
||||
|
||||
BEGIN;
|
||||
|
||||
-- 1. 创建新的分区表
|
||||
CREATE TABLE material_bindings_new (
|
||||
id BIGSERIAL,
|
||||
material_id BIGINT NOT NULL,
|
||||
group_id VARCHAR(255) NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (material_id, id)
|
||||
) PARTITION BY HASH (material_id);
|
||||
|
||||
-- 2. 创建 16 个分区 (每个分区预计容纳 600-1000 万行)
|
||||
CREATE TABLE material_bindings_p0 PARTITION OF material_bindings_new
|
||||
FOR VALUES WITH (MODULUS 16, REMAINDER 0);
|
||||
CREATE TABLE material_bindings_p1 PARTITION OF material_bindings_new
|
||||
FOR VALUES WITH (MODULUS 16, REMAINDER 1);
|
||||
CREATE TABLE material_bindings_p2 PARTITION OF material_bindings_new
|
||||
FOR VALUES WITH (MODULUS 16, REMAINDER 2);
|
||||
CREATE TABLE material_bindings_p3 PARTITION OF material_bindings_new
|
||||
FOR VALUES WITH (MODULUS 16, REMAINDER 3);
|
||||
CREATE TABLE material_bindings_p4 PARTITION OF material_bindings_new
|
||||
FOR VALUES WITH (MODULUS 16, REMAINDER 4);
|
||||
CREATE TABLE material_bindings_p5 PARTITION OF material_bindings_new
|
||||
FOR VALUES WITH (MODULUS 16, REMAINDER 5);
|
||||
CREATE TABLE material_bindings_p6 PARTITION OF material_bindings_new
|
||||
FOR VALUES WITH (MODULUS 16, REMAINDER 6);
|
||||
CREATE TABLE material_bindings_p7 PARTITION OF material_bindings_new
|
||||
FOR VALUES WITH (MODULUS 16, REMAINDER 7);
|
||||
CREATE TABLE material_bindings_p8 PARTITION OF material_bindings_new
|
||||
FOR VALUES WITH (MODULUS 16, REMAINDER 8);
|
||||
CREATE TABLE material_bindings_p9 PARTITION OF material_bindings_new
|
||||
FOR VALUES WITH (MODULUS 16, REMAINDER 9);
|
||||
CREATE TABLE material_bindings_p10 PARTITION OF material_bindings_new
|
||||
FOR VALUES WITH (MODULUS 16, REMAINDER 10);
|
||||
CREATE TABLE material_bindings_p11 PARTITION OF material_bindings_new
|
||||
FOR VALUES WITH (MODULUS 16, REMAINDER 11);
|
||||
CREATE TABLE material_bindings_p12 PARTITION OF material_bindings_new
|
||||
FOR VALUES WITH (MODULUS 16, REMAINDER 12);
|
||||
CREATE TABLE material_bindings_p13 PARTITION OF material_bindings_new
|
||||
FOR VALUES WITH (MODULUS 16, REMAINDER 13);
|
||||
CREATE TABLE material_bindings_p14 PARTITION OF material_bindings_new
|
||||
FOR VALUES WITH (MODULUS 16, REMAINDER 14);
|
||||
CREATE TABLE material_bindings_p15 PARTITION OF material_bindings_new
|
||||
FOR VALUES WITH (MODULUS 16, REMAINDER 15);
|
||||
|
||||
-- 3. 为每个分区创建 group_id 索引 (用于按 group 查询)
|
||||
CREATE INDEX idx_bindings_p0_group ON material_bindings_p0(group_id);
|
||||
CREATE INDEX idx_bindings_p1_group ON material_bindings_p1(group_id);
|
||||
CREATE INDEX idx_bindings_p2_group ON material_bindings_p2(group_id);
|
||||
CREATE INDEX idx_bindings_p3_group ON material_bindings_p3(group_id);
|
||||
CREATE INDEX idx_bindings_p4_group ON material_bindings_p4(group_id);
|
||||
CREATE INDEX idx_bindings_p5_group ON material_bindings_p5(group_id);
|
||||
CREATE INDEX idx_bindings_p6_group ON material_bindings_p6(group_id);
|
||||
CREATE INDEX idx_bindings_p7_group ON material_bindings_p7(group_id);
|
||||
CREATE INDEX idx_bindings_p8_group ON material_bindings_p8(group_id);
|
||||
CREATE INDEX idx_bindings_p9_group ON material_bindings_p9(group_id);
|
||||
CREATE INDEX idx_bindings_p10_group ON material_bindings_p10(group_id);
|
||||
CREATE INDEX idx_bindings_p11_group ON material_bindings_p11(group_id);
|
||||
CREATE INDEX idx_bindings_p12_group ON material_bindings_p12(group_id);
|
||||
CREATE INDEX idx_bindings_p13_group ON material_bindings_p13(group_id);
|
||||
CREATE INDEX idx_bindings_p14_group ON material_bindings_p14(group_id);
|
||||
CREATE INDEX idx_bindings_p15_group ON material_bindings_p15(group_id);
|
||||
|
||||
-- 4. 为每个分区创建唯一约束 (防止重复绑定)
|
||||
CREATE UNIQUE INDEX idx_bindings_p0_unique ON material_bindings_p0(material_id, group_id);
|
||||
CREATE UNIQUE INDEX idx_bindings_p1_unique ON material_bindings_p1(material_id, group_id);
|
||||
CREATE UNIQUE INDEX idx_bindings_p2_unique ON material_bindings_p2(material_id, group_id);
|
||||
CREATE UNIQUE INDEX idx_bindings_p3_unique ON material_bindings_p3(material_id, group_id);
|
||||
CREATE UNIQUE INDEX idx_bindings_p4_unique ON material_bindings_p4(material_id, group_id);
|
||||
CREATE UNIQUE INDEX idx_bindings_p5_unique ON material_bindings_p5(material_id, group_id);
|
||||
CREATE UNIQUE INDEX idx_bindings_p6_unique ON material_bindings_p6(material_id, group_id);
|
||||
CREATE UNIQUE INDEX idx_bindings_p7_unique ON material_bindings_p7(material_id, group_id);
|
||||
CREATE UNIQUE INDEX idx_bindings_p8_unique ON material_bindings_p8(material_id, group_id);
|
||||
CREATE UNIQUE INDEX idx_bindings_p9_unique ON material_bindings_p9(material_id, group_id);
|
||||
CREATE UNIQUE INDEX idx_bindings_p10_unique ON material_bindings_p10(material_id, group_id);
|
||||
CREATE UNIQUE INDEX idx_bindings_p11_unique ON material_bindings_p11(material_id, group_id);
|
||||
CREATE UNIQUE INDEX idx_bindings_p12_unique ON material_bindings_p12(material_id, group_id);
|
||||
CREATE UNIQUE INDEX idx_bindings_p13_unique ON material_bindings_p13(material_id, group_id);
|
||||
CREATE UNIQUE INDEX idx_bindings_p14_unique ON material_bindings_p14(material_id, group_id);
|
||||
CREATE UNIQUE INDEX idx_bindings_p15_unique ON material_bindings_p15(material_id, group_id);
|
||||
|
||||
-- 5. 迁移现有数据 (如果表已存在)
|
||||
-- 注意: 如果数据量大,建议分批迁移
|
||||
INSERT INTO material_bindings_new (id, material_id, group_id, created_at)
|
||||
SELECT id, material_id, group_id, created_at
|
||||
FROM material_bindings
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- 6. 切换表名
|
||||
ALTER TABLE material_bindings RENAME TO material_bindings_old;
|
||||
ALTER TABLE material_bindings_new RENAME TO material_bindings;
|
||||
|
||||
-- 7. 重置序列 (确保新插入的 ID 不冲突)
|
||||
SELECT setval('material_bindings_new_id_seq', COALESCE((SELECT MAX(id) FROM material_bindings), 0) + 1, false);
|
||||
|
||||
COMMIT;
|
||||
|
||||
-- ============================================
|
||||
-- 回滚脚本 (如需回滚,手动执行)
|
||||
-- ============================================
|
||||
-- BEGIN;
|
||||
-- DROP TABLE IF EXISTS material_bindings CASCADE;
|
||||
-- ALTER TABLE material_bindings_old RENAME TO material_bindings;
|
||||
-- COMMIT;
|
||||
26
migrations/003_add_indexes.sql
Normal file
26
migrations/003_add_indexes.sql
Normal file
@@ -0,0 +1,26 @@
|
||||
-- ============================================
|
||||
-- Migration: 003_add_indexes.sql
|
||||
-- Purpose: 添加性能优化索引
|
||||
-- ============================================
|
||||
|
||||
-- 1. 启用 pg_trgm 扩展 (支持模糊搜索索引)
|
||||
CREATE EXTENSION IF NOT EXISTS pg_trgm;
|
||||
|
||||
-- 2. 材质名称模糊搜索索引 (GIN 索引,支持 ILIKE '%keyword%')
|
||||
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_materials_name_trgm
|
||||
ON materials USING gin (name gin_trgm_ops);
|
||||
|
||||
-- 3. 材质创建时间索引 (用于排序)
|
||||
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_materials_created_at
|
||||
ON materials(created_at DESC);
|
||||
|
||||
-- 4. 材质更新时间索引
|
||||
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_materials_updated_at
|
||||
ON materials(updated_at DESC);
|
||||
|
||||
-- 注意: 如果使用了分区表 (002_partition_bindings.sql),
|
||||
-- 以下索引已在分区迁移中创建,可跳过
|
||||
|
||||
-- 5. binding 表 group_id + material_id 复合索引 (优化批量查询)
|
||||
-- CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_bindings_group_material
|
||||
-- ON material_bindings(group_id, material_id);
|
||||
11
migrations/004_add_version_column.sql
Normal file
11
migrations/004_add_version_column.sql
Normal file
@@ -0,0 +1,11 @@
|
||||
-- ============================================
|
||||
-- Migration: 004_add_version_column.sql
|
||||
-- Purpose: 添加乐观锁版本字段
|
||||
-- ============================================
|
||||
|
||||
-- 添加 version 字段到 materials 表 (用于乐观锁)
|
||||
ALTER TABLE materials
|
||||
ADD COLUMN IF NOT EXISTS version BIGINT NOT NULL DEFAULT 0;
|
||||
|
||||
-- 注释
|
||||
COMMENT ON COLUMN materials.version IS '乐观锁版本号,用于防止并发更新覆盖';
|
||||
72
pkg/response/response.go
Normal file
72
pkg/response/response.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package response
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type Response struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Data interface{} `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
type PagedData struct {
|
||||
Items interface{} `json:"items"`
|
||||
Total int64 `json:"total"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
}
|
||||
|
||||
func Success(c *gin.Context, data interface{}) {
|
||||
c.JSON(http.StatusOK, Response{
|
||||
Code: 0,
|
||||
Message: "success",
|
||||
Data: data,
|
||||
})
|
||||
}
|
||||
|
||||
func SuccessPaged(c *gin.Context, items interface{}, total int64, page, pageSize int) {
|
||||
c.JSON(http.StatusOK, Response{
|
||||
Code: 0,
|
||||
Message: "success",
|
||||
Data: PagedData{
|
||||
Items: items,
|
||||
Total: total,
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func Created(c *gin.Context, data interface{}) {
|
||||
c.JSON(http.StatusCreated, Response{
|
||||
Code: 0,
|
||||
Message: "created",
|
||||
Data: data,
|
||||
})
|
||||
}
|
||||
|
||||
func Error(c *gin.Context, statusCode int, message string) {
|
||||
c.JSON(statusCode, Response{
|
||||
Code: statusCode,
|
||||
Message: message,
|
||||
})
|
||||
}
|
||||
|
||||
func BadRequest(c *gin.Context, message string) {
|
||||
Error(c, http.StatusBadRequest, message)
|
||||
}
|
||||
|
||||
func NotFound(c *gin.Context, message string) {
|
||||
Error(c, http.StatusNotFound, message)
|
||||
}
|
||||
|
||||
func Unauthorized(c *gin.Context, message string) {
|
||||
Error(c, http.StatusUnauthorized, message)
|
||||
}
|
||||
|
||||
func InternalError(c *gin.Context, message string) {
|
||||
Error(c, http.StatusInternalServerError, message)
|
||||
}
|
||||
91
test_api.sh
Executable file
91
test_api.sh
Executable file
@@ -0,0 +1,91 @@
|
||||
#!/bin/bash
|
||||
# API测试脚本
|
||||
BASE_URL="http://localhost:8081"
|
||||
TOKEN="seatons3d"
|
||||
|
||||
echo "=========================================="
|
||||
echo "材质管理API接口测试"
|
||||
echo "=========================================="
|
||||
|
||||
echo ""
|
||||
echo "1. GET /api/v1/materials - 材质列表"
|
||||
echo "------------------------------------------"
|
||||
curl -s -H "X-API-Token: $TOKEN" "$BASE_URL/api/v1/materials?page=1&page_size=3" | jq .
|
||||
|
||||
echo ""
|
||||
echo "2. POST /api/v1/materials - 添加材质"
|
||||
echo "------------------------------------------"
|
||||
NEW_MATERIAL=$(curl -s -X POST -H "X-API-Token: $TOKEN" -H "Content-Type: application/json" \
|
||||
-d '{"name":"测试材质API","diffuse_r":100,"diffuse_g":150,"diffuse_b":200,"alpha":255,"metallic":0.5,"roughness":0.3}' \
|
||||
"$BASE_URL/api/v1/materials")
|
||||
echo "$NEW_MATERIAL" | jq .
|
||||
NEW_ID=$(echo "$NEW_MATERIAL" | jq -r '.data.id')
|
||||
echo "新建材质ID: $NEW_ID"
|
||||
|
||||
echo ""
|
||||
echo "3. GET /api/v1/materials/:id - 获取材质详情"
|
||||
echo "------------------------------------------"
|
||||
curl -s -H "X-API-Token: $TOKEN" "$BASE_URL/api/v1/materials/$NEW_ID" | jq .
|
||||
|
||||
echo ""
|
||||
echo "4. PUT /api/v1/materials/:id - 编辑材质"
|
||||
echo "------------------------------------------"
|
||||
curl -s -X PUT -H "X-API-Token: $TOKEN" -H "Content-Type: application/json" \
|
||||
-d '{"name":"测试材质API-已更新","diffuse_r":200,"diffuse_g":100,"diffuse_b":50,"alpha":255,"metallic":0.8,"roughness":0.2}' \
|
||||
"$BASE_URL/api/v1/materials/$NEW_ID" | jq .
|
||||
|
||||
echo ""
|
||||
echo "5. DELETE /api/v1/materials/:id - 删除材质"
|
||||
echo "------------------------------------------"
|
||||
curl -s -X DELETE -H "X-API-Token: $TOKEN" "$BASE_URL/api/v1/materials/$NEW_ID" | jq .
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo "绑定管理API接口测试"
|
||||
echo "=========================================="
|
||||
|
||||
echo ""
|
||||
echo "6. POST /api/v1/materials/:id/bindings - 绑定材质"
|
||||
echo "------------------------------------------"
|
||||
curl -s -X POST -H "X-API-Token: $TOKEN" -H "Content-Type: application/json" \
|
||||
-d '{"group_ids": ["test_group_001", "test_group_002", "test_group_003"]}' \
|
||||
"$BASE_URL/api/v1/materials/4/bindings" | jq .
|
||||
|
||||
echo ""
|
||||
echo "7. GET /api/v1/materials/:id/groups - 获取材质关联的groups"
|
||||
echo "------------------------------------------"
|
||||
curl -s -H "X-API-Token: $TOKEN" "$BASE_URL/api/v1/materials/4/groups" | jq '{material_id: .data.material_id, group_count: (.data.group_ids | length), sample_groups: (.data.group_ids[:5])}'
|
||||
|
||||
echo ""
|
||||
echo "8. POST /api/v1/groups/materials - 根据group_ids查询材质"
|
||||
echo "------------------------------------------"
|
||||
curl -s -X POST -H "X-API-Token: $TOKEN" -H "Content-Type: application/json" \
|
||||
-d '{"group_ids": ["test_group_001", "202510211057245681447"]}' \
|
||||
"$BASE_URL/api/v1/groups/materials" | jq .
|
||||
|
||||
echo ""
|
||||
echo "9. DELETE /api/v1/materials/:id/bindings - 解绑材质"
|
||||
echo "------------------------------------------"
|
||||
curl -s -X DELETE -H "X-API-Token: $TOKEN" -H "Content-Type: application/json" \
|
||||
-d '{"group_ids": ["test_group_001", "test_group_002", "test_group_003"]}' \
|
||||
"$BASE_URL/api/v1/materials/4/bindings" | jq .
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo "错误处理测试"
|
||||
echo "=========================================="
|
||||
|
||||
echo ""
|
||||
echo "10. 无Token访问"
|
||||
echo "------------------------------------------"
|
||||
curl -s "$BASE_URL/api/v1/materials" | jq .
|
||||
|
||||
echo ""
|
||||
echo "11. 访问不存在的材质"
|
||||
echo "------------------------------------------"
|
||||
curl -s -H "X-API-Token: $TOKEN" "$BASE_URL/api/v1/materials/99999999" | jq .
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo "测试完成!"
|
||||
echo "=========================================="
|
||||
Reference in New Issue
Block a user