Commit ec872c702c6a5da948371a16b9aafa11a13a912d

Authored by YunaiV
1 parent 541ee813

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

Showing 14 changed files with 426 additions and 66 deletions
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.*;
4 4 import io.swagger.annotations.*;
5 5 import javax.validation.constraints.*;
6 6  
7   -/**
8   - * @author fengdan
9   - */
10 7 @ApiModel("管理后台 - 公众号账号更新 Request VO")
11 8 @Data
12 9 @EqualsAndHashCode(callSuper = true)
... ...
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/MpMaterialController.http 0 → 100644
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/MpMaterialController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.mp.controller.admin.material;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.module.mp.controller.admin.material.vo.MpMaterialUploadRespVO;
  5 +import cn.iocoder.yudao.module.mp.controller.admin.material.vo.MpMaterialUploadTemporaryReqVO;
  6 +import cn.iocoder.yudao.module.mp.convert.material.MpMaterialConvert;
  7 +import cn.iocoder.yudao.module.mp.dal.dataobject.material.MpMaterialDO;
  8 +import cn.iocoder.yudao.module.mp.service.material.MpMaterialService;
  9 +import io.swagger.annotations.Api;
  10 +import io.swagger.annotations.ApiOperation;
  11 +import org.springframework.validation.annotation.Validated;
  12 +import org.springframework.web.bind.annotation.PostMapping;
  13 +import org.springframework.web.bind.annotation.RequestMapping;
  14 +import org.springframework.web.bind.annotation.RestController;
  15 +
  16 +import javax.annotation.Resource;
  17 +import javax.validation.Valid;
  18 +import java.io.IOException;
  19 +
  20 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  21 +
  22 +@Api(tags = "管理后台 - 公众号素材")
  23 +@RestController
  24 +@RequestMapping("/mp/material")
  25 +@Validated
  26 +public class MpMaterialController {
  27 +
  28 + @Resource
  29 + private MpMaterialService mpMaterialService;
  30 +
  31 + @ApiOperation("上传临时素材")
  32 + @PostMapping("/upload-temporary")
  33 + public CommonResult<MpMaterialUploadRespVO> uploadTemporaryMaterial(
  34 + @Valid MpMaterialUploadTemporaryReqVO reqVO) throws IOException {
  35 + MpMaterialDO material = mpMaterialService.uploadTemporaryMaterial(reqVO);
  36 + return success(MpMaterialConvert.INSTANCE.convert(material));
  37 + }
  38 +
  39 +}
... ...
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/vo/MpMaterialUploadRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.mp.controller.admin.material.vo;
  2 +
  3 +import io.swagger.annotations.ApiModel;
  4 +import io.swagger.annotations.ApiModelProperty;
  5 +import lombok.Data;
  6 +
  7 +@ApiModel("管理后台 - 公众号素材上传结果 Response VO")
  8 +@Data
  9 +public class MpMaterialUploadRespVO {
  10 +
  11 + @ApiModelProperty(value = "素材的 media_id", required = true, example = "123")
  12 + private String mediaId;
  13 +
  14 + @ApiModelProperty(value = "素材的 URL", required = true, example = "https://www.iocoder.cn/1.png")
  15 + private String url;
  16 +
  17 +}
... ...
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/vo/MpMaterialUploadTemporaryReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.mp.controller.admin.material.vo;
  2 +
  3 +import com.fasterxml.jackson.annotation.JsonIgnore;
  4 +import io.swagger.annotations.ApiModel;
  5 +import io.swagger.annotations.ApiModelProperty;
  6 +import lombok.Data;
  7 +import org.springframework.web.multipart.MultipartFile;
  8 +
  9 +import javax.validation.constraints.NotEmpty;
  10 +import javax.validation.constraints.NotNull;
  11 +
  12 +@ApiModel("管理后台 - 公众号素材上传临时 Request VO")
  13 +@Data
  14 +public class MpMaterialUploadTemporaryReqVO {
  15 +
  16 + @ApiModelProperty(value = "公众号账号的编号", required = true, example = "2048")
  17 + @NotNull(message = "公众号账号的编号不能为空")
  18 + private Long accountId;
  19 +
  20 + @ApiModelProperty(value = "文件类型", required = true, example = "image", notes = "参见 WxConsts.MediaFileType 枚举")
  21 + @NotEmpty(message = "文件类型不能为空")
  22 + private String type;
  23 +
  24 + @ApiModelProperty(value = "文件附件", required = true)
  25 + @NotNull(message = "文件不能为空")
  26 + @JsonIgnore // 避免被操作日志,进行序列化,导致报错
  27 + private MultipartFile file;
  28 +
  29 +}
