356 lines
9.8 KiB
Go
356 lines
9.8 KiB
Go
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("成功获取素材下载URL,MediaID: %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
|
||
}
|