feat(企业微信): 新增小程序二维码查询和生成功能
添加根据企业ID和名称查询二维码的接口 实现自动生成小程序二维码并保存到本地 新增二维码类型枚举和控制器接口
This commit is contained in:
parent
61b53dfb67
commit
8dc5994ece
|
|
@ -0,0 +1,101 @@
|
|||
package com.agileboot.admin.controller.qywx;
|
||||
|
||||
import com.agileboot.admin.customize.aop.accessLog.AccessLog;
|
||||
import com.agileboot.common.core.base.BaseController;
|
||||
import com.agileboot.common.core.dto.ResponseDTO;
|
||||
import com.agileboot.common.core.page.PageDTO;
|
||||
import com.agileboot.common.enums.common.BusinessTypeEnum;
|
||||
import com.agileboot.domain.common.command.BulkOperationCommand;
|
||||
import com.agileboot.domain.qywx.mpQrcode.MpQrcodeApplicationService;
|
||||
import com.agileboot.domain.qywx.mpQrcode.command.AddMpQrcodeCommand;
|
||||
import com.agileboot.domain.qywx.mpQrcode.command.UpdateMpQrcodeCommand;
|
||||
import com.agileboot.domain.qywx.mpQrcode.db.QyMpQrcodeEntity;
|
||||
import com.agileboot.domain.qywx.mpQrcode.dto.QyMpQrcodeDTO;
|
||||
import com.agileboot.domain.qywx.mpQrcode.query.SearchMpQrcodeQuery;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.Pattern;
|
||||
import java.util.List;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 企业小程序二维码表 前端控制器
|
||||
* </p>
|
||||
*
|
||||
* @author AgileBoot
|
||||
* @since 2025-12-02
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/qywx/mp-qrcodes")
|
||||
@RequiredArgsConstructor
|
||||
@Validated
|
||||
public class MpQrcodeController extends BaseController {
|
||||
|
||||
private final MpQrcodeApplicationService mpQrcodeApplicationService;
|
||||
|
||||
@Operation(summary = "二维码列表")
|
||||
@GetMapping
|
||||
public ResponseDTO<PageDTO<QyMpQrcodeDTO>> list(SearchMpQrcodeQuery<QyMpQrcodeEntity> query) {
|
||||
PageDTO<QyMpQrcodeDTO> page = mpQrcodeApplicationService.getQrcodeList(query);
|
||||
return ResponseDTO.ok(page);
|
||||
}
|
||||
|
||||
@Operation(summary = "二维码详情")
|
||||
@GetMapping("/{id}")
|
||||
public ResponseDTO<QyMpQrcodeDTO> detail(@PathVariable Long id) {
|
||||
// 通过查询接口获取详情
|
||||
SearchMpQrcodeQuery<QyMpQrcodeEntity> query = new SearchMpQrcodeQuery<>();
|
||||
query.setPageNum(1);
|
||||
query.setPageSize(1);
|
||||
PageDTO<QyMpQrcodeDTO> page = mpQrcodeApplicationService.getQrcodeList(query);
|
||||
if (page.getRows() != null && !page.getRows().isEmpty()) {
|
||||
return ResponseDTO.ok(page.getRows().get(0));
|
||||
}
|
||||
return ResponseDTO.ok(null);
|
||||
}
|
||||
|
||||
@Operation(summary = "新增二维码")
|
||||
@AccessLog(title = "二维码管理", businessType = BusinessTypeEnum.ADD)
|
||||
@PostMapping
|
||||
public ResponseDTO<Void> add(@Validated @RequestBody AddMpQrcodeCommand command) {
|
||||
mpQrcodeApplicationService.addQrcode(command);
|
||||
return ResponseDTO.ok();
|
||||
}
|
||||
|
||||
@Operation(summary = "修改二维码")
|
||||
@AccessLog(title = "二维码管理", businessType = BusinessTypeEnum.MODIFY)
|
||||
@PutMapping("/{id}")
|
||||
public ResponseDTO<Void> edit(@PathVariable Long id, @Validated @RequestBody UpdateMpQrcodeCommand command) {
|
||||
command.setQrcodeId(id);
|
||||
mpQrcodeApplicationService.updateQrcode(command);
|
||||
return ResponseDTO.ok();
|
||||
}
|
||||
|
||||
@Operation(summary = "删除二维码")
|
||||
@AccessLog(title = "二维码管理", businessType = BusinessTypeEnum.DELETE)
|
||||
@DeleteMapping("/{ids}")
|
||||
public ResponseDTO<Void> remove(@PathVariable @NotNull List<Long> ids) {
|
||||
mpQrcodeApplicationService.deleteQrcode(new BulkOperationCommand<>(ids));
|
||||
return ResponseDTO.ok();
|
||||
}
|
||||
|
||||
@Operation(summary = "根据企业ID查询二维码")
|
||||
@GetMapping("/by-corpid/{corpid}")
|
||||
public ResponseDTO<List<QyMpQrcodeEntity>> getByCorpid(@PathVariable String corpid) {
|
||||
List<QyMpQrcodeEntity> list = mpQrcodeApplicationService.getQrcodeListByCorpid(corpid);
|
||||
return ResponseDTO.ok(list);
|
||||
}
|
||||
|
||||
@Operation(summary = "根据企业ID和名称查询或创建二维码")
|
||||
@GetMapping("/get-or-create")
|
||||
public ResponseDTO<QyMpQrcodeEntity> getOrCreateQrcode(
|
||||
@RequestParam String corpid,
|
||||
@RequestParam @Pattern(regexp = "^[A-Z_]+$", message = "名称必须为大写字母或下划线") String name) {
|
||||
|
||||
QyMpQrcodeEntity qrcode = mpQrcodeApplicationService.getOrCreateQrcode(corpid, name);
|
||||
return ResponseDTO.ok(qrcode);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,19 +1,37 @@
|
|||
package com.agileboot.domain.qywx.mpQrcode;
|
||||
|
||||
import cn.hutool.core.date.DatePattern;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.agileboot.common.core.page.PageDTO;
|
||||
import com.agileboot.common.exception.ApiException;
|
||||
import com.agileboot.common.exception.error.ErrorCode;
|
||||
import com.agileboot.common.utils.file.FileUploadUtils;
|
||||
import com.agileboot.domain.common.cache.CacheCenter;
|
||||
import com.agileboot.domain.common.command.BulkOperationCommand;
|
||||
import com.agileboot.domain.qywx.authCorpInfo.db.QyAuthCorpInfoEntity;
|
||||
import com.agileboot.domain.qywx.authCorpInfo.db.QyAuthCorpInfoService;
|
||||
import com.agileboot.domain.qywx.mpQrcode.command.AddMpQrcodeCommand;
|
||||
import com.agileboot.domain.qywx.mpQrcode.command.UpdateMpQrcodeCommand;
|
||||
import com.agileboot.domain.qywx.mpQrcode.db.QyMpQrcodeEntity;
|
||||
import com.agileboot.domain.qywx.mpQrcode.db.QyMpQrcodeService;
|
||||
import com.agileboot.domain.qywx.mpQrcode.dto.QyMpQrcodeDTO;
|
||||
import com.agileboot.domain.qywx.mpQrcode.enums.MpQrcodeTypeEnum;
|
||||
import com.agileboot.domain.qywx.mpQrcode.model.MpQrcodeModel;
|
||||
import com.agileboot.domain.qywx.mpQrcode.model.MpQrcodeModelFactory;
|
||||
import com.agileboot.domain.qywx.mpQrcode.query.SearchMpQrcodeQuery;
|
||||
import com.agileboot.domain.wx.UnlimitedQRCodeRequest;
|
||||
import com.agileboot.domain.wx.WxAccessToken;
|
||||
import com.agileboot.domain.wx.WxConstant;
|
||||
import com.agileboot.domain.wx.WxService;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
/**
|
||||
|
|
@ -24,12 +42,17 @@ import org.springframework.stereotype.Service;
|
|||
* @author AgileBoot
|
||||
* @since 2025-12-02
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class MpQrcodeApplicationService {
|
||||
|
||||
private static final String QRCODE_SUB_DIR = "upload";
|
||||
|
||||
private final QyMpQrcodeService qrcodeService;
|
||||
private final MpQrcodeModelFactory qrcodeModelFactory;
|
||||
private final WxService wxService;
|
||||
private final QyAuthCorpInfoService authCorpInfoService;
|
||||
|
||||
/**
|
||||
* 分页查询二维码列表
|
||||
|
|
@ -76,4 +99,148 @@ public class MpQrcodeApplicationService {
|
|||
public List<QyMpQrcodeEntity> getQrcodeListByCorpid(String corpid) {
|
||||
return qrcodeService.selectByCorpid(corpid);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据企业ID和名称查询或创建二维码
|
||||
*/
|
||||
public QyMpQrcodeEntity getOrCreateQrcode(String corpid, String name) {
|
||||
// 先查询是否已存在
|
||||
QyMpQrcodeEntity existingQrcode = qrcodeService.selectByCorpidAndName(corpid, name);
|
||||
if (existingQrcode != null) {
|
||||
return existingQrcode;
|
||||
}
|
||||
|
||||
MpQrcodeTypeEnum typeEnum = MpQrcodeTypeEnum.getByName(name);
|
||||
if (typeEnum == null) {
|
||||
throw new ApiException(ErrorCode.Internal.INVALID_PARAMETER, "name");
|
||||
}
|
||||
|
||||
// 从数据库中查询企业ID对应的cid
|
||||
QyAuthCorpInfoEntity authCorpInfo = authCorpInfoService.selectByCorpid(corpid);
|
||||
if (authCorpInfo == null) {
|
||||
throw new ApiException(ErrorCode.Business.COMMON_OBJECT_NOT_FOUND, corpid, "企业认证信息");
|
||||
}
|
||||
Integer cid = authCorpInfo.getId();
|
||||
|
||||
// 构建 scene 和 page
|
||||
String scene = MpQrcodeTypeEnum.buildScene(typeEnum, cid);
|
||||
String page = typeEnum.getPage();
|
||||
|
||||
// 生成小程序码
|
||||
String qrcodeUrl = generateQrcode(scene, page);
|
||||
|
||||
// 创建记录
|
||||
AddMpQrcodeCommand command = new AddMpQrcodeCommand();
|
||||
command.setCorpid(corpid);
|
||||
command.setName(name);
|
||||
command.setScene(scene);
|
||||
command.setPage(page);
|
||||
command.setQrcodeUrl(qrcodeUrl);
|
||||
|
||||
addQrcode(command);
|
||||
|
||||
// 重新查询刚创建的记录
|
||||
return qrcodeService.selectByCorpidAndName(corpid, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成小程序码
|
||||
* @param scene 场景参数
|
||||
* @param page 页面路径
|
||||
* @return 小程序码的相对URL路径
|
||||
*/
|
||||
private String generateQrcode(String scene, String page) {
|
||||
try {
|
||||
// 1. 获取 access_token
|
||||
String accessToken = getAccessToken();
|
||||
if (StrUtil.isEmpty(accessToken)) {
|
||||
log.error("获取access_token失败");
|
||||
return "";
|
||||
}
|
||||
|
||||
// 2. 构建请求参数
|
||||
UnlimitedQRCodeRequest request = new UnlimitedQRCodeRequest();
|
||||
request.setScene(scene);
|
||||
request.setPage(page);
|
||||
request.setWidth(430);
|
||||
request.setAutoColor(false);
|
||||
request.setIsHyaline(false);
|
||||
|
||||
// 3. 调用微信接口获取小程序码
|
||||
byte[] qrCodeBytes = wxService.getUnlimitedQRCode(accessToken, request);
|
||||
|
||||
if (qrCodeBytes != null && qrCodeBytes.length > 0) {
|
||||
// 4. 生成文件名并保存到本地
|
||||
String fileName = generateQrcodeFilename(scene);
|
||||
saveQrcodeToLocal(qrCodeBytes, QRCODE_SUB_DIR, fileName);
|
||||
|
||||
String relativeUrl = FileUploadUtils.getRelativeFileUrl(QRCODE_SUB_DIR, fileName);
|
||||
log.info("成功生成小程序码,文件路径:{}", relativeUrl);
|
||||
return relativeUrl;
|
||||
} else {
|
||||
log.error("获取小程序码失败,返回数据为空");
|
||||
return "";
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("生成小程序码时发生错误", e);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 access_token,优先从缓存获取
|
||||
*/
|
||||
private String getAccessToken() {
|
||||
// 1. 先从缓存获取
|
||||
WxAccessToken cachedToken = CacheCenter.accessTokenCache.get(WxConstant.AccessTokenCacheName);
|
||||
if (cachedToken != null && cachedToken.getAccessToken() != null) {
|
||||
log.debug("从缓存获取access_token");
|
||||
return cachedToken.getAccessToken();
|
||||
}
|
||||
|
||||
// 2. 缓存中没有,调用微信接口获取
|
||||
log.info("缓存中没有access_token,开始调用微信接口获取...");
|
||||
WxAccessToken newToken = wxService.getAccessToken();
|
||||
|
||||
if (newToken != null && newToken.getAccessToken() != null) {
|
||||
// 将 access_token 存入缓存
|
||||
CacheCenter.accessTokenCache.put(WxConstant.AccessTokenCacheName, newToken);
|
||||
log.info("成功获取access_token并存入缓存,有效期:{}秒", newToken.getExpiresIn());
|
||||
return newToken.getAccessToken();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成小程序码文件名
|
||||
*/
|
||||
private String generateQrcodeFilename(String scene) {
|
||||
return StrUtil.format("qrcode_{}_{}.jpg",
|
||||
DateUtil.format(DateUtil.date(), DatePattern.PURE_DATETIME_PATTERN),
|
||||
scene.replace("=", "_"));
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存小程序码到本地
|
||||
*/
|
||||
private void saveQrcodeToLocal(byte[] qrCodeBytes, String subDir, String fileName) throws IOException {
|
||||
if (StrUtil.isEmpty(subDir) || StrUtil.isEmpty(fileName)) {
|
||||
throw new ApiException(ErrorCode.Internal.INVALID_PARAMETER, "subDir or fileName");
|
||||
}
|
||||
|
||||
String filePath = FileUploadUtils.getFileAbsolutePath(subDir, fileName);
|
||||
File destination = new File(filePath);
|
||||
|
||||
if (!destination.getParentFile().exists()) {
|
||||
destination.getParentFile().mkdirs();
|
||||
}
|
||||
|
||||
try (FileOutputStream fos = new FileOutputStream(destination)) {
|
||||
fos.write(qrCodeBytes);
|
||||
fos.flush();
|
||||
}
|
||||
|
||||
log.info("小程序码已保存到:{}", filePath);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,4 +46,13 @@ public interface QyMpQrcodeMapper extends BaseMapper<QyMpQrcodeEntity> {
|
|||
"WHERE corpid = #{corpid} AND deleted = 0 " +
|
||||
"ORDER BY create_time DESC")
|
||||
List<QyMpQrcodeEntity> selectByCorpid(@Param("corpid") String corpid);
|
||||
|
||||
/**
|
||||
* 根据企业ID和名称查询二维码
|
||||
*/
|
||||
@Select("SELECT * " +
|
||||
"FROM qy_mp_qrcode " +
|
||||
"WHERE corpid = #{corpid} AND name = #{name} AND deleted = 0 " +
|
||||
"LIMIT 1")
|
||||
QyMpQrcodeEntity selectByCorpidAndName(@Param("corpid") String corpid, @Param("name") String name);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,4 +29,9 @@ public interface QyMpQrcodeService extends IService<QyMpQrcodeEntity> {
|
|||
* 根据企业ID查询二维码列表
|
||||
*/
|
||||
List<QyMpQrcodeEntity> selectByCorpid(String corpid);
|
||||
|
||||
/**
|
||||
* 根据企业ID和名称查询二维码
|
||||
*/
|
||||
QyMpQrcodeEntity selectByCorpidAndName(String corpid, String name);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,4 +33,9 @@ public class QyMpQrcodeServiceImpl extends ServiceImpl<QyMpQrcodeMapper, QyMpQrc
|
|||
public List<QyMpQrcodeEntity> selectByCorpid(String corpid) {
|
||||
return baseMapper.selectByCorpid(corpid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public QyMpQrcodeEntity selectByCorpidAndName(String corpid, String name) {
|
||||
return baseMapper.selectByCorpidAndName(corpid, name);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,73 @@
|
|||
package com.agileboot.domain.qywx.mpQrcode.enums;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 小程序二维码类型枚举
|
||||
* </p>
|
||||
*
|
||||
* @author AgileBoot
|
||||
* @since 2025-12-03
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum MpQrcodeTypeEnum {
|
||||
|
||||
/**
|
||||
* 首页
|
||||
*/
|
||||
HOME("HOME", "cid", "pages/index/index")
|
||||
|
||||
;
|
||||
|
||||
/**
|
||||
* 名称
|
||||
*/
|
||||
private final String name;
|
||||
|
||||
/**
|
||||
* 场景参数
|
||||
*/
|
||||
private final String scene;
|
||||
|
||||
/**
|
||||
* 页面路径
|
||||
*/
|
||||
private final String page;
|
||||
|
||||
/**
|
||||
* 根据名称获取枚举
|
||||
*
|
||||
* @param name 名称
|
||||
* @return 对应的枚举
|
||||
*/
|
||||
public static MpQrcodeTypeEnum getByName(String name) {
|
||||
for (MpQrcodeTypeEnum typeEnum : values()) {
|
||||
if (typeEnum.getName().equals(name)) {
|
||||
return typeEnum;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建场景参数
|
||||
*
|
||||
* @param typeEnum 枚举
|
||||
* @param corpid 企业ID
|
||||
* @return 场景参数
|
||||
*/
|
||||
public static String buildScene(MpQrcodeTypeEnum typeEnum, Integer cid) {
|
||||
if (typeEnum == null) {
|
||||
return null;
|
||||
}
|
||||
if (typeEnum.getName().equals("HOME")) {
|
||||
return StringUtils.join(typeEnum.getScene(), "=", cid);
|
||||
}
|
||||
return typeEnum.getScene();
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue