浏览代码

mp:增加上传临时素材的接口

YunaiV 2 年之前
父节点
当前提交
ec872c702c
共有 14 个文件被更改,包括 426 次插入66 次删除
  1. 0 3
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountUpdateReqVO.java
  2. 0 0
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/MpMaterialController.http
  3. 39 0
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/MpMaterialController.java
  4. 17 0
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/vo/MpMaterialUploadRespVO.java
  5. 29 0
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/vo/MpMaterialUploadTemporaryReqVO.java
  6. 25 0
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/material/MpMaterialConvert.java
  7. 95 0
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/material/MpMaterialDO.java
  8. 15 0
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/material/MpMaterialMapper.java
  9. 21 0
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/core/util/MpUtils.java
  10. 30 0
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/material/MpMaterialService.java
  11. 133 0
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/material/MpMaterialServiceImpl.java
  12. 12 36
      yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpMessageServiceImpl.java
  13. 1 1
      yudao-ui-admin/src/views/mp/components/wx-msg/main.vue
  14. 9 26
      yudao-ui-admin/src/views/mp/components/wx-reply/main.vue

+ 0 - 3
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/account/vo/MpAccountUpdateReqVO.java

@@ -4,9 +4,6 @@ import lombok.*;
 import io.swagger.annotations.*;
 import javax.validation.constraints.*;
 
-/**
- * @author fengdan
- */
 @ApiModel("管理后台 - 公众号账号更新 Request VO")
 @Data
 @EqualsAndHashCode(callSuper = true)

+ 0 - 0
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/MpMaterialController.http


+ 39 - 0
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/MpMaterialController.java

@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.mp.controller.admin.material;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.mp.controller.admin.material.vo.MpMaterialUploadRespVO;
+import cn.iocoder.yudao.module.mp.controller.admin.material.vo.MpMaterialUploadTemporaryReqVO;
+import cn.iocoder.yudao.module.mp.convert.material.MpMaterialConvert;
+import cn.iocoder.yudao.module.mp.dal.dataobject.material.MpMaterialDO;
+import cn.iocoder.yudao.module.mp.service.material.MpMaterialService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import javax.validation.Valid;
+import java.io.IOException;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Api(tags = "管理后台 - 公众号素材")
+@RestController
+@RequestMapping("/mp/material")
+@Validated
+public class MpMaterialController {
+
+    @Resource
+    private MpMaterialService mpMaterialService;
+
+    @ApiOperation("上传临时素材")
+    @PostMapping("/upload-temporary")
+    public CommonResult<MpMaterialUploadRespVO> uploadTemporaryMaterial(
+            @Valid MpMaterialUploadTemporaryReqVO reqVO) throws IOException {
+        MpMaterialDO material = mpMaterialService.uploadTemporaryMaterial(reqVO);
+        return success(MpMaterialConvert.INSTANCE.convert(material));
+    }
+
+}

+ 17 - 0
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/vo/MpMaterialUploadRespVO.java

@@ -0,0 +1,17 @@
+package cn.iocoder.yudao.module.mp.controller.admin.material.vo;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+@ApiModel("管理后台 - 公众号素材上传结果 Response VO")
+@Data
+public class MpMaterialUploadRespVO {
+
+    @ApiModelProperty(value = "素材的 media_id", required = true, example = "123")
+    private String mediaId;
+
+    @ApiModelProperty(value = "素材的 URL", required = true, example = "https://www.iocoder.cn/1.png")
+    private String url;
+
+}

+ 29 - 0
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/vo/MpMaterialUploadTemporaryReqVO.java

@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.mp.controller.admin.material.vo;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+
+@ApiModel("管理后台 - 公众号素材上传临时 Request VO")
+@Data
+public class MpMaterialUploadTemporaryReqVO {
+
+    @ApiModelProperty(value = "公众号账号的编号", required = true, example = "2048")
+    @NotNull(message = "公众号账号的编号不能为空")
+    private Long accountId;
+
+    @ApiModelProperty(value = "文件类型", required = true, example = "image", notes = "参见 WxConsts.MediaFileType 枚举")
+    @NotEmpty(message = "文件类型不能为空")
+    private String type;
+
+    @ApiModelProperty(value = "文件附件", required = true)
+    @NotNull(message = "文件不能为空")
+    @JsonIgnore // 避免被操作日志,进行序列化,导致报错
+    private MultipartFile file;
+
+}

