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:
157
internal/handlers/binding.go
Normal file
157
internal/handlers/binding.go
Normal file
@@ -0,0 +1,157 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"material_texture/internal/models"
|
||||
"material_texture/internal/repository"
|
||||
"material_texture/pkg/response"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type BindingHandler struct {
|
||||
bindingRepo *repository.BindingRepository
|
||||
materialRepo *repository.MaterialRepository
|
||||
}
|
||||
|
||||
func NewBindingHandler(bindingRepo *repository.BindingRepository, materialRepo *repository.MaterialRepository) *BindingHandler {
|
||||
return &BindingHandler{
|
||||
bindingRepo: bindingRepo,
|
||||
materialRepo: materialRepo,
|
||||
}
|
||||
}
|
||||
|
||||
// BindMaterial 绑定材质到多个group_id
|
||||
// POST /api/v1/materials/:id/bindings
|
||||
// Body: {"group_ids": ["g1", "g2", "g3"]}
|
||||
func (h *BindingHandler) BindMaterial(c *gin.Context) {
|
||||
materialID, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
response.BadRequest(c, "invalid material id")
|
||||
return
|
||||
}
|
||||
|
||||
// 检查材质是否存在
|
||||
exists, err := h.materialRepo.Exists(materialID)
|
||||
if err != nil {
|
||||
response.InternalError(c, "failed to check material")
|
||||
return
|
||||
}
|
||||
if !exists {
|
||||
response.NotFound(c, "material not found")
|
||||
return
|
||||
}
|
||||
|
||||
var req models.BindingRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "invalid request body: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.bindingRepo.BindMaterial(materialID, req.GroupIDs); err != nil {
|
||||
response.InternalError(c, "failed to bind material")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"material_id": materialID,
|
||||
"group_ids": req.GroupIDs,
|
||||
})
|
||||
}
|
||||
|
||||
// UnbindMaterial 解绑材质与指定group_id
|
||||
// DELETE /api/v1/materials/:id/bindings
|
||||
// Body: {"group_ids": ["g1", "g2"]}
|
||||
func (h *BindingHandler) UnbindMaterial(c *gin.Context) {
|
||||
materialID, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
response.BadRequest(c, "invalid material id")
|
||||
return
|
||||
}
|
||||
|
||||
var req models.UnbindRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "invalid request body: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.bindingRepo.UnbindMaterial(materialID, req.GroupIDs); err != nil {
|
||||
response.InternalError(c, "failed to unbind material")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{
|
||||
"material_id": materialID,
|
||||
"unbound": req.GroupIDs,
|
||||
})
|
||||
}
|
||||
|
||||
// GetGroupsByMaterial 获取材质关联的所有group_id (分页版本)
|
||||
// GET /api/v1/materials/:id/groups?page=1&page_size=20
|
||||
func (h *BindingHandler) GetGroupsByMaterial(c *gin.Context) {
|
||||
materialID, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
response.BadRequest(c, "invalid material id")
|
||||
return
|
||||
}
|
||||
|
||||
// 解析分页参数
|
||||
var query models.GroupListQuery
|
||||
if err := c.ShouldBindQuery(&query); err != nil {
|
||||
response.BadRequest(c, "invalid query parameters")
|
||||
return
|
||||
}
|
||||
|
||||
// 设置默认值
|
||||
if query.Page < 1 {
|
||||
query.Page = 1
|
||||
}
|
||||
if query.PageSize < 1 || query.PageSize > 100 {
|
||||
query.PageSize = 20
|
||||
}
|
||||
|
||||
// 检查材质是否存在
|
||||
exists, err := h.materialRepo.Exists(materialID)
|
||||
if err != nil {
|
||||
response.InternalError(c, "failed to check material")
|
||||
return
|
||||
}
|
||||
if !exists {
|
||||
response.NotFound(c, "material not found")
|
||||
return
|
||||
}
|
||||
|
||||
groupIDs, total, err := h.bindingRepo.GetGroupsByMaterialID(materialID, query.Page, query.PageSize)
|
||||
if err != nil {
|
||||
response.InternalError(c, "failed to get groups")
|
||||
return
|
||||
}
|
||||
|
||||
// 返回分页格式
|
||||
response.Success(c, models.GroupListResponse{
|
||||
Items: groupIDs,
|
||||
Total: total,
|
||||
Page: query.Page,
|
||||
PageSize: query.PageSize,
|
||||
})
|
||||
}
|
||||
|
||||
// GetMaterialsByGroups 根据多个group_id获取关联的材质
|
||||
// POST /api/v1/groups/materials
|
||||
// Body: {"group_ids": ["g1", "g2", "g3"]}
|
||||
func (h *BindingHandler) GetMaterialsByGroups(c *gin.Context) {
|
||||
var req models.GroupMaterialsRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "invalid request body: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
results, err := h.bindingRepo.GetMaterialsByGroupIDs(req.GroupIDs)
|
||||
if err != nil {
|
||||
response.InternalError(c, "failed to get materials")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, results)
|
||||
}
|
||||
178
internal/handlers/material.go
Normal file
178
internal/handlers/material.go
Normal file
@@ -0,0 +1,178 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
|
||||
"material_texture/internal/models"
|
||||
"material_texture/internal/repository"
|
||||
"material_texture/pkg/response"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type MaterialHandler struct {
|
||||
repo *repository.MaterialRepository
|
||||
}
|
||||
|
||||
func NewMaterialHandler(repo *repository.MaterialRepository) *MaterialHandler {
|
||||
return &MaterialHandler{repo: repo}
|
||||
}
|
||||
|
||||
// List 获取材质列表
|
||||
// GET /api/v1/materials?page=1&page_size=20&name=xxx
|
||||
func (h *MaterialHandler) List(c *gin.Context) {
|
||||
var query models.MaterialListQuery
|
||||
if err := c.ShouldBindQuery(&query); err != nil {
|
||||
response.BadRequest(c, "invalid query parameters")
|
||||
return
|
||||
}
|
||||
|
||||
// 设置默认值
|
||||
if query.Page < 1 {
|
||||
query.Page = 1
|
||||
}
|
||||
if query.PageSize < 1 || query.PageSize > 100 {
|
||||
query.PageSize = 20
|
||||
}
|
||||
|
||||
materials, total, err := h.repo.List(query)
|
||||
if err != nil {
|
||||
response.InternalError(c, "failed to fetch materials")
|
||||
return
|
||||
}
|
||||
|
||||
response.SuccessPaged(c, materials, total, query.Page, query.PageSize)
|
||||
}
|
||||
|
||||
// GetByID 获取单个材质详情
|
||||
// GET /api/v1/materials/:id
|
||||
func (h *MaterialHandler) GetByID(c *gin.Context) {
|
||||
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
response.BadRequest(c, "invalid material id")
|
||||
return
|
||||
}
|
||||
|
||||
material, err := h.repo.GetByID(id)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
response.NotFound(c, "material not found")
|
||||
return
|
||||
}
|
||||
response.InternalError(c, "failed to fetch material")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, material)
|
||||
}
|
||||
|
||||
// Create 创建材质
|
||||
// POST /api/v1/materials
|
||||
func (h *MaterialHandler) Create(c *gin.Context) {
|
||||
var req models.MaterialRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "invalid request body: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
material := &models.Material{
|
||||
Name: req.Name,
|
||||
DiffuseR: req.DiffuseR,
|
||||
DiffuseG: req.DiffuseG,
|
||||
DiffuseB: req.DiffuseB,
|
||||
Alpha: req.Alpha,
|
||||
Shininess: req.Shininess,
|
||||
SpecularR: req.SpecularR,
|
||||
SpecularG: req.SpecularG,
|
||||
SpecularB: req.SpecularB,
|
||||
AmbientR: req.AmbientR,
|
||||
AmbientG: req.AmbientG,
|
||||
AmbientB: req.AmbientB,
|
||||
Metallic: req.Metallic,
|
||||
Roughness: req.Roughness,
|
||||
Reflectance: req.Reflectance,
|
||||
}
|
||||
|
||||
if err := h.repo.Create(material); err != nil {
|
||||
response.InternalError(c, "failed to create material")
|
||||
return
|
||||
}
|
||||
|
||||
response.Created(c, material)
|
||||
}
|
||||
|
||||
// Update 更新材质 (优化: 单次查询)
|
||||
// PUT /api/v1/materials/:id
|
||||
func (h *MaterialHandler) Update(c *gin.Context) {
|
||||
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
response.BadRequest(c, "invalid material id")
|
||||
return
|
||||
}
|
||||
|
||||
var req models.MaterialRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
response.BadRequest(c, "invalid request body: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 直接更新,通过 RowsAffected 判断是否存在
|
||||
updates := map[string]interface{}{
|
||||
"name": req.Name,
|
||||
"diffuse_r": req.DiffuseR,
|
||||
"diffuse_g": req.DiffuseG,
|
||||
"diffuse_b": req.DiffuseB,
|
||||
"alpha": req.Alpha,
|
||||
"shininess": req.Shininess,
|
||||
"specular_r": req.SpecularR,
|
||||
"specular_g": req.SpecularG,
|
||||
"specular_b": req.SpecularB,
|
||||
"ambient_r": req.AmbientR,
|
||||
"ambient_g": req.AmbientG,
|
||||
"ambient_b": req.AmbientB,
|
||||
"metallic": req.Metallic,
|
||||
"roughness": req.Roughness,
|
||||
"reflectance": req.Reflectance,
|
||||
}
|
||||
|
||||
rowsAffected, err := h.repo.UpdateByID(id, updates)
|
||||
if err != nil {
|
||||
response.InternalError(c, "failed to update material")
|
||||
return
|
||||
}
|
||||
|
||||
if rowsAffected == 0 {
|
||||
response.NotFound(c, "material not found")
|
||||
return
|
||||
}
|
||||
|
||||
// 返回更新后的数据
|
||||
material, _ := h.repo.GetByID(id)
|
||||
response.Success(c, material)
|
||||
}
|
||||
|
||||
// Delete 删除材质 (优化: 单次查询,通过 RowsAffected 判断)
|
||||
// DELETE /api/v1/materials/:id
|
||||
func (h *MaterialHandler) Delete(c *gin.Context) {
|
||||
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
response.BadRequest(c, "invalid material id")
|
||||
return
|
||||
}
|
||||
|
||||
// 直接删除,通过 RowsAffected 判断是否存在
|
||||
rowsAffected, err := h.repo.DeleteByID(id)
|
||||
if err != nil {
|
||||
response.InternalError(c, "failed to delete material")
|
||||
return
|
||||
}
|
||||
|
||||
if rowsAffected == 0 {
|
||||
response.NotFound(c, "material not found")
|
||||
return
|
||||
}
|
||||
|
||||
response.Success(c, gin.H{"id": id})
|
||||
}
|
||||
Reference in New Issue
Block a user