Commit ec872c702c6a5da948371a16b9aafa11a13a912d
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
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 '@/api/mp/message' |
| 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" | ... | ... |