+ 25 - 0
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/material/MpMaterialConvert.java

@@ -0,0 +1,25 @@
+package cn.iocoder.yudao.module.mp.convert.material;
+
+import cn.iocoder.yudao.module.mp.controller.admin.material.vo.MpMaterialUploadRespVO;
+import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;
+import cn.iocoder.yudao.module.mp.dal.dataobject.material.MpMaterialDO;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.Mappings;
+import org.mapstruct.factory.Mappers;
+
+@Mapper
+public interface MpMaterialConvert {
+
+    MpMaterialConvert INSTANCE = Mappers.getMapper(MpMaterialConvert.class);
+
+    @Mappings({
+            @Mapping(target = "id", ignore = true),
+            @Mapping(source = "account.id", target = "accountId"),
+            @Mapping(source = "account.appId", target = "appId"),
+    })
+    MpMaterialDO convert(String mediaId, String type, String url, MpAccountDO account);
+
+    MpMaterialUploadRespVO convert(MpMaterialDO bean);
+
+}

+ 95 - 0
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/material/MpMaterialDO.java

@@ -0,0 +1,95 @@
+package cn.iocoder.yudao.module.mp.dal.dataobject.material;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+import me.chanjar.weixin.common.api.WxConsts;
+
+/**
+ * 公众号素材 DO
+ *
+ * 1. <a href="https://developers.weixin.qq.com/doc/offiaccount/Asset_Management/New_temporary_materials.html">临时素材</a>
+ * 2. <a href="https://developers.weixin.qq.com/doc/offiaccount/Asset_Management/Adding_Permanent_Assets.html">永久素材</a>
+ *
+ * @author 芋道源码
+ */
+@TableName("mp_material")
+@KeySequence("mp_material_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class MpMaterialDO extends BaseDO {
+
+    /**
+     * 主键
+     */
+    @TableId
+    private Long id;
+    /**
+     * 微信公众号 ID
+     *
+     * 关联 {@link MpAccountDO#getId()}
+     */
+    private Long accountId;
+    /**
+     * 微信公众号 appid
+     *
+     * 冗余 {@link MpAccountDO#getAppId()}
+     */
+    private String appId;
+
+    /**
+     * 公众号素材 id
+     */
+    private String mediaId;
+    /**
+     * 文件类型
+     *
+     * 枚举 {@link WxConsts.MediaFileType}
+     */
+    private String type;
+    /**
+     * 是否永久
+     *
+     * true - 永久素材
+     * false - 临时素材
+     */
+    private Boolean permanent;
+    /**
+     * 文件服务器的 URL
+     */
+    private String url;
+
+    /**
+     * 名字
+     *
+     * 只有【永久素材】使用
+     */
+    private String name;
+    /**
+     * 公众号文件 URL
+     *
+     * 只有【永久素材】使用
+     */
+    private String mpUrl;
+
+    /**
+     * 视频素材的标题
+     *
+     * 只有【永久素材】使用
+     */
+    private String title;
+    /**
+     * 视频素材的描述
+     *
+     * 只有【永久素材】使用
+     */
+    private String introduction;
+
+}

+ 15 - 0
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/material/MpMaterialMapper.java

@@ -0,0 +1,15 @@
+package cn.iocoder.yudao.module.mp.dal.mysql.material;
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.module.mp.dal.dataobject.material.MpMaterialDO;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface MpMaterialMapper extends BaseMapperX<MpMaterialDO> {
+
+    default MpMaterialDO selectByAccountIdAndMediaId(Long accountId, String mediaId) {
+        return selectOne(MpMaterialDO::getAccountId, accountId,
+                MpMaterialDO::getMediaId, mediaId);
+    }
+
+}

+ 21 - 0
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/framework/mp/core/util/MpUtils.java

@@ -80,4 +80,25 @@ public class MpUtils {
         ValidationUtils.validate(validator, message, group);
     }
 
