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:
104
internal/repository/binding.go
Normal file
104
internal/repository/binding.go
Normal file
@@ -0,0 +1,104 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"material_texture/internal/models"
|
||||
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
type BindingRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewBindingRepository(db *gorm.DB) *BindingRepository {
|
||||
return &BindingRepository{db: db}
|
||||
}
|
||||
|
||||
// BindMaterial 绑定材质到多个group_id(幂等操作,使用upsert)
|
||||
// 优化: 分批处理,避免单条 SQL 过大
|
||||
func (r *BindingRepository) BindMaterial(materialID int64, groupIDs []string) error {
|
||||
const batchSize = 1000 // 每批最多 1000 条
|
||||
|
||||
for i := 0; i < len(groupIDs); i += batchSize {
|
||||
end := i + batchSize
|
||||
if end > len(groupIDs) {
|
||||
end = len(groupIDs)
|
||||
}
|
||||
|
||||
batch := groupIDs[i:end]
|
||||
bindings := make([]models.MaterialBinding, len(batch))
|
||||
for j, groupID := range batch {
|
||||
bindings[j] = models.MaterialBinding{
|
||||
MaterialID: materialID,
|
||||
GroupID: groupID,
|
||||
}
|
||||
}
|
||||
|
||||
// 使用 ON CONFLICT DO NOTHING 实现幂等
|
||||
if err := r.db.Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "material_id"}, {Name: "group_id"}},
|
||||
DoNothing: true,
|
||||
}).Create(&bindings).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnbindMaterial 解绑材质与指定的group_id
|
||||
func (r *BindingRepository) UnbindMaterial(materialID int64, groupIDs []string) error {
|
||||
return r.db.Where("material_id = ? AND group_id IN ?", materialID, groupIDs).
|
||||
Delete(&models.MaterialBinding{}).Error
|
||||
}
|
||||
|
||||
// GetGroupsByMaterialID 根据材质ID获取所有关联的group_id (分页版本)
|
||||
func (r *BindingRepository) GetGroupsByMaterialID(materialID int64, page, pageSize int) ([]string, int64, error) {
|
||||
var groupIDs []string
|
||||
var total int64
|
||||
|
||||
db := r.db.Model(&models.MaterialBinding{}).Where("material_id = ?", materialID)
|
||||
|
||||
// 获取总数
|
||||
if err := db.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 分页查询
|
||||
offset := (page - 1) * pageSize
|
||||
err := db.Order("created_at DESC").
|
||||
Offset(offset).
|
||||
Limit(pageSize).
|
||||
Pluck("group_id", &groupIDs).Error
|
||||
|
||||
return groupIDs, total, err
|
||||
}
|
||||
|
||||
// GetMaterialsByGroupIDs 根据多个group_id获取关联的材质(含材质详情)
|
||||
func (r *BindingRepository) GetMaterialsByGroupIDs(groupIDs []string) ([]models.GroupMaterialResult, error) {
|
||||
var bindings []models.MaterialBinding
|
||||
|
||||
err := r.db.Preload("Material").
|
||||
Where("group_id IN ?", groupIDs).
|
||||
Find(&bindings).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
results := make([]models.GroupMaterialResult, len(bindings))
|
||||
for i, binding := range bindings {
|
||||
results[i] = models.GroupMaterialResult{
|
||||
GroupID: binding.GroupID,
|
||||
Material: binding.Material,
|
||||
}
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// DeleteByMaterialID 删除材质的所有绑定(材质删除时级联调用)
|
||||
func (r *BindingRepository) DeleteByMaterialID(materialID int64) error {
|
||||
return r.db.Where("material_id = ?", materialID).
|
||||
Delete(&models.MaterialBinding{}).Error
|
||||
}
|
||||
99
internal/repository/material.go
Normal file
99
internal/repository/material.go
Normal file
@@ -0,0 +1,99 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"material_texture/internal/models"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type MaterialRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewMaterialRepository(db *gorm.DB) *MaterialRepository {
|
||||
return &MaterialRepository{db: db}
|
||||
}
|
||||
|
||||
// List 获取材质列表(支持分页和名称搜索)
|
||||
func (r *MaterialRepository) List(query models.MaterialListQuery) ([]models.Material, int64, error) {
|
||||
var materials []models.Material
|
||||
var total int64
|
||||
|
||||
db := r.db.Model(&models.Material{})
|
||||
|
||||
// 名称模糊搜索
|
||||
if query.Name != "" {
|
||||
db = db.Where("name ILIKE ?", "%"+query.Name+"%")
|
||||
}
|
||||
|
||||
// 获取总数
|
||||
if err := db.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 分页查询
|
||||
offset := (query.Page - 1) * query.PageSize
|
||||
if err := db.Order("id DESC").Offset(offset).Limit(query.PageSize).Find(&materials).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return materials, total, nil
|
||||
}
|
||||
|
||||
// GetByID 根据ID获取单个材质
|
||||
func (r *MaterialRepository) GetByID(id int64) (*models.Material, error) {
|
||||
var material models.Material
|
||||
if err := r.db.First(&material, id).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &material, nil
|
||||
}
|
||||
|
||||
// Create 创建材质
|
||||
func (r *MaterialRepository) Create(material *models.Material) error {
|
||||
return r.db.Create(material).Error
|
||||
}
|
||||
|
||||
// Update 更新材质 (优化: 直接 UPDATE,返回影响行数)
|
||||
func (r *MaterialRepository) Update(material *models.Material) error {
|
||||
return r.db.Save(material).Error
|
||||
}
|
||||
|
||||
// UpdateByID 根据 ID 直接更新 (优化版本,减少查询)
|
||||
func (r *MaterialRepository) UpdateByID(id int64, updates map[string]interface{}) (int64, error) {
|
||||
result := r.db.Model(&models.Material{}).Where("id = ?", id).Updates(updates)
|
||||
return result.RowsAffected, result.Error
|
||||
}
|
||||
|
||||
// UpdateByIDWithVersion 带乐观锁的更新 (防止并发覆盖)
|
||||
// 只有当 version 匹配时才更新,并自动递增 version
|
||||
func (r *MaterialRepository) UpdateByIDWithVersion(id int64, version int64, updates map[string]interface{}) (int64, error) {
|
||||
// 在更新中自动递增 version
|
||||
updates["version"] = gorm.Expr("version + 1")
|
||||
|
||||
result := r.db.Model(&models.Material{}).
|
||||
Where("id = ? AND version = ?", id, version).
|
||||
Updates(updates)
|
||||
|
||||
return result.RowsAffected, result.Error
|
||||
}
|
||||
|
||||
// Delete 删除材质
|
||||
func (r *MaterialRepository) Delete(id int64) error {
|
||||
return r.db.Delete(&models.Material{}, id).Error
|
||||
}
|
||||
|
||||
// DeleteByID 删除材质并返回影响行数 (优化版本)
|
||||
func (r *MaterialRepository) DeleteByID(id int64) (int64, error) {
|
||||
result := r.db.Delete(&models.Material{}, id)
|
||||
return result.RowsAffected, result.Error
|
||||
}
|
||||
|
||||
// Exists 检查材质是否存在
|
||||
func (r *MaterialRepository) Exists(id int64) (bool, error) {
|
||||
var count int64
|
||||
if err := r.db.Model(&models.Material{}).Where("id = ?", id).Count(&count).Error; err != nil {
|
||||
return false, err
|
||||
}
|
||||
return count > 0, nil
|
||||
}
|
||||
Reference in New Issue
Block a user