... ...
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/material/MpMaterialConvert.java 0 → 100644
  1 +package cn.iocoder.yudao.module.mp.convert.material;
  2 +
  3 +import cn.iocoder.yudao.module.mp.controller.admin.material.vo.MpMaterialUploadRespVO;
  4 +import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;
  5 +import cn.iocoder.yudao.module.mp.dal.dataobject.material.MpMaterialDO;
  6 +import org.mapstruct.Mapper;
  7 +import org.mapstruct.Mapping;
  8 +import org.mapstruct.Mappings;
  9 +import org.mapstruct.factory.Mappers;
  10 +
  11 +@Mapper
  12 +public interface MpMaterialConvert {
  13 +
  14 + MpMaterialConvert INSTANCE = Mappers.getMapper(MpMaterialConvert.class);
  15 +
  16 + @Mappings({
  17 + @Mapping(target = "id", ignore = true),
  18 + @Mapping(source = "account.id", target = "accountId"),
  19 + @Mapping(source = "account.appId", target = "appId"),
  20 + })
  21 + MpMaterialDO convert(String mediaId, String type, String url, MpAccountDO account);
  22 +
  23 + MpMaterialUploadRespVO convert(MpMaterialDO bean);
  24 +
  25 +}
... ...
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/material/MpMaterialDO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.mp.dal.dataobject.material;
  2 +
  3 +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
  4 +import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;
  5 +import com.baomidou.mybatisplus.annotation.KeySequence;
  6 +import com.baomidou.mybatisplus.annotation.TableId;
  7 +import com.baomidou.mybatisplus.annotation.TableName;
  8 +import lombok.*;
  9 +import me.chanjar.weixin.common.api.WxConsts;
  10 +
  11 +/**
  12 + * 公众号素材 DO
  13 + *
  14 + * 1. <a href="https://developers.weixin.qq.com/doc/offiaccount/Asset_Management/New_temporary_materials.html">临时素材</a>
  15 + * 2. <a href="https://developers.weixin.qq.com/doc/offiaccount/Asset_Management/Adding_Permanent_Assets.html">永久素材</a>
  16 + *
  17 + * @author 芋道源码
  18 + */
  19 +@TableName("mp_material")
  20 +@KeySequence("mp_material_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
  21 +@Data
  22 +@EqualsAndHashCode(callSuper = true)
  23 +@ToString(callSuper = true)
  24 +@Builder
  25 +@NoArgsConstructor
  26 +@AllArgsConstructor
  27 +public class MpMaterialDO extends BaseDO {
  28 +
  29 + /**
  30 + * 主键
  31 + */
  32 + @TableId
  33 + private Long id;
  34 + /**
  35 + * 微信公众号 ID
  36 + *
  37 + * 关联 {@link MpAccountDO#getId()}
  38 + */
  39 + private Long accountId;
  40 + /**
  41 + * 微信公众号 appid
  42 + *
  43 + * 冗余 {@link MpAccountDO#getAppId()}
  44 + */
  45 + private String appId;
  46 +
  47 + /**
  48 + * 公众号素材 id
  49 + */
  50 + private String mediaId;
  51 + /**
  52 + * 文件类型
  53 + *
  54 + * 枚举 {@link WxConsts.MediaFileType}
  55 + */
  56 + private String type;
  57 + /**
  58 + * 是否永久
  59 + *
  60 + * true - 永久素材
  61 + * false - 临时素材
  62 + */
  63 + private Boolean permanent;
  64 + /**
  65 + * 文件服务器的 URL
  66 + */
  67 + private String url;
  68 +
  69 + /**
  70 + * 名字
  71 + *
  72 + * 只有【永久素材】使用
  73 + */
  74 + private String name;
  75 + /**
  76 + * 公众号文件 URL
  77 + *
  78 + * 只有【永久素材】使用
  79 + */
  80 + private String mpUrl;
  81 +
  82 + /**
  83 + * 视频素材的标题
  84 + *
  85 + * 只有【永久素材】使用
  86 + */
  87 + private String title;
  88 + /**
  89 + * 视频素材的描述
  90 + *
  91 + * 只有【永久素材】使用
  92 + */
  93 + private String introduction;
  94 +
  95 +}
... ...
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/mysql/material/MpMaterialMapper.java 0 → 100644
  1 +package cn.iocoder.yudao.module.mp.dal.mysql.material;
  2 +
  3 +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
  4 +import cn.iocoder.yudao.module.mp.dal.dataobject.material.MpMaterialDO;
  5 +import org.apache.ibatis.annotations.Mapper;
  6 +
  7 +@Mapper
  8 +public interface MpMaterialMapper extends BaseMapperX<MpMaterialDO> {
  9 +
  10 + default MpMaterialDO selectByAccountIdAndMediaId(Long accountId, String mediaId) {
  11 + return selectOne(MpMaterialDO::getAccountId, accountId,
  12 + MpMaterialDO::getMediaId, mediaId);
  13 + }
  14 +
  15 +}
... ...
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 {
80 80 ValidationUtils.validate(validator, message, group);
81 81 }
82 82  
  83 + /**
  84 + * 根据消息类型,获得对应的媒体文件类型
  85 + *
  86 + * 注意,不会返回 WxConsts.MediaFileType.THUMB,因为该类型会有明确标注
  87 + *
  88 + * @param messageType 消息类型 {@link WxConsts.XmlMsgType}
  89 + * @return 媒体文件类型 {@link WxConsts.MediaFileType}
  90 + */
  91 + public static String getMediaFileType(String messageType) {
  92 + switch (messageType) {
  93 + case WxConsts.XmlMsgType.IMAGE:
  94 + return WxConsts.MediaFileType.IMAGE;
  95 + case WxConsts.XmlMsgType.VOICE:
  96 + return WxConsts.MediaFileType.VOICE;
  97 + case WxConsts.XmlMsgType.VIDEO:
  98 + return WxConsts.MediaFileType.VIDEO;
  99 + default:
  100 + return WxConsts.MediaFileType.FILE;
  101 + }
  102 + }
  103 +
83 104 }
... ...
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/material/MpMaterialService.java 0 → 100644
  1 +package cn.iocoder.yudao.module.mp.service.material;
  2 +
  3 +import cn.iocoder.yudao.module.mp.controller.admin.material.vo.MpMaterialUploadTemporaryReqVO;
  4 +import cn.iocoder.yudao.module.mp.dal.dataobject.material.MpMaterialDO;
  5 +import me.chanjar.weixin.common.api.WxConsts;
  6 +
  7 +import javax.validation.Valid;
  8 +import java.io.IOException;
  9 +
  10 +/**
  11 + * 公众号素材 Service 接口
  12 + *
  13 + * @author 芋道源码
  14 + */
  15 +public interface MpMaterialService {
  16 +
  17 + /**
  18 + * 获得素材的 URL
  19 + *
  20 + * 该 URL 来自我们自己的文件服务器存储的 URL,不是公众号存储的 URL
  21 + *
  22 + * @param accountId 公众号账号编号
  23 + * @param mediaId 公众号素材 id
  24 + * @param type 文件类型 {@link WxConsts.MediaFileType}
  25 + * @return 素材的 URL
  26 + */
  27 + String downloadMaterialUrl(Long accountId, String mediaId, String type);
  28 +
  29 + MpMaterialDO uploadTemporaryMaterial(@Valid MpMaterialUploadTemporaryReqVO reqVO) throws IOException;
  30 +}