+    /**
+     * 根据消息类型,获得对应的媒体文件类型
+     *
+     * 注意,不会返回 WxConsts.MediaFileType.THUMB,因为该类型会有明确标注
+     *
+     * @param messageType 消息类型 {@link  WxConsts.XmlMsgType}
+     * @return 媒体文件类型 {@link WxConsts.MediaFileType}
+     */
+    public static String getMediaFileType(String messageType) {
+        switch (messageType) {
+            case WxConsts.XmlMsgType.IMAGE:
+                return WxConsts.MediaFileType.IMAGE;
+            case WxConsts.XmlMsgType.VOICE:
+                return WxConsts.MediaFileType.VOICE;
+            case WxConsts.XmlMsgType.VIDEO:
+                return WxConsts.MediaFileType.VIDEO;
+            default:
+                return WxConsts.MediaFileType.FILE;
+        }
+    }
+
 }

+ 30 - 0
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/material/MpMaterialService.java

@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.mp.service.material;
+
+import cn.iocoder.yudao.module.mp.controller.admin.material.vo.MpMaterialUploadTemporaryReqVO;
+import cn.iocoder.yudao.module.mp.dal.dataobject.material.MpMaterialDO;
+import me.chanjar.weixin.common.api.WxConsts;
+
+import javax.validation.Valid;
+import java.io.IOException;
+
+/**
+ * 公众号素材 Service 接口
+ *
+ * @author 芋道源码
+ */
+public interface MpMaterialService {
+
+    /**
+     * 获得素材的 URL
+     *
+     * 该 URL 来自我们自己的文件服务器存储的 URL,不是公众号存储的 URL
+     *
+     * @param accountId 公众号账号编号
+     * @param mediaId 公众号素材 id
+     * @param type 文件类型 {@link WxConsts.MediaFileType}
+     * @return 素材的 URL
+     */
+    String downloadMaterialUrl(Long accountId, String mediaId, String type);
+
+    MpMaterialDO uploadTemporaryMaterial(@Valid MpMaterialUploadTemporaryReqVO reqVO) throws IOException;
+}

+ 133 - 0
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/material/MpMaterialServiceImpl.java

