diff --git a/yudao-module-mp/yudao-module-mp-api/src/main/java/cn/iocoder/yudao/module/mp/enums/ErrorCodeConstants.java b/yudao-module-mp/yudao-module-mp-api/src/main/java/cn/iocoder/yudao/module/mp/enums/ErrorCodeConstants.java index 66e8ddd43..1d5c0962e 100644 --- a/yudao-module-mp/yudao-module-mp-api/src/main/java/cn/iocoder/yudao/module/mp/enums/ErrorCodeConstants.java +++ b/yudao-module-mp/yudao-module-mp-api/src/main/java/cn/iocoder/yudao/module/mp/enums/ErrorCodeConstants.java @@ -27,11 +27,14 @@ public interface ErrorCodeConstants { ErrorCode TAG_DELETE_FAIL = new ErrorCode(1006002001, "删除标签失败,原因:{}"); ErrorCode TAG_GET_FAIL = new ErrorCode(1006002001, "获得标签失败,原因:{}"); - // ========== 公众号标签 1006003000============ + // ========== 公众号粉丝 1006003000============ ErrorCode USER_NOT_EXISTS = new ErrorCode(1006003000, "用户不存在"); ErrorCode USER_UPDATE_TAG_FAIL = new ErrorCode(1006003001, "更新用户标签失败,原因:{}"); + // ========== 公众号素材 1006004000============ + ErrorCode MATERIAL_UPLOAD_FAIL = new ErrorCode(1006004000, "上传素材失败,原因:{}"); + // TODO 要处理下 - ErrorCode COMMON_NOT_EXISTS = new ErrorCode(1006001002, "用户不存在"); + ErrorCode MENU_NOT_EXISTS = new ErrorCode(1006001002, "菜单不存在"); } diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/MpMaterialController.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/MpMaterialController.java index c10f81856..2b07d3625 100644 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/MpMaterialController.java +++ b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/MpMaterialController.java @@ -1,6 +1,7 @@ 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.MpMaterialUploadPermanentReqVO; 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; @@ -36,4 +37,12 @@ public class MpMaterialController { return success(MpMaterialConvert.INSTANCE.convert(material)); } + @ApiOperation("上传永久素材") + @PostMapping("/upload-permanent") + public CommonResult uploadPermanentMaterial( + @Valid MpMaterialUploadPermanentReqVO reqVO) throws IOException { + MpMaterialDO material = mpMaterialService.uploadPermanentMaterial(reqVO); + return success(MpMaterialConvert.INSTANCE.convert(material)); + } + } diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/vo/MpMaterialUploadPermanentReqVO.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/vo/MpMaterialUploadPermanentReqVO.java new file mode 100644 index 000000000..706f71351 --- /dev/null +++ b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/controller/admin/material/vo/MpMaterialUploadPermanentReqVO.java @@ -0,0 +1,54 @@ +package cn.iocoder.yudao.module.mp.controller.admin.material.vo; + +import cn.hutool.core.util.ObjectUtil; +import com.fasterxml.jackson.annotation.JsonIgnore; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import me.chanjar.weixin.common.api.WxConsts; +import org.springframework.web.multipart.MultipartFile; + +import javax.validation.constraints.AssertTrue; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; + +@ApiModel("管理后台 - 公众号素材上传永久 Request VO") +@Data +public class MpMaterialUploadPermanentReqVO { + + @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; + + @ApiModelProperty(value = "名字", example = "wechat.mp", notes = "如果 name 为空,则使用 file 文件名") + private String name; + + @ApiModelProperty(value = "视频素材的标题", example = "视频素材的标题", notes = "文件类型为 video 时,必填") + private String title; + @ApiModelProperty(value = "视频素材的描述", example = "视频素材的描述", notes = "文件类型为 video 时,必填") + private String introduction; + + @AssertTrue(message = "标题不能为空") + public boolean isTitleValid() { + // 生成场景为管理后台时,必须设置上级菜单,不然生成的菜单 SQL 是无父级菜单的 + return ObjectUtil.notEqual(type, WxConsts.MediaFileType.VIDEO) + || title != null; + } + + @AssertTrue(message = "描述不能为空") + public boolean isIntroductionValid() { + // 生成场景为管理后台时,必须设置上级菜单,不然生成的菜单 SQL 是无父级菜单的 + return ObjectUtil.notEqual(type, WxConsts.MediaFileType.VIDEO) + || introduction != null; + } + +} diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/material/MpMaterialConvert.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/material/MpMaterialConvert.java index c6215aa02..523b51fd0 100644 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/material/MpMaterialConvert.java +++ b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/convert/material/MpMaterialConvert.java @@ -3,11 +3,14 @@ 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 me.chanjar.weixin.mp.bean.material.WxMpMaterial; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.Mappings; import org.mapstruct.factory.Mappers; +import java.io.File; + @Mapper public interface MpMaterialConvert { @@ -20,6 +23,19 @@ public interface MpMaterialConvert { }) MpMaterialDO convert(String mediaId, String type, String url, MpAccountDO account); + @Mappings({ + @Mapping(target = "id", ignore = true), + @Mapping(source = "account.id", target = "accountId"), + @Mapping(source = "account.appId", target = "appId"), + @Mapping(source = "name", target = "name") + }) + MpMaterialDO convert(String mediaId, String type, String url, MpAccountDO account, + String name, String title, String introduction, String mpUrl); + MpMaterialUploadRespVO convert(MpMaterialDO bean); + default WxMpMaterial convert(String name, File file, String title, String introduction) { + return new WxMpMaterial(name, file, title, introduction); + } + } diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/material/MpMaterialService.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/material/MpMaterialService.java index 56d9b645a..ec7023af0 100644 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/material/MpMaterialService.java +++ b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/material/MpMaterialService.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.mp.service.material; +import cn.iocoder.yudao.module.mp.controller.admin.material.vo.MpMaterialUploadPermanentReqVO; 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; @@ -26,5 +27,22 @@ public interface MpMaterialService { */ String downloadMaterialUrl(Long accountId, String mediaId, String type); + /** + * 上传临时素材 + * + * @param reqVO 请求 + * @return 素材 + * @throws IOException 文件操作发生异常 + */ MpMaterialDO uploadTemporaryMaterial(@Valid MpMaterialUploadTemporaryReqVO reqVO) throws IOException; + + /** + * 上传永久素材 + * + * @param reqVO 请求 + * @return 素材 + * @throws IOException 文件操作发生异常 + */ + MpMaterialDO uploadPermanentMaterial(@Valid MpMaterialUploadPermanentReqVO reqVO) throws IOException; + } diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/material/MpMaterialServiceImpl.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/material/MpMaterialServiceImpl.java index ff6885f6e..371638e64 100644 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/material/MpMaterialServiceImpl.java +++ b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/material/MpMaterialServiceImpl.java @@ -3,7 +3,9 @@ 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.hutool.core.util.StrUtil; import cn.iocoder.yudao.module.infra.api.file.FileApi; +import cn.iocoder.yudao.module.mp.controller.admin.material.vo.MpMaterialUploadPermanentReqVO; 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; @@ -15,6 +17,7 @@ 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 me.chanjar.weixin.mp.bean.material.WxMpMaterialUploadResult; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; @@ -23,6 +26,9 @@ import javax.annotation.Resource; import java.io.File; import java.io.IOException; +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants.MATERIAL_UPLOAD_FAIL; + /** * 公众号素材 Service 接口 * @@ -87,8 +93,7 @@ public class MpMaterialServiceImpl implements MpMaterialService { mediaId = ObjUtil.defaultIfNull(result.getMediaId(), result.getThumbMediaId()); url = uploadFile(mediaId, file); } catch (WxErrorException e) { - // TODO yunai:待完善 - throw new RuntimeException(e); + throw exception(MATERIAL_UPLOAD_FAIL, e.getError().getErrorMsg()); } finally { FileUtil.del(file); } @@ -101,6 +106,39 @@ public class MpMaterialServiceImpl implements MpMaterialService { return material; } + @Override + public MpMaterialDO uploadPermanentMaterial(MpMaterialUploadPermanentReqVO reqVO) throws IOException { + WxMpService mpService = mpServiceFactory.getRequiredMpService(reqVO.getAccountId()); + // 第一步,上传到公众号 + String name = StrUtil.blankToDefault(reqVO.getName(), reqVO.getFile().getName()); + File file = null; + WxMpMaterialUploadResult result; + String mediaId; + String url; + try { + // 写入到临时文件 + file = FileUtil.newFile(FileUtil.getTmpDirPath() + reqVO.getFile().getOriginalFilename()); + reqVO.getFile().transferTo(file); + // 上传到公众号 + result = mpService.getMaterialService().materialFileUpload(reqVO.getType(), + MpMaterialConvert.INSTANCE.convert(name, file, reqVO.getTitle(), reqVO.getIntroduction())); + // 上传到文件服务 + mediaId = ObjUtil.defaultIfNull(result.getMediaId(), result.getMediaId()); + url = uploadFile(mediaId, file); + } catch (WxErrorException e) { + throw exception(MATERIAL_UPLOAD_FAIL, e.getError().getErrorMsg()); + } finally { + FileUtil.del(file); + } + + // 第二步,存储到数据库 + MpAccountDO account = mpAccountService.getRequiredAccount(reqVO.getAccountId()); + MpMaterialDO material = MpMaterialConvert.INSTANCE.convert(mediaId, reqVO.getType(), url, account, + name, reqVO.getTitle(), reqVO.getIntroduction(), result.getUrl()).setPermanent(true); + mpMaterialMapper.insert(material); + return material; + } + /** * 下载微信媒体文件的内容,并上传到文件服务 * diff --git a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/menu/MpMenuServiceImpl.java b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/menu/MpMenuServiceImpl.java index 67242f446..e9a5c9b7f 100644 --- a/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/menu/MpMenuServiceImpl.java +++ b/yudao-module-mp/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/menu/MpMenuServiceImpl.java @@ -77,7 +77,7 @@ public class MpMenuServiceImpl implements MpMenuService { private void validateMenuExists(Long id) { if (mpMenuMapper.selectById(id) == null) { // TODO 芋艿:错误码不太对 - throw exception(COMMON_NOT_EXISTS); + throw exception(MENU_NOT_EXISTS); } } diff --git a/yudao-ui-admin/src/views/mp/components/wx-material-select/main.vue b/yudao-ui-admin/src/views/mp/components/wx-material-select/main.vue new file mode 100644 index 000000000..7f5fd32a9 --- /dev/null +++ b/yudao-ui-admin/src/views/mp/components/wx-material-select/main.vue @@ -0,0 +1,255 @@ + + + + + +