... ...
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/material/MpMaterialServiceImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.mp.service.material;
  2 +
  3 +import cn.hutool.core.io.FileTypeUtil;
  4 +import cn.hutool.core.io.FileUtil;
  5 +import cn.hutool.core.util.ObjUtil;
  6 +import cn.iocoder.yudao.module.infra.api.file.FileApi;
  7 +import cn.iocoder.yudao.module.mp.controller.admin.material.vo.MpMaterialUploadTemporaryReqVO;
  8 +import cn.iocoder.yudao.module.mp.convert.material.MpMaterialConvert;
  9 +import cn.iocoder.yudao.module.mp.dal.dataobject.account.MpAccountDO;
  10 +import cn.iocoder.yudao.module.mp.dal.dataobject.material.MpMaterialDO;
  11 +import cn.iocoder.yudao.module.mp.dal.mysql.material.MpMaterialMapper;
  12 +import cn.iocoder.yudao.module.mp.framework.mp.core.MpServiceFactory;
  13 +import cn.iocoder.yudao.module.mp.service.account.MpAccountService;
  14 +import lombok.extern.slf4j.Slf4j;
  15 +import me.chanjar.weixin.common.bean.result.WxMediaUploadResult;
  16 +import me.chanjar.weixin.common.error.WxErrorException;
  17 +import me.chanjar.weixin.mp.api.WxMpService;
  18 +import org.springframework.context.annotation.Lazy;
  19 +import org.springframework.stereotype.Service;
  20 +import org.springframework.validation.annotation.Validated;
  21 +
  22 +import javax.annotation.Resource;
  23 +import java.io.File;
  24 +import java.io.IOException;
  25 +
  26 +/**
  27 + * 公众号素材 Service 接口
  28 + *
  29 + * @author 芋道源码
  30 + */
  31 +@Service
  32 +@Validated
  33 +@Slf4j
  34 +public class MpMaterialServiceImpl implements MpMaterialService {
  35 +
  36 + @Resource
  37 + private MpMaterialMapper mpMaterialMapper;
  38 +
  39 + @Resource
  40 + private FileApi fileApi;
  41 +
  42 + @Resource
  43 + @Lazy // 延迟加载,解决循环依赖的问题
  44 + private MpAccountService mpAccountService;
  45 +
  46 + @Resource
  47 + @Lazy // 延迟加载,解决循环依赖的问题
  48 + private MpServiceFactory mpServiceFactory;
  49 +
  50 + @Override
  51 + public String downloadMaterialUrl(Long accountId, String mediaId, String type) {
  52 + // 第一步,直接从数据库查询。如果已经下载,直接返回
  53 + MpMaterialDO material = mpMaterialMapper.selectByAccountIdAndMediaId(accountId, mediaId);
  54 + if (material != null) {
  55 + return material.getUrl();
  56 + }
  57 +
  58 + // 第二步,尝试从临时素材中下载
  59 + String url = downloadMedia(accountId, mediaId);
  60 + if (url == null) {
  61 + return null;
  62 + }
  63 + MpAccountDO account = mpAccountService.getRequiredAccount(accountId);
  64 + material = MpMaterialConvert.INSTANCE.convert(mediaId, type, url, account)
  65 + .setPermanent(false);
  66 + mpMaterialMapper.insert(material);
  67 +
  68 + // 不考虑下载永久素材,因为上传的时候已经保存
  69 + return url;
  70 + }
  71 +
  72 + @Override
  73 + public MpMaterialDO uploadTemporaryMaterial(MpMaterialUploadTemporaryReqVO reqVO) throws IOException {
  74 + WxMpService mpService = mpServiceFactory.getRequiredMpService(reqVO.getAccountId());
  75 + // 第一步,上传到公众号
  76 + File file = null;
  77 + WxMediaUploadResult result;
  78 + String mediaId;
  79 + String url;
  80 + try {
  81 + // 写入到临时文件
  82 + file = FileUtil.newFile(FileUtil.getTmpDirPath() + reqVO.getFile().getOriginalFilename());
  83 + reqVO.getFile().transferTo(file);
  84 + // 上传到公众号
  85 + result = mpService.getMaterialService().mediaUpload(reqVO.getType(), file);
  86 + // 上传到文件服务
  87 + mediaId = ObjUtil.defaultIfNull(result.getMediaId(), result.getThumbMediaId());
  88 + url = uploadFile(mediaId, file);
  89 + } catch (WxErrorException e) {
  90 + // TODO yunai:待完善
  91 + throw new RuntimeException(e);
  92 + } finally {
  93 + FileUtil.del(file);
  94 + }
  95 +
  96 + // 第二步,存储到数据库
  97 + MpAccountDO account = mpAccountService.getRequiredAccount(reqVO.getAccountId());
  98 + MpMaterialDO material = MpMaterialConvert.INSTANCE.convert(mediaId, reqVO.getType(), url, account)
  99 + .setPermanent(false);
  100 + mpMaterialMapper.insert(material);
  101 + return material;
  102 + }
  103 +
  104 + /**
  105 + * 下载微信媒体文件的内容,并上传到文件服务
  106 + *
  107 + * 为什么要下载?媒体文件在微信后台保存时间为 3 天,即 3 天后 media_id 失效。
  108 + *
  109 + * @param accountId 公众号账号的编号
  110 + * @param mediaId 媒体文件编号
  111 + * @return 上传后的 URL
  112 + */
  113 + public String downloadMedia(Long accountId, String mediaId) {
  114 + WxMpService mpService = mpServiceFactory.getMpService(accountId);
  115 + for (int i = 0; i < 3; i++) {
  116 + try {
  117 + // 第一步,从公众号下载媒体文件
  118 + File file = mpService.getMaterialService().mediaDownload(mediaId);
  119 + // 第二步,上传到文件服务
  120 + return uploadFile(mediaId, file);
  121 + } catch (WxErrorException e) {
  122 + log.error("[mediaDownload][media({}) 第 ({}) 次下载失败]", mediaId, i);
  123 + }
  124 + }
  125 + return null;
  126 + }
  127 +
  128 + private String uploadFile(String mediaId, File file) {
  129 + String path = mediaId + "." + FileTypeUtil.getType(file);
  130 + return fileApi.createFile(path, FileUtil.readBytes(file));
  131 + }
  132 +
  133 +}
