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,9 +4,6 @@ import lombok.*;
4 import io.swagger.annotations.*; 4 import io.swagger.annotations.*;
5 import javax.validation.constraints.*; 5 import javax.validation.constraints.*;
6 6
7 -/**  
8 - * @author fengdan  
9 - */  
10 @ApiModel("管理后台 - 公众号账号更新 Request VO") 7 @ApiModel("管理后台 - 公众号账号更新 Request VO")
11 @Data 8 @Data
12 @EqualsAndHashCode(callSuper = true) 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,4 +80,25 @@ public class MpUtils {
80 ValidationUtils.validate(validator, message, group); 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 package cn.iocoder.yudao.module.mp.service.message; 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 import cn.hutool.core.lang.Assert; 3 import cn.hutool.core.lang.Assert;
6 import cn.hutool.core.util.StrUtil; 4 import cn.hutool.core.util.StrUtil;
7 import cn.iocoder.yudao.framework.common.pojo.PageResult; 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
8 -import cn.iocoder.yudao.module.infra.api.file.FileApi;  
9 import cn.iocoder.yudao.module.mp.controller.admin.message.vo.MpMessagePageReqVO; 6 import cn.iocoder.yudao.module.mp.controller.admin.message.vo.MpMessagePageReqVO;
10 import cn.iocoder.yudao.module.mp.controller.admin.message.vo.MpMessageSendReqVO; 7 import cn.iocoder.yudao.module.mp.controller.admin.message.vo.MpMessageSendReqVO;
11 import cn.iocoder.yudao.module.mp.convert.message.MpMessageConvert; 8 import cn.iocoder.yudao.module.mp.convert.message.MpMessageConvert;
@@ -17,9 +14,11 @@ import cn.iocoder.yudao.module.mp.enums.message.MpMessageSendFromEnum; @@ -17,9 +14,11 @@ import cn.iocoder.yudao.module.mp.enums.message.MpMessageSendFromEnum;
17 import cn.iocoder.yudao.module.mp.framework.mp.core.MpServiceFactory; 14 import cn.iocoder.yudao.module.mp.framework.mp.core.MpServiceFactory;
18 import cn.iocoder.yudao.module.mp.framework.mp.core.util.MpUtils; 15 import cn.iocoder.yudao.module.mp.framework.mp.core.util.MpUtils;
19 import cn.iocoder.yudao.module.mp.service.account.MpAccountService; 16 import cn.iocoder.yudao.module.mp.service.account.MpAccountService;
  17 +import cn.iocoder.yudao.module.mp.service.material.MpMaterialService;
20 import cn.iocoder.yudao.module.mp.service.message.bo.MpMessageSendOutReqBO; 18 import cn.iocoder.yudao.module.mp.service.message.bo.MpMessageSendOutReqBO;
21 import cn.iocoder.yudao.module.mp.service.user.MpUserService; 19 import cn.iocoder.yudao.module.mp.service.user.MpUserService;
22 import lombok.extern.slf4j.Slf4j; 20 import lombok.extern.slf4j.Slf4j;
  21 +import me.chanjar.weixin.common.api.WxConsts;
23 import me.chanjar.weixin.common.error.WxErrorException; 22 import me.chanjar.weixin.common.error.WxErrorException;
24 import me.chanjar.weixin.mp.api.WxMpService; 23 import me.chanjar.weixin.mp.api.WxMpService;
25 import me.chanjar.weixin.mp.bean.kefu.WxMpKefuMessage; 24 import me.chanjar.weixin.mp.bean.kefu.WxMpKefuMessage;
@@ -31,7 +30,6 @@ import org.springframework.validation.annotation.Validated; @@ -31,7 +30,6 @@ import org.springframework.validation.annotation.Validated;
31 30
32 import javax.annotation.Resource; 31 import javax.annotation.Resource;
33 import javax.validation.Validator; 32 import javax.validation.Validator;
34 -import java.io.File;  
35 33
36 /** 34 /**
37 * 粉丝消息表 Service 实现类 35 * 粉丝消息表 Service 实现类
@@ -48,6 +46,8 @@ public class MpMessageServiceImpl implements MpMessageService { @@ -48,6 +46,8 @@ public class MpMessageServiceImpl implements MpMessageService {
48 private MpAccountService mpAccountService; 46 private MpAccountService mpAccountService;
49 @Resource 47 @Resource
50 private MpUserService mpUserService; 48 private MpUserService mpUserService;
  49 + @Resource
  50 + private MpMaterialService mpMaterialService;
51 51
52 @Resource 52 @Resource
53 private MpMessageMapper mpMessageMapper; 53 private MpMessageMapper mpMessageMapper;
@@ -57,9 +57,6 @@ public class MpMessageServiceImpl implements MpMessageService { @@ -57,9 +57,6 @@ public class MpMessageServiceImpl implements MpMessageService {
57 private MpServiceFactory mpServiceFactory; 57 private MpServiceFactory mpServiceFactory;
58 58
59 @Resource 59 @Resource
60 - private FileApi fileApi;  
61 -  
62 - @Resource  
63 private Validator validator; 60 private Validator validator;
64 61
65 @Override 62 @Override
@@ -69,7 +66,6 @@ public class MpMessageServiceImpl implements MpMessageService { @@ -69,7 +66,6 @@ public class MpMessageServiceImpl implements MpMessageService {
69 66
70 @Override 67 @Override
71 public void receiveMessage(String appId, WxMpXmlMessage wxMessage) { 68 public void receiveMessage(String appId, WxMpXmlMessage wxMessage) {
72 - WxMpService mpService = mpServiceFactory.getRequiredMpService(appId);  
73 // 获得关联信息 69 // 获得关联信息
74 MpAccountDO account = mpAccountService.getAccountFromCache(appId); 70 MpAccountDO account = mpAccountService.getAccountFromCache(appId);
75 Assert.notNull(account, "公众号账号({}) 不存在", appId); 71 Assert.notNull(account, "公众号账号({}) 不存在", appId);
@@ -79,7 +75,7 @@ public class MpMessageServiceImpl implements MpMessageService { @@ -79,7 +75,7 @@ public class MpMessageServiceImpl implements MpMessageService {
79 // 记录消息 75 // 记录消息
80 MpMessageDO message = MpMessageConvert.INSTANCE.convert(wxMessage, account, user); 76 MpMessageDO message = MpMessageConvert.INSTANCE.convert(wxMessage, account, user);
81 message.setSendFrom(MpMessageSendFromEnum.USER_TO_MP.getFrom()); 77 message.setSendFrom(MpMessageSendFromEnum.USER_TO_MP.getFrom());
82 - downloadMessageMedia(mpService, message); 78 + downloadMessageMedia(message);
83 mpMessageMapper.insert(message); 79 mpMessageMapper.insert(message);
84 } 80 }
85 81
@@ -97,6 +93,7 @@ public class MpMessageServiceImpl implements MpMessageService { @@ -97,6 +93,7 @@ public class MpMessageServiceImpl implements MpMessageService {
97 // 记录消息 93 // 记录消息
98 MpMessageDO message = MpMessageConvert.INSTANCE.convert(sendReqBO, account, user); 94 MpMessageDO message = MpMessageConvert.INSTANCE.convert(sendReqBO, account, user);
99 message.setSendFrom(MpMessageSendFromEnum.MP_TO_USER.getFrom()); 95 message.setSendFrom(MpMessageSendFromEnum.MP_TO_USER.getFrom());
  96 + // TODO 芋艿:downloadMessageMedia
100 mpMessageMapper.insert(message); 97 mpMessageMapper.insert(message);
101 98
102 // 转换返回 WxMpXmlOutMessage 对象 99 // 转换返回 WxMpXmlOutMessage 对象
@@ -124,7 +121,7 @@ public class MpMessageServiceImpl implements MpMessageService { @@ -124,7 +121,7 @@ public class MpMessageServiceImpl implements MpMessageService {
124 // 记录消息 121 // 记录消息
125 MpMessageDO message = MpMessageConvert.INSTANCE.convert(wxMessage, account, user); 122 MpMessageDO message = MpMessageConvert.INSTANCE.convert(wxMessage, account, user);
126 message.setSendFrom(MpMessageSendFromEnum.MP_TO_USER.getFrom()); 123 message.setSendFrom(MpMessageSendFromEnum.MP_TO_USER.getFrom());
127 - downloadMessageMedia(mpService, message); 124 + downloadMessageMedia(message);
128 mpMessageMapper.insert(message); 125 mpMessageMapper.insert(message);
129 return message; 126 return message;
130 } 127 }
@@ -132,38 +129,17 @@ public class MpMessageServiceImpl implements MpMessageService { @@ -132,38 +129,17 @@ public class MpMessageServiceImpl implements MpMessageService {
132 /** 129 /**
133 * 下载消息使用到的媒体文件,并上传到文件服务 130 * 下载消息使用到的媒体文件,并上传到文件服务
134 * 131 *
135 - * @param mpService 公众号 Service  
136 * @param message 消息 132 * @param message 消息
137 */ 133 */
138 - private void downloadMessageMedia(WxMpService mpService, MpMessageDO message) { 134 + private void downloadMessageMedia(MpMessageDO message) {
139 if (StrUtil.isNotEmpty(message.getMediaId())) { 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 if (StrUtil.isNotEmpty(message.getThumbMediaId())) { 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,7 +158,7 @@ import {getMessagePage, sendMessage} from &#39;@/api/mp/message&#39;
158 }, { 158 }, {
159 ...this.objData, 159 ...this.objData,
160 type: this.objData.repType, 160 type: this.objData.repType,
161 - content: this.objData.repContent, 161 + // content: this.objData.repContent,
162 // TODO 芋艿:临时适配,保证可用 162 // TODO 芋艿:临时适配,保证可用
163 })).then(response => { 163 })).then(response => {
164 this.sendLoading = false 164 this.sendLoading = false
yudao-ui-admin/src/views/mp/components/wx-reply/main.vue
@@ -4,15 +4,12 @@ @@ -4,15 +4,12 @@
4 --> 4 -->
5 <template> 5 <template>
6 <el-tabs type="border-card" v-model="objData.repType" @tab-click="handleClick"> 6 <el-tabs type="border-card" v-model="objData.repType" @tab-click="handleClick">
  7 + <!-- 类型 1:文本 -->
7 <el-tab-pane name="text"> 8 <el-tab-pane name="text">
8 <span slot="label"><i class="el-icon-document"></i> 文本</span> 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 </el-tab-pane> 11 </el-tab-pane>
  12 + <!-- 类型 2:图片 -->
16 <el-tab-pane name="image"> 13 <el-tab-pane name="image">
17 <span slot="label"><i class="el-icon-picture"></i> 图片</span> 14 <span slot="label"><i class="el-icon-picture"></i> 图片</span>
18 <el-row> 15 <el-row>
@@ -29,19 +26,10 @@ @@ -29,19 +26,10 @@
29 <el-button type="success" @click="openMaterial">素材库选择<i class="el-icon-circle-check el-icon--right"></i></el-button> 26 <el-button type="success" @click="openMaterial">素材库选择<i class="el-icon-circle-check el-icon--right"></i></el-button>
30 </el-col> 27 </el-col>
31 <el-col :span="12" class="col-add"> 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 <el-button type="primary">上传图片</el-button> 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 </el-upload> 33 </el-upload>
46 </el-col> 34 </el-col>
47 </el-row> 35 </el-row>
@@ -69,12 +57,7 @@ @@ -69,12 +57,7 @@
69 <el-button type="success" @click="openMaterial">素材库选择<i class="el-icon-circle-check el-icon--right"></i></el-button> 57 <el-button type="success" @click="openMaterial">素材库选择<i class="el-icon-circle-check el-icon--right"></i></el-button>
70 </el-col> 58 </el-col>
71 <el-col :span="12" class="col-add"> 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 :file-list="fileList" 61 :file-list="fileList"
79 :before-upload="beforeVoiceUpload" 62 :before-upload="beforeVoiceUpload"
80 :data="uploadData"> 63 :data="uploadData">
@@ -188,9 +171,9 @@ @@ -188,9 +171,9 @@
188 }, 171 },
189 props: { 172 props: {
190 objData:{ 173 objData:{
191 - type:Object 174 + type: Object
192 }, 175 },
193 - //图文类型:1、已发布图文;2、草稿箱图文 176 + // 图文类型:1、已发布图文;2、草稿箱图文
194 newsType:{ 177 newsType:{
195 type: String, 178 type: String,
196 default: "1" 179 default: "1"