pet-ai/server/plugin/wechat-integration/service/mp_material_service.go

356 lines
9.8 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package service
import (
"errors"
"fmt"
"io"
"mime/multipart"
"github.com/flipped-aurora/gin-vue-admin/server/global"
"github.com/flipped-aurora/gin-vue-admin/server/plugin/wechat-integration/model"
"github.com/silenceper/wechat/v2/officialaccount"
"github.com/silenceper/wechat/v2/officialaccount/material"
)
type MpMaterialService struct{}
// getOfficialAccount 获取微信公众号实例
func (m *MpMaterialService) getOfficialAccount() (*officialaccount.OfficialAccount, error) {
// 使用账号服务获取默认公众号实例
return nil, nil
}
// GetMaterialList 获取素材列表
func (m *MpMaterialService) GetMaterialList(page, pageSize int, materialType string, permanent *bool) ([]model.MpMaterial, int64, error) {
var materials []model.MpMaterial
var total int64
db := global.GVA_DB.Model(&model.MpMaterial{})
// 根据素材类型筛选
if materialType != "" {
db = db.Where("type = ?", materialType)
}
// 根据是否永久素材筛选
if permanent != nil {
db = db.Where("permanent = ?", *permanent)
}
// 获取总数
err := db.Count(&total).Error
if err != nil {
return nil, 0, err
}
// 分页查询
offset := (page - 1) * pageSize
err = db.Offset(offset).Limit(pageSize).Order("created_at desc").Find(&materials).Error
if err != nil {
return nil, 0, err
}
return materials, total, nil
}
// UploadTempMaterial 上传临时素材
func (m *MpMaterialService) UploadTempMaterial(file multipart.File, filename, materialType string) (*model.MpMaterial, error) {
if file == nil {
return nil, errors.New("文件不能为空")
}
if filename == "" {
return nil, errors.New("文件名不能为空")
}
if materialType == "" {
return nil, errors.New("素材类型不能为空")
}
// 获取微信公众号实例
oa, err := m.getOfficialAccount()
if err != nil {
return nil, err
}
// 转换素材类型
var mediaType material.MediaType
switch materialType {
case "image":
mediaType = material.MediaTypeImage
case "voice":
mediaType = material.MediaTypeVoice
case "video":
mediaType = material.MediaTypeVideo
case "thumb":
mediaType = material.MediaTypeThumb
default:
return nil, fmt.Errorf("不支持的素材类型: %s", materialType)
}
// 调用微信API上传临时素材
materialService := oa.GetMaterial()
media, err := materialService.MediaUploadFromReader(mediaType, filename, file)
if err != nil {
global.GVA_LOG.Error("上传临时素材失败: " + err.Error())
return nil, fmt.Errorf("上传临时素材失败: %v", err)
}
// 保存到数据库
mpMaterial := &model.MpMaterial{
MediaID: media.MediaID,
Type: materialType,
Name: &filename,
Permanent: false,
}
err = global.GVA_DB.Create(mpMaterial).Error
if err != nil {
global.GVA_LOG.Error("保存临时素材记录失败: " + err.Error())
return nil, fmt.Errorf("保存素材记录失败: %v", err)
}
global.GVA_LOG.Info(fmt.Sprintf("成功上传临时素材MediaID: %s", media.MediaID))
return mpMaterial, nil
}
// UploadPermanentMaterial 上传永久素材
func (m *MpMaterialService) UploadPermanentMaterial(file multipart.File, filename, materialType string) (*model.MpMaterial, error) {
if file == nil {
return nil, errors.New("文件不能为空")
}
if filename == "" {
return nil, errors.New("文件名不能为空")
}
if materialType == "" {
return nil, errors.New("素材类型不能为空")
}
// 获取微信公众号实例
oa, err := m.getOfficialAccount()
if err != nil {
return nil, err
}
materialService := oa.GetMaterial()
var mediaID, url string
// 根据素材类型调用不同的上传方法
switch materialType {
case "image":
mediaID, url, err = materialService.AddMaterialFromReader(material.MediaTypeImage, filename, file)
case "voice":
mediaID, url, err = materialService.AddMaterialFromReader(material.MediaTypeVoice, filename, file)
case "thumb":
mediaID, url, err = materialService.AddMaterialFromReader(material.MediaTypeThumb, filename, file)
case "video":
// 视频素材需要额外的标题和描述参数,这里使用文件名作为标题
mediaID, url, err = materialService.AddVideoFromReader(filename, filename, "视频素材", file)
default:
return nil, fmt.Errorf("不支持的永久素材类型: %s", materialType)
}
if err != nil {
global.GVA_LOG.Error("上传永久素材失败: " + err.Error())
return nil, fmt.Errorf("上传永久素材失败: %v", err)
}
// 保存到数据库
mpMaterial := &model.MpMaterial{
MediaID: mediaID,
Type: materialType,
Name: &filename,
Permanent: true,
URL: &url,
}
err = global.GVA_DB.Create(mpMaterial).Error
if err != nil {
global.GVA_LOG.Error("保存永久素材记录失败: " + err.Error())
return nil, fmt.Errorf("保存素材记录失败: %v", err)
}
global.GVA_LOG.Info(fmt.Sprintf("成功上传永久素材MediaID: %s", mediaID))
return mpMaterial, nil
}
// DeleteMaterial 删除素材
func (m *MpMaterialService) DeleteMaterial(id uint) error {
// 先获取素材信息
var mat model.MpMaterial
err := global.GVA_DB.Where("id = ?", id).First(&mat).Error
if err != nil {
return err
}
// 如果是永久素材,需要从微信服务器删除
if mat.IsPermanent() {
mpUserService := &MpUserService{}
oa, err := mpUserService.GetOfficialAccount()
if err != nil {
return err
}
err = oa.GetMaterial().DeleteMaterial(mat.MediaID)
if err != nil {
global.GVA_LOG.Error("删除微信素材失败: " + err.Error())
// 即使微信删除失败,也继续删除本地记录
}
}
// 删除本地记录
err = global.GVA_DB.Delete(&model.MpMaterial{}, id).Error
if err != nil {
global.GVA_LOG.Error("删除素材记录失败: " + err.Error())
return err
}
return nil
}
// GetMaterialByID 根据ID获取素材
func (m *MpMaterialService) GetMaterialByID(id uint) (*model.MpMaterial, error) {
var mat model.MpMaterial
err := global.GVA_DB.Where("id = ?", id).First(&mat).Error
if err != nil {
return nil, err
}
return &mat, nil
}
// GetMaterialByMediaID 根据MediaID获取素材
func (m *MpMaterialService) GetMaterialByMediaID(mediaID string) (*model.MpMaterial, error) {
var mat model.MpMaterial
err := global.GVA_DB.Where("media_id = ?", mediaID).First(&mat).Error
if err != nil {
return nil, err
}
return &mat, nil
}
// DownloadMaterial 下载素材
func (m *MpMaterialService) DownloadMaterial(mediaID string) (io.ReadCloser, string, error) {
if mediaID == "" {
return nil, "", errors.New("MediaID不能为空")
}
// 获取微信公众号实例
oa, err := m.getOfficialAccount()
if err != nil {
return nil, "", err
}
// 获取素材下载URL
materialService := oa.GetMaterial()
mediaURL, err := materialService.GetMediaURL(mediaID)
if err != nil {
global.GVA_LOG.Error("获取素材下载URL失败: " + err.Error())
return nil, "", fmt.Errorf("获取素材下载URL失败: %v", err)
}
global.GVA_LOG.Info(fmt.Sprintf("成功获取素材下载URLMediaID: %s", mediaID))
// 注意这里返回的是下载URL实际的文件下载需要客户端自行处理
// 由于微信API返回的是URL而不是文件流这里暂时返回错误提示
return nil, mediaURL, fmt.Errorf("请使用返回的URL进行下载: %s", mediaURL)
}
// SyncMaterialsFromWechat 从微信服务器同步素材列表
func (m *MpMaterialService) SyncMaterialsFromWechat() error {
// 获取微信公众号实例
oa, err := m.getOfficialAccount()
if err != nil {
return err
}
materialService := oa.GetMaterial()
// 获取素材总数
materialCount, err := materialService.GetMaterialCount()
if err != nil {
global.GVA_LOG.Error("获取素材总数失败: " + err.Error())
return fmt.Errorf("获取素材总数失败: %v", err)
}
global.GVA_LOG.Info(fmt.Sprintf("开始同步素材,总数统计 - 图片: %d, 语音: %d, 视频: %d, 图文: %d",
materialCount.ImageCount, materialCount.VoiceCount, materialCount.VideoCount, materialCount.NewsCount))
// 同步各类型的永久素材
materialTypes := []material.PermanentMaterialType{
material.PermanentMaterialTypeImage,
material.PermanentMaterialTypeVoice,
material.PermanentMaterialTypeVideo,
material.PermanentMaterialTypeNews,
}
totalSynced := 0
for _, materialType := range materialTypes {
synced, err := m.syncMaterialsByType(materialService, materialType)
if err != nil {
global.GVA_LOG.Error(fmt.Sprintf("同步%s类型素材失败: %s", string(materialType), err.Error()))
continue
}
totalSynced += synced
}
global.GVA_LOG.Info(fmt.Sprintf("素材同步完成,共同步 %d 个素材", totalSynced))
return nil
}
// syncMaterialsByType 按类型同步素材
func (m *MpMaterialService) syncMaterialsByType(materialService *material.Material, materialType material.PermanentMaterialType) (int, error) {
const batchSize = 20 // 每次获取20个素材
offset := int64(0)
synced := 0
for {
// 批量获取素材
articleList, err := materialService.BatchGetMaterial(materialType, offset, batchSize)
if err != nil {
return synced, err
}
if len(articleList.Item) == 0 {
break
}
// 处理每个素材
for _, item := range articleList.Item {
// 检查是否已存在
var existingMaterial model.MpMaterial
err := global.GVA_DB.Where("media_id = ?", item.MediaID).First(&existingMaterial).Error
if err == nil {
// 已存在,跳过
continue
}
// 创建新的素材记录
mpMaterial := &model.MpMaterial{
MediaID: item.MediaID,
Type: string(materialType),
Permanent: true,
URL: &item.URL,
}
if item.Name != "" {
mpMaterial.Name = &item.Name
}
err = global.GVA_DB.Create(mpMaterial).Error
if err != nil {
global.GVA_LOG.Error(fmt.Sprintf("保存素材记录失败: %s", err.Error()))
continue
}
synced++
}
// 如果返回的素材数量少于批次大小,说明已经获取完毕
if int64(len(articleList.Item)) < batchSize {
break
}
offset += batchSize
}
return synced, nil
}