... ...
yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/message/MpMessageServiceImpl.java
1 1 package cn.iocoder.yudao.module.mp.service.message;
2 2  
3   -import cn.hutool.core.io.FileTypeUtil;
4   -import cn.hutool.core.io.FileUtil;
5 3 import cn.hutool.core.lang.Assert;
6 4 import cn.hutool.core.util.StrUtil;
7 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
8   -import cn.iocoder.yudao.module.infra.api.file.FileApi;
9 6 import cn.iocoder.yudao.module.mp.controller.admin.message.vo.MpMessagePageReqVO;
10 7 import cn.iocoder.yudao.module.mp.controller.admin.message.vo.MpMessageSendReqVO;
11 8 import cn.iocoder.yudao.module.mp.convert.message.MpMessageConvert;
... ... @@ -17,9 +14,11 @@ import cn.iocoder.yudao.module.mp.enums.message.MpMessageSendFromEnum;
17 14 import cn.iocoder.yudao.module.mp.framework.mp.core.MpServiceFactory;
18 15 import cn.iocoder.yudao.module.mp.framework.mp.core.util.MpUtils;
19 16 import cn.iocoder.yudao.module.mp.service.account.MpAccountService;
  17 +import cn.iocoder.yudao.module.mp.service.material.MpMaterialService;