@@ -0,0 +1,133 @@
+package cn.iocoder.yudao.module.mp.service.material;
+
+import cn.hutool.core.io.FileTypeUtil;
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.util.ObjUtil;
+import cn.iocoder.yudao.module.infra.api.file.FileApi;
+import cn.iocoder.yudao.module.mp.controller.admin.material.vo.MpMaterialUploadTemporaryReqVO;
+import cn.iocoder.yudao.module.mp.convert.material.MpMaterialConvert;
+import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;
+import cn.iocoder.yudao.module.mp.dal.dataobject.material.MpMaterialDO;
+import cn.iocoder.yudao.module.mp.dal.mysql.material.MpMaterialMapper;
+import cn.iocoder.yudao.module.mp.framework.mp.core.MpServiceFactory;
+import cn.iocoder.yudao.module.mp.service.account.MpAccountService;
+import lombok.extern.slf4j.Slf4j;
+import me.chanjar.weixin.common.bean.result.WxMediaUploadResult;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.mp.api.WxMpService;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * 公众号素材 Service 接口
+ *
+ * @author 芋道源码
+ */
+@Service
+@Validated
+@Slf4j
+public class MpMaterialServiceImpl implements MpMaterialService {
+
+    @Resource
+    private MpMaterialMapper mpMaterialMapper;
+
+    @Resource
+    private FileApi fileApi;
+
+    @Resource
+    @Lazy // 延迟加载,解决循环依赖的问题
+    private MpAccountService mpAccountService;
+
+    @Resource
+    @Lazy // 延迟加载,解决循环依赖的问题
+    private MpServiceFactory mpServiceFactory;
+
+    @Override
+    public String downloadMaterialUrl(Long accountId, String mediaId, String type) {
+        // 第一步,直接从数据库查询。如果已经下载,直接返回
+        MpMaterialDO material = mpMaterialMapper.selectByAccountIdAndMediaId(accountId, mediaId);
+        if (material != null) {
+            return material.getUrl();
+        }
+
+        // 第二步,尝试从临时素材中下载
+        String url = downloadMedia(accountId, mediaId);
+        if (url == null) {
+            return null;
+        }
+        MpAccountDO account = mpAccountService.getRequiredAccount(accountId);
+        material = MpMaterialConvert.INSTANCE.convert(mediaId, type, url, account)
+                .setPermanent(false);
+        mpMaterialMapper.insert(material);
+
+        // 不考虑下载永久素材,因为上传的时候已经保存
+        return url;
+    }
+
+    @Override
+    public MpMaterialDO uploadTemporaryMaterial(MpMaterialUploadTemporaryReqVO reqVO) throws IOException {
+        WxMpService mpService = mpServiceFactory.getRequiredMpService(reqVO.getAccountId());
+        // 第一步,上传到公众号
+        File file = null;
+        WxMediaUploadResult result;
+        String mediaId;
+        String url;
+        try {
+            // 写入到临时文件
+            file = FileUtil.newFile(FileUtil.getTmpDirPath() + reqVO.getFile().getOriginalFilename());
+            reqVO.getFile().transferTo(file);
+            // 上传到公众号
+            result = mpService.getMaterialService().mediaUpload(reqVO.getType(), file);
+            // 上传到文件服务
+            mediaId = ObjUtil.defaultIfNull(result.getMediaId(), result.getThumbMediaId());
+            url = uploadFile(mediaId, file);
+        } catch (WxErrorException e) {
+            // TODO yunai:待完善
+            throw new RuntimeException(e);
+        } finally {
+            FileUtil.del(file);
+        }
+
+        // 第二步,存储到数据库
+        MpAccountDO account = mpAccountService.getRequiredAccount(reqVO.getAccountId());
+        MpMaterialDO material = MpMaterialConvert.INSTANCE.convert(mediaId, reqVO.getType(), url, account)
+                .setPermanent(false);
+        mpMaterialMapper.insert(material);
+        return material;
+    }
+
+    /**
+     * 下载微信媒体文件的内容,并上传到文件服务
+     *
+     * 为什么要下载?媒体文件在微信后台保存时间为 3 天,即 3 天后 media_id 失效。
+     *
+     * @param accountId 公众号账号的编号
+     * @param mediaId 媒体文件编号
+     * @return 上传后的 URL
+     */
+    public String downloadMedia(Long accountId, String mediaId) {
+        WxMpService mpService = mpServiceFactory.getMpService(accountId);
+        for (int i = 0; i < 3; i++) {
+            try {
+                // 第一步,从公众号下载媒体文件
+                File file = mpService.getMaterialService().mediaDownload(mediaId);
+                // 第二步,上传到文件服务
+                return uploadFile(mediaId, file);
+            } catch (WxErrorException e) {
+                log.error("[mediaDownload][media({}) 第 ({}) 次下载失败]", mediaId, i);
+            }
+        }
+        return null;
+    }
+
+    private String uploadFile(String mediaId, File file) {
+        String path = mediaId + "." + FileTypeUtil.getType(file);
+        return fileApi.createFile(path, FileUtil.readBytes(file));
+    }
+
+}

+ 12 - 36
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpMessageServiceImpl.java

@@ -1,11 +1,8 @@
 package cn.iocoder.yudao.module.mp.service.message;
 
-import cn.hutool.core.io.FileTypeUtil;
-import cn.hutool.core.io.FileUtil;
 import cn.hutool.core.lang.Assert;
 import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.module.infra.api.file.FileApi;
 import cn.iocoder.yudao.module.mp.controller.admin.message.vo.MpMessagePageReqVO;
 import cn.iocoder.yudao.module.mp.controller.admin.message.vo.MpMessageSendReqVO;
 import cn.iocoder.yudao.module.mp.convert.message.MpMessageConvert;
@@ -17,9 +14,11 @@ import cn.iocoder.yudao.module.mp.enums.message.MpMessageSendFromEnum;
 import cn.iocoder.yudao.module.mp.framework.mp.core.MpServiceFactory;
 import cn.iocoder.yudao.module.mp.framework.mp.core.util.MpUtils;
 import cn.iocoder.yudao.module.mp.service.account.MpAccountService;
+import cn.iocoder.yudao.module.mp.service.material.MpMaterialService;
 import cn.iocoder.yudao.module.mp.service.message.bo.MpMessageSendOutReqBO;
 import cn.iocoder.yudao.module.mp.service.user.MpUserService;
 import lombok.extern.slf4j.Slf4j;
