This commit is contained in:
@@ -0,0 +1,355 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user