20 18 import cn.iocoder.yudao.module.mp.service.message.bo.MpMessageSendOutReqBO;
21 19 import cn.iocoder.yudao.module.mp.service.user.MpUserService;
22 20 import lombok.extern.slf4j.Slf4j;
  21 +import me.chanjar.weixin.common.api.WxConsts;
23 22 import me.chanjar.weixin.common.error.WxErrorException;
24 23 import me.chanjar.weixin.mp.api.WxMpService;
25 24 import me.chanjar.weixin.mp.bean.kefu.WxMpKefuMessage;
... ... @@ -31,7 +30,6 @@ import org.springframework.validation.annotation.Validated;
31 30  
32 31 import javax.annotation.Resource;
33 32 import javax.validation.Validator;
34   -import java.io.File;
35 33  
36 34 /**
37 35 * 粉丝消息表 Service 实现类
... ... @@ -48,6 +46,8 @@ public class MpMessageServiceImpl implements MpMessageService {
48 46 private MpAccountService mpAccountService;
49 47 @Resource
50 48 private MpUserService mpUserService;
  49 + @Resource
  50 + private MpMaterialService mpMaterialService;
51 51  
52 52 @Resource
53 53 private MpMessageMapper mpMessageMapper;
... ... @@ -57,9 +57,6 @@ public class MpMessageServiceImpl implements MpMessageService {
57 57 private MpServiceFactory mpServiceFactory;
58 58  
59 59 @Resource
60   - private FileApi fileApi;
61   -
62   - @Resource
63 60 private Validator validator;
64 61  
65 62 @Override
... ... @@ -69,7 +66,6 @@ public class MpMessageServiceImpl implements MpMessageService {
69 66  
70 67 @Override
71 68 public void receiveMessage(String appId, WxMpXmlMessage wxMessage) {
72   - WxMpService mpService = mpServiceFactory.getRequiredMpService(appId);
73 69 // 获得关联信息
74 70 MpAccountDO account = mpAccountService.getAccountFromCache(appId);
75 71 Assert.notNull(account, "公众号账号({}) 不存在", appId);
... ... @@ -79,7 +75,7 @@ public class MpMessageServiceImpl implements MpMessageService {
79 75 // 记录消息
80 76 MpMessageDO message = MpMessageConvert.INSTANCE.convert(wxMessage, account, user);
81 77 message.setSendFrom(MpMessageSendFromEnum.USER_TO_MP.getFrom());
82   - downloadMessageMedia(mpService, message);
  78 + downloadMessageMedia(message);
83 79 mpMessageMapper.insert(message);
84 80 }
85 81  
... ... @@ -97,6 +93,7 @@ public class MpMessageServiceImpl implements MpMessageService {
97 93 // 记录消息
98 94 MpMessageDO message = MpMessageConvert.INSTANCE.convert(sendReqBO, account, user);
99 95 message.setSendFrom(MpMessageSendFromEnum.MP_TO_USER.getFrom());
  96 + // TODO 芋艿:downloadMessageMedia
100 97 mpMessageMapper.insert(message);
101 98  
102 99 // 转换返回 WxMpXmlOutMessage 对象
... ... @@ -124,7 +121,7 @@ public class MpMessageServiceImpl implements MpMessageService {
124 121 // 记录消息
125 122 MpMessageDO message = MpMessageConvert.INSTANCE.convert(wxMessage, account, user);
126 123 message.setSendFrom(MpMessageSendFromEnum.MP_TO_USER.getFrom());
127   - downloadMessageMedia(mpService, message);
  124 + downloadMessageMedia(message);
128 125 mpMessageMapper.insert(message);
129 126 return message;
130 127 }
... ... @@ -132,38 +129,17 @@ public class MpMessageServiceImpl implements MpMessageService {
132 129 /**
133 130 * 下载消息使用到的媒体文件,并上传到文件服务
134 131 *
135   - * @param mpService 公众号 Service
136 132 * @param message 消息
137 133 */
138   - private void downloadMessageMedia(WxMpService mpService, MpMessageDO message) {
  134 + private void downloadMessageMedia(MpMessageDO message) {
139 135 if (StrUtil.isNotEmpty(message.getMediaId())) {
140   - message.setMediaUrl(downloadMedia(mpService, message.getMediaId()));
  136 + message.setMediaUrl(mpMaterialService.downloadMaterialUrl(message.getAccountId(),
  137 + message.getMediaId(), MpUtils.getMediaFileType(message.getType())));
141 138 }
142 139 if (StrUtil.isNotEmpty(message.getThumbMediaId())) {
143   - message.setThumbMediaUrl(downloadMedia(mpService, message.getThumbMediaId()));
144   - }
145   - }
146   -
147   - /**
148   - * 下载微信媒体文件的内容,并上传到文件服务
149   - *
150   - * 为什么要下载?媒体文件在微信后台保存时间为 3 天,即 3 天后 media_id 失效。
151   - *
152   - * @param mpService 微信公众号 Service
153   - * @param mediaId 媒体文件编号
154   - * @return 上传后的 URL
155   - */
156   - private String downloadMedia(WxMpService mpService, String mediaId) {
157   - try {
158   - // 第一步,从公众号下载媒体文件
159   - File file = mpService.getMaterialService().mediaDownload(mediaId);
160   - // 第二步,上传到文件服务
161   - String path = mediaId + "." + FileTypeUtil.getType(file);
162   - return fileApi.createFile(path, FileUtil.readBytes(file));
163   - } catch (WxErrorException e) {
164   - log.error("[mediaDownload][media({}) 下载失败]", mediaId);
  140 + message.setThumbMediaUrl(mpMaterialService.downloadMaterialUrl(message.getAccountId(),
  141 + message.getThumbMediaId(), WxConsts.MediaFileType.THUMB));
165 142 }
166   - return null;
167 143 }
168 144  
169 145 }
... ...
yudao-ui-admin/src/views/mp/components/wx-msg/main.vue
... ... @@ -158,7 +158,7 @@ import {getMessagePage, sendMessage} from &#39;@/api/mp/message&#39;
158 158 }, {
159 159 ...this.objData,
160 160 type: this.objData.repType,
161   - content: this.objData.repContent,
  161 + // content: this.objData.repContent,
162 162 // TODO 芋艿:临时适配,保证可用
163 163 })).then(response => {
164 164 this.sendLoading = false
... ...
yudao-ui-admin/src/views/mp/components/wx-reply/main.vue
... ... @@ -4,15 +4,12 @@
4 4 -->
5 5 <template>
6 6 <el-tabs type="border-card" v-model="objData.repType" @tab-click="handleClick">
  7 + <!-- 类型 1:文本 -->
7 8 <el-tab-pane name="text">
8 9 <span slot="label"><i class="el-icon-document"></i> 文本</span>
9   - <el-input
10   - type="textarea"
11   - :rows="5"
12   - placeholder="请输入内容"
13   - v-model="objData.repContent">
14   - </el-input>
  10 + <el-input type="textarea" :rows="5" placeholder="请输入内容" v-model="objData.content" />
15 11 </el-tab-pane>
  12 + <!-- 类型 2:图片 -->
16 13 <el-tab-pane name="image">
17 14 <span slot="label"><i class="el-icon-picture"></i> 图片</span>
18 15 <el-row>
... ... @@ -29,19 +26,10 @@
29 26 <el-button type="success" @click="openMaterial">素材库选择<i class="el-icon-circle-check el-icon--right"></i></el-button>
30 27 </el-col>
31 28 <el-col :span="12" class="col-add">
32   - <el-upload
33   - :action="actionUrl"
34   - :headers="headers"
35   - multiple
36   - :limit="1"
37   - :on-success="handleUploadSuccess"
38   - :file-list="fileList"
39   - :before-upload="beforeImageUpload"
40   - :data="uploadData">
  29 + <el-upload :action="actionUrl" :headers="headers" multiple :limit="1" :file-list="fileList" :data="uploadData"
  30 + :before-upload="beforeImageUpload" :on-success="handleUploadSuccess">
41 31 <el-button type="primary">上传图片</el-button>
42   - <div slot="tip" class="el-upload__tip">
43   - 支持bmp/png/jpeg/jpg/gif格式,大小不超过2M
44   - </div>
  32 + <div slot="tip" class="el-upload__tip">支持 bmp/png/jpeg/jpg/gif 格式,大小不超过 2M</div>
45 33 </el-upload>
46 34 </el-col>
47 35 </el-row>
... ... @@ -69,12 +57,7 @@
69 57 <el-button type="success" @click="openMaterial">素材库选择<i class="el-icon-circle-check el-icon--right"></i></el-button>
70 58 </el-col>
71 59 <el-col :span="12" class="col-add">
72   - <el-upload
73   - :action="actionUrl"
74   - :headers="headers"
75   - multiple
76   - :limit="1"
77   - :on-success="handleUploadSuccess"
  60 + <el-upload :action="actionUrl" :headers="headers" multiple :limit="1" :on-success="handleUploadSuccess"
78 61 :file-list="fileList"
79 62 :before-upload="beforeVoiceUpload"
80 63 :data="uploadData">
... ... @@ -188,9 +171,9 @@
188 171 },
189 172 props: {
190 173 objData:{
191   - type:Object
  174 + type: Object
192 175 },
193   - //图文类型:1、已发布图文;2、草稿箱图文
  176 + // 图文类型:1、已发布图文;2、草稿箱图文
194 177 newsType:{
195 178 type: String,
196 179 default: "1"
... ...