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:
likegears
2025-12-11 15:29:49 +08:00
commit 85ba15c564
31 changed files with 1518167 additions and 0 deletions

8
.env.example Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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;
```

View 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

File diff suppressed because it is too large Load Diff

304
export_tables/color.csv Normal file
View 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,
1 ColorId,Name,DiffuseRed,DiffuseGreen,DiffuseBlue,Alpha,Shininess,SpecularRed,SpecularGreen,SpecularBlue,AmbientRed,AmbientGreen,AmbientBlue,Metallic,Roughness,Reflectance
2 -1,默认值,192,192,192,255,20,230,230,230,50,50,50,3,6,0,
3 -9,红色,255,0,0,255,20,230,230,230,50,50,50,3,6,0,
4 -8,定位颜色,254,254,101,100,20,230,230,230,50,50,50,3,6,0,
5 0,dgnColor0,0,0,0,255,20,230,230,230,50,50,50,0,0,0,
6 1,dgnColor1,204,204,204,255,20,230,230,230,50,50,50,0,0,0,
7 2,dgnColor2,105,150,255,255,20,230,230,230,50,50,50,0,0,0,
8 3,dgnColor3,0,204,0,255,20,230,230,230,50,50,50,0,0,0,
9 4,dgnColor4,255,0,0,255,20,230,230,230,50,50,50,0,0,0,
10 5,dgnColor5,255,255,0,255,20,230,230,230,50,50,50,0,0,0,
11 6,dgnColor6,255,0,255,255,20,230,230,230,50,50,50,0,0,0,
12 7,dgnColor7,255,155,0,255,20,230,230,230,50,50,50,0,0,0,
13 8,dgnColor8,138,128,128,255,20,230,230,230,50,50,50,0,0,0,
14 9,dgnColor9,196,196,196,255,20,230,230,230,50,50,50,0,0,0,
15 10,dgnColor10,104,146,244,255,20,230,230,230,50,50,50,0,0,0,
16 11,dgnColor11,6,196,6,255,20,230,230,230,50,50,50,0,0,0,
17 12,dgnColor12,244,6,6,255,20,230,230,230,50,50,50,0,0,0,
18 13,dgnColor13,244,244,6,255,20,230,230,230,50,50,50,0,0,0,
19 14,dgnColor14,244,6,244,255,20,230,230,230,50,50,50,0,0,0,
20 15,dgnColor15,244,150,6,255,20,230,230,230,50,50,50,0,0,0,
21 16,dgnColor16,129,119,119,255,20,230,230,230,50,50,50,0,0,0,
22 17,dgnColor17,188,188,188,255,20,230,230,230,50,50,50,0,0,0,
23 18,dgnColor18,102,141,232,255,20,230,230,230,50,50,50,0,0,0,
24 19,dgnColor19,11,188,11,255,20,230,230,230,50,50,50,0,0,0,
25 20,dgnColor20,232,11,11,255,20,230,230,230,50,50,50,0,0,0,
26 21,dgnColor21,232,232,11,255,20,230,230,230,50,50,50,0,0,0,
27 22,dgnColor22,232,11,232,255,20,230,230,230,50,50,50,0,0,0,
28 23,dgnColor23,232,146,11,255,20,230,230,230,50,50,50,0,0,0,
29 24,dgnColor24,119,111,111,255,20,230,230,230,50,50,50,0,0,0,
30 25,dgnColor25,180,180,180,255,20,230,230,230,50,50,50,0,0,0,
31 26,dgnColor26,101,137,221,255,20,230,230,230,50,50,50,0,0,0,
32 27,dgnColor27,17,180,17,255,20,230,230,230,50,50,50,0,0,0,
33 28,dgnColor28,221,17,17,255,20,230,230,230,50,50,50,0,0,0,
34 29,dgnColor29,221,221,17,255,20,230,230,230,50,50,50,0,0,0,
35 30,dgnColor30,221,17,221,255,20,230,230,230,50,50,50,0,0,0,
36 31,dgnColor31,221,141,17,255,20,230,230,230,50,50,50,0,0,0,
37 32,dgnColor32,110,102,102,255,20,230,230,230,50,50,50,0,0,0,
38 33,dgnColor33,172,172,172,255,20,230,230,230,50,50,50,0,0,0,
39 34,dgnColor34,100,133,210,255,20,230,230,230,50,50,50,0,0,0,
40 35,dgnColor35,23,172,23,255,20,230,230,230,50,50,50,0,0,0,
41 36,dgnColor36,210,23,23,255,20,230,230,230,50,50,50,0,0,0,
42 37,dgnColor37,210,210,23,255,20,230,230,230,50,50,50,0,0,0,
43 38,dgnColor38,210,23,210,255,20,230,230,230,50,50,50,0,0,0,
44 39,dgnColor39,210,136,23,255,20,230,230,230,50,50,50,0,0,0,
45 40,dgnColor40,101,94,94,255,20,230,230,230,50,50,50,0,0,0,
46 41,dgnColor41,164,164,164,255,20,230,230,230,50,50,50,0,0,0,
47 42,dgnColor42,98,128,198,255,20,230,230,230,50,50,50,0,0,0,
48 43,dgnColor43,28,164,28,255,20,230,230,230,50,50,50,0,0,0,
49 44,dgnColor44,198,28,28,255,20,230,230,230,50,50,50,0,0,0,
50 45,dgnColor45,198,198,28,255,20,230,230,230,50,50,50,0,0,0,
51 46,dgnColor46,198,28,198,255,20,230,230,230,50,50,50,0,0,0,
52 47,dgnColor47,198,132,28,255,20,230,230,230,50,50,50,0,0,0,
53 48,dgnColor48,92,85,85,255,20,230,230,230,50,50,50,0,0,0,
54 49,dgnColor49,156,156,156,255,20,230,230,230,50,50,50,0,0,0,
55 50,dgnColor50,97,124,187,255,20,230,230,230,50,50,50,0,0,0,
56 51,dgnColor51,34,156,34,255,20,230,230,230,50,50,50,0,0,0,
57 52,dgnColor52,187,34,34,255,20,230,230,230,50,50,50,0,0,0,
58 53,dgnColor53,187,187,34,255,20,230,230,230,50,50,50,0,0,0,
59 54,dgnColor54,187,34,187,255,20,230,230,230,50,50,50,0,0,0,
60 55,dgnColor55,187,127,34,255,20,230,230,230,50,50,50,0,0,0,
61 56,dgnColor56,83,77,77,255,20,230,230,230,50,50,50,0,0,0,
62 57,dgnColor57,148,148,148,255,20,230,230,230,50,50,50,0,0,0,
63 58,dgnColor58,96,120,176,255,20,230,230,230,50,50,50,0,0,0,
64 59,dgnColor59,40,148,40,255,20,230,230,230,50,50,50,0,0,0,
65 60,dgnColor60,176,40,40,255,20,230,230,230,50,50,50,0,0,0,
66 61,dgnColor61,176,176,40,255,20,230,230,230,50,50,50,0,0,0,
67 62,dgnColor62,176,40,176,255,20,230,230,230,50,50,50,0,0,0,
68 63,dgnColor63,176,122,40,255,20,230,230,230,50,50,50,0,0,0,
69 64,dgnColor64,73,68,68,255,20,230,230,230,50,50,50,0,0,0,
70 65,dgnColor65,141,141,141,255,20,230,230,230,50,50,50,0,0,0,
71 66,dgnColor66,94,115,164,255,20,230,230,230,50,50,50,0,0,0,
72 67,dgnColor67,45,141,45,255,20,230,230,230,50,50,50,0,0,0,
73 68,dgnColor68,164,45,45,255,20,230,230,230,50,50,50,0,0,0,
74 69,dgnColor69,164,164,45,255,20,230,230,230,50,50,50,0,0,0,
75 70,dgnColor70,164,45,164,255,20,230,230,230,50,50,50,0,0,0,
76 71,dgnColor71,164,118,45,255,20,230,230,230,50,50,50,0,0,0,
77 72,dgnColor72,64,60,60,255,20,230,230,230,50,50,50,0,0,0,
78 73,dgnColor73,133,133,133,255,20,230,230,230,50,50,50,0,0,0,
79 74,dgnColor74,93,111,153,255,20,230,230,230,50,50,50,0,0,0,
80 75,dgnColor75,51,133,51,255,20,230,230,230,50,50,50,0,0,0,
81 76,dgnColor76,153,51,51,255,20,230,230,230,50,50,50,0,0,0,
82 77,dgnColor77,153,153,51,255,20,230,230,230,50,50,50,0,0,0,
83 78,dgnColor78,153,51,153,255,20,230,230,230,50,50,50,0,0,0,
84 79,dgnColor79,153,113,51,255,20,230,230,230,50,50,50,0,0,0,
85 80,dgnColor80,55,51,51,255,20,230,230,230,50,50,50,0,0,0,
86 81,dgnColor81,125,125,125,255,20,230,230,230,50,50,50,0,0,0,
87 82,dgnColor82,92,107,142,255,20,230,230,230,50,50,50,0,0,0,
88 83,dgnColor83,57,125,57,255,20,230,230,230,50,50,50,0,0,0,
89 84,dgnColor84,142,57,57,255,20,230,230,230,50,50,50,0,0,0,
90 85,dgnColor85,142,142,57,255,20,230,230,230,50,50,50,0,0,0,
91 86,dgnColor86,142,57,142,255,20,230,230,230,50,50,50,0,0,0,
92 87,dgnColor87,142,108,57,255,20,230,230,230,50,50,50,0,0,0,
93 88,dgnColor88,46,43,43,255,20,230,230,230,50,50,50,0,0,0,
94 89,dgnColor89,117,117,117,255,20,230,230,230,50,50,50,0,0,0,
95 90,dgnColor90,90,102,130,255,20,230,230,230,50,50,50,0,0,0,
96 91,dgnColor91,62,117,62,255,20,230,230,230,50,50,50,0,0,0,
97 92,dgnColor92,130,62,62,255,20,230,230,230,50,50,50,0,0,0,
98 93,dgnColor93,130,130,62,255,20,230,230,230,50,50,50,0,0,0,
99 94,dgnColor94,130,62,130,255,20,230,230,230,50,50,50,0,0,0,
100 95,dgnColor95,130,104,62,255,20,230,230,230,50,50,50,0,0,0,
101 96,dgnColor96,37,34,34,255,20,230,230,230,50,50,50,0,0,0,
102 97,dgnColor97,109,109,109,255,20,230,230,230,50,50,50,0,0,0,
103 98,dgnColor98,89,98,119,255,20,230,230,230,50,50,50,0,0,0,
104 99,dgnColor99,68,109,68,255,20,230,230,230,50,50,50,0,0,0,
105 100,dgnColor100,119,68,68,255,20,230,230,230,50,50,50,0,0,0,
106 101,dgnColor101,119,119,68,255,20,230,230,230,50,50,50,0,0,0,
107 102,dgnColor102,119,68,119,255,20,230,230,230,50,50,50,0,0,0,
108 103,dgnColor103,119,99,68,255,20,230,230,230,50,50,50,0,0,0,
109 104,dgnColor104,28,26,26,255,20,230,230,230,50,50,50,0,0,0,
110 105,dgnColor105,101,101,101,255,20,230,230,230,50,50,50,0,0,0,
111 106,dgnColor106,88,94,108,255,20,230,230,230,50,50,50,0,0,0,
112 107,dgnColor107,74,101,74,255,20,230,230,230,50,50,50,0,0,0,
113 108,dgnColor108,108,74,74,255,20,230,230,230,50,50,50,0,0,0,
114 109,dgnColor109,108,108,74,255,20,230,230,230,50,50,50,0,0,0,
115 110,dgnColor110,108,74,108,255,20,230,230,230,50,50,50,0,0,0,
116 111,dgnColor111,108,94,74,255,20,230,230,230,50,50,50,0,0,0,
117 112,dgnColor112,18,17,17,255,20,230,230,230,50,50,50,0,0,0,
118 113,dgnColor113,93,93,93,255,20,230,230,230,50,50,50,0,0,0,
119 114,dgnColor114,86,89,96,255,20,230,230,230,50,50,50,0,0,0,
120 115,dgnColor115,79,93,79,255,20,230,230,230,50,50,50,0,0,0,
121 116,dgnColor116,96,79,79,255,20,230,230,230,50,50,50,0,0,0,
122 117,dgnColor117,96,96,79,255,20,230,230,230,50,50,50,0,0,0,
123 118,dgnColor118,96,79,96,255,20,230,230,230,50,50,50,0,0,0,
124 119,dgnColor119,96,90,79,255,20,230,230,230,50,50,50,0,0,0,
125 120,dgnColor120,9,9,9,255,20,230,230,230,50,50,50,0,0,0,
126 121,dgnColor121,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
127 122,dgnColor122,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
128 123,dgnColor123,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
129 124,dgnColor124,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
130 125,dgnColor125,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
131 126,dgnColor126,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
132 127,dgnColor127,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
133 128,dgnColor128,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
134 129,dgnColor129,141,141,141,255,20,230,230,230,50,50,50,0,0,0,
135 130,dgnColor130,94,115,164,255,20,230,230,230,50,50,50,0,0,0,
136 131,dgnColor131,45,141,45,255,20,230,230,230,50,50,50,0,0,0,
137 132,dgnColor132,164,45,45,255,20,230,230,230,50,50,50,0,0,0,
138 133,dgnColor133,164,164,45,255,20,230,230,230,50,50,50,0,0,0,
139 134,dgnColor134,164,45,164,255,20,230,230,230,50,50,50,0,0,0,
140 135,dgnColor135,164,118,45,255,20,230,230,230,50,50,50,0,0,0,
141 136,dgnColor136,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
142 137,dgnColor137,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
143 138,dgnColor138,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
144 139,dgnColor139,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
145 140,dgnColor140,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
146 141,dgnColor141,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
147 142,dgnColor142,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
148 143,dgnColor143,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
149 144,dgnColor144,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
150 145,dgnColor145,133,133,133,255,20,230,230,230,50,50,50,0,0,0,
151 146,dgnColor146,93,111,153,255,20,230,230,230,50,50,50,0,0,0,
152 147,dgnColor147,51,133,51,255,20,230,230,230,50,50,50,0,0,0,
153 148,dgnColor148,153,51,51,255,20,230,230,230,50,50,50,0,0,0,
154 149,dgnColor149,153,153,51,255,20,230,230,230,50,50,50,0,0,0,
155 150,dgnColor150,153,51,153,255,20,230,230,230,50,50,50,0,0,0,
156 151,dgnColor151,153,113,51,255,20,230,230,230,50,50,50,0,0,0,
157 152,dgnColor152,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
158 153,dgnColor153,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
159 154,dgnColor154,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
160 155,dgnColor155,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
161 156,dgnColor156,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
162 157,dgnColor157,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
163 158,dgnColor158,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
164 159,dgnColor159,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
165 160,dgnColor160,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
166 161,dgnColor161,125,125,125,255,20,230,230,230,50,50,50,0,0,0,
167 162,dgnColor162,92,107,142,255,20,230,230,230,50,50,50,0,0,0,
168 163,dgnColor163,57,125,57,255,20,230,230,230,50,50,50,0,0,0,
169 164,dgnColor164,142,57,57,255,20,230,230,230,50,50,50,0,0,0,
170 165,dgnColor165,142,142,57,255,20,230,230,230,50,50,50,0,0,0,
171 166,dgnColor166,142,57,142,255,20,230,230,230,50,50,50,0,0,0,
172 167,dgnColor167,142,108,57,255,20,230,230,230,50,50,50,0,0,0,
173 168,dgnColor168,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
174 169,dgnColor169,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
175 170,dgnColor170,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
176 171,dgnColor171,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
177 172,dgnColor172,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
178 173,dgnColor173,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
179 174,dgnColor174,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
180 175,dgnColor175,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
181 176,dgnColor176,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
182 177,dgnColor177,117,117,117,255,20,230,230,230,50,50,50,0,0,0,
183 178,dgnColor178,90,102,130,255,20,230,230,230,50,50,50,0,0,0,
184 179,dgnColor179,62,117,62,255,20,230,230,230,50,50,50,0,0,0,
185 180,dgnColor180,130,62,62,255,20,230,230,230,50,50,50,0,0,0,
186 181,dgnColor181,130,130,62,255,20,230,230,230,50,50,50,0,0,0,
187 182,dgnColor182,130,62,130,255,20,230,230,230,50,50,50,0,0,0,
188 183,dgnColor183,130,104,62,255,20,230,230,230,50,50,50,0,0,0,
189 184,dgnColor184,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
190 185,dgnColor185,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
191 186,dgnColor186,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
192 187,dgnColor187,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
193 188,dgnColor188,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
194 189,dgnColor189,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
195 190,dgnColor190,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
196 191,dgnColor191,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
197 192,dgnColor192,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
198 193,dgnColor193,109,109,109,255,20,230,230,230,50,50,50,0,0,0,
199 194,dgnColor194,89,98,119,255,20,230,230,230,50,50,50,0,0,0,
200 195,dgnColor195,68,109,68,255,20,230,230,230,50,50,50,0,0,0,
201 196,dgnColor196,119,68,68,255,20,230,230,230,50,50,50,0,0,0,
202 197,dgnColor197,119,119,68,255,20,230,230,230,50,50,50,0,0,0,
203 198,dgnColor198,119,68,119,255,20,230,230,230,50,50,50,0,0,0,
204 199,dgnColor199,119,99,68,255,20,230,230,230,50,50,50,0,0,0,
205 200,dgnColor200,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
206 201,dgnColor201,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
207 202,dgnColor202,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
208 203,dgnColor203,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
209 204,dgnColor204,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
210 205,dgnColor205,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
211 206,dgnColor206,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
212 207,dgnColor207,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
213 208,dgnColor208,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
214 209,dgnColor209,101,101,101,255,20,230,230,230,50,50,50,0,0,0,
215 210,dgnColor210,88,94,108,255,20,230,230,230,50,50,50,0,0,0,
216 211,dgnColor211,74,101,74,255,20,230,230,230,50,50,50,0,0,0,
217 212,dgnColor212,108,74,74,255,20,230,230,230,50,50,50,0,0,0,
218 213,dgnColor213,108,108,74,255,20,230,230,230,50,50,50,0,0,0,
219 214,dgnColor214,108,74,108,255,20,230,230,230,50,50,50,0,0,0,
220 215,dgnColor215,108,94,74,255,20,230,230,230,50,50,50,0,0,0,
221 216,dgnColor216,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
222 217,dgnColor217,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
223 218,dgnColor218,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
224 219,dgnColor219,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
225 220,dgnColor220,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
226 221,dgnColor221,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
227 222,dgnColor222,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
228 223,dgnColor223,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
229 224,dgnColor224,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
230 225,dgnColor225,93,93,93,255,20,230,230,230,50,50,50,0,0,0,
231 226,dgnColor226,86,89,96,255,20,230,230,230,50,50,50,0,0,0,
232 227,dgnColor227,79,93,79,255,20,230,230,230,50,50,50,0,0,0,
233 228,dgnColor228,96,79,79,255,20,230,230,230,50,50,50,0,0,0,
234 229,dgnColor229,96,96,79,255,20,230,230,230,50,50,50,0,0,0,
235 230,dgnColor230,96,79,96,255,20,230,230,230,50,50,50,0,0,0,
236 231,dgnColor231,96,90,79,255,20,230,230,230,50,50,50,0,0,0,
237 232,dgnColor232,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
238 233,dgnColor233,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
239 234,dgnColor234,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
240 235,dgnColor235,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
241 236,dgnColor236,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
242 237,dgnColor237,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
243 238,dgnColor238,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
244 239,dgnColor239,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
245 240,dgnColor240,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
246 241,dgnColor241,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
247 242,dgnColor242,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
248 243,dgnColor243,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
249 244,dgnColor244,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
250 245,dgnColor245,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
251 246,dgnColor246,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
252 247,dgnColor247,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
253 248,dgnColor248,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
254 249,dgnColor249,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
255 250,dgnColor250,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
256 251,dgnColor251,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
257 252,dgnColor252,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
258 253,dgnColor253,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
259 254,dgnColor254,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
260 255,dgnColor255,255,255,255,255,20,230,230,230,50,50,50,0,0,0,
261 256,银白色,220,220,220,255,20,230,230,230,50,50,50,0,0,0,
262 257,艳绿色,0,200,70,255,20,230,230,230,50,50,50,0,0,0,
263 258,粉红色,250,128,128,255,20,230,230,230,50,50,50,0,0,0,
264 259,浅蓝色,157,215,255,255,20,230,230,230,50,50,50,0,0,0,
265 260,橙黄色,255,120,30,255,20,230,230,230,50,50,50,0,0,0,
266 261,银灰色,190,205,210,255,20,230,230,230,50,50,50,0,0,0,
267 265,dgnColor0,0,0,0,255,20,230,230,230,50,50,50,0,0,0,
268 266,dgnColor1,204,204,204,255,20,230,230,230,50,50,50,0,0,0,
269 267,dgnColor2,105,150,255,255,20,230,230,230,50,50,50,0,0,0,
270 268,dgnColor3,0,204,0,255,20,230,230,230,50,50,50,0,0,0,
271 269,dgnColor4,255,0,0,255,20,230,230,230,50,50,50,0,0,0,
272 270,dgnColor5,255,255,0,255,20,230,230,230,50,50,50,0,0,0,
273 271,dgnColor7,255,155,0,255,20,230,230,230,50,50,50,0,0,0,
274 272,dgnColor14,244,6,244,255,20,230,230,230,50,50,50,0,0,0,
275 273,dgnColor16,129,119,119,255,20,230,230,230,50,50,50,0,0,0,
276 274,dgnColor17,188,188,188,255,20,230,230,230,50,50,50,0,0,0,
277 275,dgnColor18,102,141,232,255,20,230,230,230,50,50,50,0,0,0,
278 276,dgnColor21,232,232,11,255,20,230,230,230,50,50,50,0,0,0,
279 277,dgnColor24,119,111,111,255,20,230,230,230,50,50,50,0,0,0,
280 278,dgnColor32,110,102,102,255,20,230,230,230,50,50,50,0,0,0,
281 279,dgnColor34,100,133,210,255,20,230,230,230,50,50,50,0,0,0,
282 280,dgnColor35,23,172,23,255,20,230,230,230,50,50,50,0,0,0,
283 281,dgnColor36,210,23,23,255,20,230,230,230,50,50,50,0,0,0,
284 282,dgnColor37,210,210,23,255,20,230,230,230,50,50,50,0,0,0,
285 283,dgnColor39,210,136,23,255,20,230,230,230,50,50,50,0,0,0,
286 284,dgnColor40,101,94,94,255,20,230,230,230,50,50,50,0,0,0,
287 285,dgnColor41,164,164,164,255,20,230,230,230,50,50,50,0,0,0,
288 286,dgnColor42,98,128,198,255,20,230,230,230,50,50,50,0,0,0,
289 287,dgnColor43,28,164,28,255,20,230,230,230,50,50,50,0,0,0,
290 288,dgnColor44,198,28,28,255,20,230,230,230,50,50,50,0,0,0,
291 289,dgnColor45,198,198,28,255,20,230,230,230,50,50,50,0,0,0,
292 290,dgnColor46,198,28,198,255,20,230,230,230,50,50,50,0,0,0,
293 291,dgnColor47,198,132,28,255,20,230,230,230,50,50,50,0,0,0,
294 292,dgnColor49,156,156,156,255,20,230,230,230,50,50,50,0,0,0,
295 293,dgnColor50,97,124,187,255,20,230,230,230,50,50,50,0,0,0,
296 294,dgnColor51,34,156,34,255,20,230,230,230,50,50,50,0,0,0,
297 295,dgnColor54,187,34,187,255,20,230,230,230,50,50,50,0,0,0,
298 296,dgnColor55,187,127,34,255,20,230,230,230,50,50,50,0,0,0,
299 297,dgnColor108,108,74,74,255,20,230,230,230,50,50,50,0,0,0,
300 298,dgnColor115,79,93,79,255,20,230,230,230,50,50,50,0,0,0,
301 299,dgnColor119,96,90,79,255,20,230,230,230,50,50,50,0,0,0,
302 300,dgnColor148,153,51,51,255,20,230,230,230,50,50,50,0,0,0,
303 301,dgnColor249,85,85,85,255,20,230,230,230,50,50,50,0,0,0,
304 302,dgnColor252,85,85,85,255,20,230,230,230,50,50,50,0,0,0,

757537
export_tables/map.csv Normal file

File diff suppressed because it is too large Load Diff

49
go.mod Normal file
View 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
View 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
View 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
}

View 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
}

View 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)
}

View 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})
}

View 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()
}
}

View 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"`
}

View 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"`
}

View 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
}

View 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
View 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
View 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);

View 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;

View 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);

View 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
View 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
View 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 "=========================================="