+import me.chanjar.weixin.common.api.WxConsts;
 import me.chanjar.weixin.common.error.WxErrorException;
 import me.chanjar.weixin.mp.api.WxMpService;
 import me.chanjar.weixin.mp.bean.kefu.WxMpKefuMessage;
@@ -31,7 +30,6 @@ import org.springframework.validation.annotation.Validated;
 
 import javax.annotation.Resource;
 import javax.validation.Validator;
-import java.io.File;
 
 /**
  * 粉丝消息表 Service 实现类
@@ -48,6 +46,8 @@ public class MpMessageServiceImpl implements MpMessageService {
     private MpAccountService mpAccountService;
     @Resource
     private MpUserService mpUserService;
+    @Resource
+    private MpMaterialService mpMaterialService;
 
     @Resource
     private MpMessageMapper mpMessageMapper;
@@ -56,9 +56,6 @@ public class MpMessageServiceImpl implements MpMessageService {
     @Lazy // 延迟加载,解决循环依赖的问题
     private MpServiceFactory mpServiceFactory;
 
-    @Resource
-    private FileApi fileApi;
-
     @Resource
     private Validator validator;
 
@@ -69,7 +66,6 @@ public class MpMessageServiceImpl implements MpMessageService {
 
     @Override
     public void receiveMessage(String appId, WxMpXmlMessage wxMessage) {
-        WxMpService mpService = mpServiceFactory.getRequiredMpService(appId);
         // 获得关联信息
         MpAccountDO account = mpAccountService.getAccountFromCache(appId);
         Assert.notNull(account, "公众号账号({}) 不存在", appId);
@@ -79,7 +75,7 @@ public class MpMessageServiceImpl implements MpMessageService {
         // 记录消息
         MpMessageDO message = MpMessageConvert.INSTANCE.convert(wxMessage, account, user);
         message.setSendFrom(MpMessageSendFromEnum.USER_TO_MP.getFrom());
-        downloadMessageMedia(mpService, message);
+        downloadMessageMedia(message);
         mpMessageMapper.insert(message);
     }
 
@@ -97,6 +93,7 @@ public class MpMessageServiceImpl implements MpMessageService {
         // 记录消息
         MpMessageDO message = MpMessageConvert.INSTANCE.convert(sendReqBO, account, user);
         message.setSendFrom(MpMessageSendFromEnum.MP_TO_USER.getFrom());
+        // TODO 芋艿:downloadMessageMedia
         mpMessageMapper.insert(message);
 
         // 转换返回 WxMpXmlOutMessage 对象
@@ -124,7 +121,7 @@ public class MpMessageServiceImpl implements MpMessageService {
         // 记录消息
         MpMessageDO message = MpMessageConvert.INSTANCE.convert(wxMessage, account, user);
         message.setSendFrom(MpMessageSendFromEnum.MP_TO_USER.getFrom());
-        downloadMessageMedia(mpService, message);
+        downloadMessageMedia(message);
         mpMessageMapper.insert(message);
         return message;
     }
@@ -132,38 +129,17 @@ public class MpMessageServiceImpl implements MpMessageService {
     /**
      * 下载消息使用到的媒体文件,并上传到文件服务
      *
-     * @param mpService 公众号 Service
      * @param message 消息
      */
-    private void downloadMessageMedia(WxMpService mpService, MpMessageDO message) {
+    private void downloadMessageMedia(MpMessageDO message) {
         if (StrUtil.isNotEmpty(message.getMediaId())) {
-            message.setMediaUrl(downloadMedia(mpService, message.getMediaId()));
+            message.setMediaUrl(mpMaterialService.downloadMaterialUrl(message.getAccountId(),
+                    message.getMediaId(), MpUtils.getMediaFileType(message.getType())));
         }
         if (StrUtil.isNotEmpty(message.getThumbMediaId())) {
-            message.setThumbMediaUrl(downloadMedia(mpService, message.getThumbMediaId()));
-        }
-    }
-
-    /**
-     * 下载微信媒体文件的内容,并上传到文件服务
-     *
-     * 为什么要下载?媒体文件在微信后台保存时间为 3 天,即 3 天后 media_id 失效。
-     *
-     * @param mpService 微信公众号 Service
-     * @param mediaId 媒体文件编号
-     * @return 上传后的 URL
-     */
-    private String downloadMedia(WxMpService mpService, String mediaId) {
-        try {
-            // 第一步,从公众号下载媒体文件
-            File file = mpService.getMaterialService().mediaDownload(mediaId);
-            // 第二步,上传到文件服务
-            String path = mediaId + "." + FileTypeUtil.getType(file);
-            return fileApi.createFile(path, FileUtil.readBytes(file));
-        } catch (WxErrorException e) {
-            log.error("[mediaDownload][media({}) 下载失败]", mediaId);
+            message.setThumbMediaUrl(mpMaterialService.downloadMaterialUrl(message.getAccountId(),
+                    message.getThumbMediaId(), WxConsts.MediaFileType.THUMB));
         }
-        return null;
     }
 
 }

