mirror of
https://gitee.com/huangge1199_admin/vue-pro.git
synced 2024-11-25 08:41:52 +08:00
mp:增加上传临时素材的接口
This commit is contained in:
parent
541ee81300
commit
ec872c702c
@ -4,9 +4,6 @@ import lombok.*;
|
|||||||
import io.swagger.annotations.*;
|
import io.swagger.annotations.*;
|
||||||
import javax.validation.constraints.*;
|
import javax.validation.constraints.*;
|
||||||
|
|
||||||
/**
|
|
||||||
* @author fengdan
|
|
||||||
*/
|
|
||||||
@ApiModel("管理后台 - 公众号账号更新 Request VO")
|
@ApiModel("管理后台 - 公众号账号更新 Request VO")
|
||||||
@Data
|
@Data
|
||||||
@EqualsAndHashCode(callSuper = true)
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@ -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));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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;
|
||||||
|
|
||||||
|
}
|
@ -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;
|
||||||
|
|
||||||
|
}
|
@ -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);
|
||||||
|
|
||||||
|
}
|
@ -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;
|
||||||
|
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -80,4 +80,25 @@ public class MpUtils {
|
|||||||
ValidationUtils.validate(validator, message, group);
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
@ -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));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,11 +1,8 @@
|
|||||||
package cn.iocoder.yudao.module.mp.service.message;
|
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.lang.Assert;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
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.MpMessagePageReqVO;
|
||||||
import cn.iocoder.yudao.module.mp.controller.admin.message.vo.MpMessageSendReqVO;
|
import cn.iocoder.yudao.module.mp.controller.admin.message.vo.MpMessageSendReqVO;
|
||||||
import cn.iocoder.yudao.module.mp.convert.message.MpMessageConvert;
|
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.MpServiceFactory;
|
||||||
import cn.iocoder.yudao.module.mp.framework.mp.core.util.MpUtils;
|
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.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.message.bo.MpMessageSendOutReqBO;
|
||||||
import cn.iocoder.yudao.module.mp.service.user.MpUserService;
|
import cn.iocoder.yudao.module.mp.service.user.MpUserService;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import me.chanjar.weixin.common.api.WxConsts;
|
||||||
import me.chanjar.weixin.common.error.WxErrorException;
|
import me.chanjar.weixin.common.error.WxErrorException;
|
||||||
import me.chanjar.weixin.mp.api.WxMpService;
|
import me.chanjar.weixin.mp.api.WxMpService;
|
||||||
import me.chanjar.weixin.mp.bean.kefu.WxMpKefuMessage;
|
import me.chanjar.weixin.mp.bean.kefu.WxMpKefuMessage;
|
||||||
@ -31,7 +30,6 @@ import org.springframework.validation.annotation.Validated;
|
|||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
import javax.validation.Validator;
|
import javax.validation.Validator;
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 粉丝消息表 Service 实现类
|
* 粉丝消息表 Service 实现类
|
||||||
@ -48,6 +46,8 @@ public class MpMessageServiceImpl implements MpMessageService {
|
|||||||
private MpAccountService mpAccountService;
|
private MpAccountService mpAccountService;
|
||||||
@Resource
|
@Resource
|
||||||
private MpUserService mpUserService;
|
private MpUserService mpUserService;
|
||||||
|
@Resource
|
||||||
|
private MpMaterialService mpMaterialService;
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private MpMessageMapper mpMessageMapper;
|
private MpMessageMapper mpMessageMapper;
|
||||||
@ -56,9 +56,6 @@ public class MpMessageServiceImpl implements MpMessageService {
|
|||||||
@Lazy // 延迟加载,解决循环依赖的问题
|
@Lazy // 延迟加载,解决循环依赖的问题
|
||||||
private MpServiceFactory mpServiceFactory;
|
private MpServiceFactory mpServiceFactory;
|
||||||
|
|
||||||
@Resource
|
|
||||||
private FileApi fileApi;
|
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private Validator validator;
|
private Validator validator;
|
||||||
|
|
||||||
@ -69,7 +66,6 @@ public class MpMessageServiceImpl implements MpMessageService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void receiveMessage(String appId, WxMpXmlMessage wxMessage) {
|
public void receiveMessage(String appId, WxMpXmlMessage wxMessage) {
|
||||||
WxMpService mpService = mpServiceFactory.getRequiredMpService(appId);
|
|
||||||
// 获得关联信息
|
// 获得关联信息
|
||||||
MpAccountDO account = mpAccountService.getAccountFromCache(appId);
|
MpAccountDO account = mpAccountService.getAccountFromCache(appId);
|
||||||
Assert.notNull(account, "公众号账号({}) 不存在", appId);
|
Assert.notNull(account, "公众号账号({}) 不存在", appId);
|
||||||
@ -79,7 +75,7 @@ public class MpMessageServiceImpl implements MpMessageService {
|
|||||||
// 记录消息
|
// 记录消息
|
||||||
MpMessageDO message = MpMessageConvert.INSTANCE.convert(wxMessage, account, user);
|
MpMessageDO message = MpMessageConvert.INSTANCE.convert(wxMessage, account, user);
|
||||||
message.setSendFrom(MpMessageSendFromEnum.USER_TO_MP.getFrom());
|
message.setSendFrom(MpMessageSendFromEnum.USER_TO_MP.getFrom());
|
||||||
downloadMessageMedia(mpService, message);
|
downloadMessageMedia(message);
|
||||||
mpMessageMapper.insert(message);
|
mpMessageMapper.insert(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,6 +93,7 @@ public class MpMessageServiceImpl implements MpMessageService {
|
|||||||
// 记录消息
|
// 记录消息
|
||||||
MpMessageDO message = MpMessageConvert.INSTANCE.convert(sendReqBO, account, user);
|
MpMessageDO message = MpMessageConvert.INSTANCE.convert(sendReqBO, account, user);
|
||||||
message.setSendFrom(MpMessageSendFromEnum.MP_TO_USER.getFrom());
|
message.setSendFrom(MpMessageSendFromEnum.MP_TO_USER.getFrom());
|
||||||
|
// TODO 芋艿:downloadMessageMedia
|
||||||
mpMessageMapper.insert(message);
|
mpMessageMapper.insert(message);
|
||||||
|
|
||||||
// 转换返回 WxMpXmlOutMessage 对象
|
// 转换返回 WxMpXmlOutMessage 对象
|
||||||
@ -124,7 +121,7 @@ public class MpMessageServiceImpl implements MpMessageService {
|
|||||||
// 记录消息
|
// 记录消息
|
||||||
MpMessageDO message = MpMessageConvert.INSTANCE.convert(wxMessage, account, user);
|
MpMessageDO message = MpMessageConvert.INSTANCE.convert(wxMessage, account, user);
|
||||||
message.setSendFrom(MpMessageSendFromEnum.MP_TO_USER.getFrom());
|
message.setSendFrom(MpMessageSendFromEnum.MP_TO_USER.getFrom());
|
||||||
downloadMessageMedia(mpService, message);
|
downloadMessageMedia(message);
|
||||||
mpMessageMapper.insert(message);
|
mpMessageMapper.insert(message);
|
||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
@ -132,38 +129,17 @@ public class MpMessageServiceImpl implements MpMessageService {
|
|||||||
/**
|
/**
|
||||||
* 下载消息使用到的媒体文件,并上传到文件服务
|
* 下载消息使用到的媒体文件,并上传到文件服务
|
||||||
*
|
*
|
||||||
* @param mpService 公众号 Service
|
|
||||||
* @param message 消息
|
* @param message 消息
|
||||||
*/
|
*/
|
||||||
private void downloadMessageMedia(WxMpService mpService, MpMessageDO message) {
|
private void downloadMessageMedia(MpMessageDO message) {
|
||||||
if (StrUtil.isNotEmpty(message.getMediaId())) {
|
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())) {
|
if (StrUtil.isNotEmpty(message.getThumbMediaId())) {
|
||||||
message.setThumbMediaUrl(downloadMedia(mpService, message.getThumbMediaId()));
|
message.setThumbMediaUrl(mpMaterialService.downloadMaterialUrl(message.getAccountId(),
|
||||||
|
message.getThumbMediaId(), WxConsts.MediaFileType.THUMB));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 下载微信媒体文件的内容,并上传到文件服务
|
|
||||||
*
|
|
||||||
* 为什么要下载?媒体文件在微信后台保存时间为 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);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -158,7 +158,7 @@ import {getMessagePage, sendMessage} from '@/api/mp/message'
|
|||||||
}, {
|
}, {
|
||||||
...this.objData,
|
...this.objData,
|
||||||
type: this.objData.repType,
|
type: this.objData.repType,
|
||||||
content: this.objData.repContent,
|
// content: this.objData.repContent,
|
||||||
// TODO 芋艿:临时适配,保证可用
|
// TODO 芋艿:临时适配,保证可用
|
||||||
})).then(response => {
|
})).then(response => {
|
||||||
this.sendLoading = false
|
this.sendLoading = false
|
||||||
|
@ -4,15 +4,12 @@
|
|||||||
-->
|
-->
|
||||||
<template>
|
<template>
|
||||||
<el-tabs type="border-card" v-model="objData.repType" @tab-click="handleClick">
|
<el-tabs type="border-card" v-model="objData.repType" @tab-click="handleClick">
|
||||||
|
<!-- 类型 1:文本 -->
|
||||||
<el-tab-pane name="text">
|
<el-tab-pane name="text">
|
||||||
<span slot="label"><i class="el-icon-document"></i> 文本</span>
|
<span slot="label"><i class="el-icon-document"></i> 文本</span>
|
||||||
<el-input
|
<el-input type="textarea" :rows="5" placeholder="请输入内容" v-model="objData.content" />
|
||||||
type="textarea"
|
|
||||||
:rows="5"
|
|
||||||
placeholder="请输入内容"
|
|
||||||
v-model="objData.repContent">
|
|
||||||
</el-input>
|
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
|
<!-- 类型 2:图片 -->
|
||||||
<el-tab-pane name="image">
|
<el-tab-pane name="image">
|
||||||
<span slot="label"><i class="el-icon-picture"></i> 图片</span>
|
<span slot="label"><i class="el-icon-picture"></i> 图片</span>
|
||||||
<el-row>
|
<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-button type="success" @click="openMaterial">素材库选择<i class="el-icon-circle-check el-icon--right"></i></el-button>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="12" class="col-add">
|
<el-col :span="12" class="col-add">
|
||||||
<el-upload
|
<el-upload :action="actionUrl" :headers="headers" multiple :limit="1" :file-list="fileList" :data="uploadData"
|
||||||
:action="actionUrl"
|
:before-upload="beforeImageUpload" :on-success="handleUploadSuccess">
|
||||||
:headers="headers"
|
|
||||||
multiple
|
|
||||||
:limit="1"
|
|
||||||
:on-success="handleUploadSuccess"
|
|
||||||
:file-list="fileList"
|
|
||||||
:before-upload="beforeImageUpload"
|
|
||||||
:data="uploadData">
|
|
||||||
<el-button type="primary">上传图片</el-button>
|
<el-button type="primary">上传图片</el-button>
|
||||||
<div slot="tip" class="el-upload__tip">
|
<div slot="tip" class="el-upload__tip">支持 bmp/png/jpeg/jpg/gif 格式,大小不超过 2M</div>
|
||||||
支持bmp/png/jpeg/jpg/gif格式,大小不超过2M
|
|
||||||
</div>
|
|
||||||
</el-upload>
|
</el-upload>
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</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-button type="success" @click="openMaterial">素材库选择<i class="el-icon-circle-check el-icon--right"></i></el-button>
|
||||||
</el-col>
|
</el-col>
|
||||||
<el-col :span="12" class="col-add">
|
<el-col :span="12" class="col-add">
|
||||||
<el-upload
|
<el-upload :action="actionUrl" :headers="headers" multiple :limit="1" :on-success="handleUploadSuccess"
|
||||||
:action="actionUrl"
|
|
||||||
:headers="headers"
|
|
||||||
multiple
|
|
||||||
:limit="1"
|
|
||||||
:on-success="handleUploadSuccess"
|
|
||||||
:file-list="fileList"
|
:file-list="fileList"
|
||||||
:before-upload="beforeVoiceUpload"
|
:before-upload="beforeVoiceUpload"
|
||||||
:data="uploadData">
|
:data="uploadData">
|
||||||
@ -188,9 +171,9 @@
|
|||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
objData:{
|
objData:{
|
||||||
type:Object
|
type: Object
|
||||||
},
|
},
|
||||||
//图文类型:1、已发布图文;2、草稿箱图文
|
// 图文类型:1、已发布图文;2、草稿箱图文
|
||||||
newsType:{
|
newsType:{
|
||||||
type: String,
|
type: String,
|
||||||
default: "1"
|
default: "1"
|
||||||
|
Loading…
Reference in New Issue
Block a user