+ 1 - 1
yudao-ui-admin/src/views/mp/components/wx-msg/main.vue

@@ -158,7 +158,7 @@ import {getMessagePage, sendMessage} from '@/api/mp/message'
         }, {
           ...this.objData,
           type: this.objData.repType,
-          content: this.objData.repContent,
+          // content: this.objData.repContent,
           // TODO 芋艿:临时适配,保证可用
         })).then(response => {
           this.sendLoading = false

+ 9 - 26
yudao-ui-admin/src/views/mp/components/wx-reply/main.vue

@@ -4,15 +4,12 @@
 -->
 <template>
   <el-tabs type="border-card" v-model="objData.repType" @tab-click="handleClick">
+    <!-- 类型 1:文本 -->
     <el-tab-pane name="text">
       <span slot="label"><i class="el-icon-document"></i> 文本</span>
-      <el-input
-        type="textarea"
-        :rows="5"
-        placeholder="请输入内容"
-        v-model="objData.repContent">
-      </el-input>
+      <el-input type="textarea" :rows="5" placeholder="请输入内容" v-model="objData.content" />
     </el-tab-pane>
+    <!-- 类型 2:图片 -->
     <el-tab-pane name="image">
       <span slot="label"><i class="el-icon-picture"></i> 图片</span>
       <el-row>
@@ -29,19 +26,10 @@
               <el-button type="success" @click="openMaterial">素材库选择<i class="el-icon-circle-check el-icon--right"></i></el-button>
             </el-col>
             <el-col :span="12" class="col-add">
-              <el-upload
-                :action="actionUrl"
-                :headers="headers"
-                multiple
-                :limit="1"
-                :on-success="handleUploadSuccess"
-                :file-list="fileList"
-                :before-upload="beforeImageUpload"
-                :data="uploadData">
+              <el-upload :action="actionUrl" :headers="headers" multiple :limit="1" :file-list="fileList" :data="uploadData"
+                         :before-upload="beforeImageUpload" :on-success="handleUploadSuccess">
                 <el-button type="primary">上传图片</el-button>
-                <div slot="tip" class="el-upload__tip">
-                  支持bmp/png/jpeg/jpg/gif格式,大小不超过2M
-                </div>
+                <div slot="tip" class="el-upload__tip">支持 bmp/png/jpeg/jpg/gif 格式,大小不超过 2M</div>
               </el-upload>
             </el-col>
           </el-row>
@@ -69,12 +57,7 @@
               <el-button type="success" @click="openMaterial">素材库选择<i class="el-icon-circle-check el-icon--right"></i></el-button>
             </el-col>
             <el-col :span="12" class="col-add">
-              <el-upload
-                :action="actionUrl"
-                :headers="headers"
-                multiple
-                :limit="1"
-                :on-success="handleUploadSuccess"
+              <el-upload :action="actionUrl" :headers="headers" multiple :limit="1" :on-success="handleUploadSuccess"
                 :file-list="fileList"
                 :before-upload="beforeVoiceUpload"
                 :data="uploadData">
@@ -188,9 +171,9 @@
     },
     props: {
       objData:{
-        type:Object
+        type: Object
       },
-      //图文类型:1、已发布图文;2、草稿箱图文
+      // 图文类型:1、已发布图文;2、草稿箱图文
       newsType:{
         type: String,
         default: "1"