diff --git a/agileboot-admin/src/main/java/com/agileboot/admin/controller/qywx/MpQrcodeController.java b/agileboot-admin/src/main/java/com/agileboot/admin/controller/qywx/MpQrcodeController.java
new file mode 100644
index 0000000..3090202
--- /dev/null
+++ b/agileboot-admin/src/main/java/com/agileboot/admin/controller/qywx/MpQrcodeController.java
@@ -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.*;
+
+/**
+ *
+ * 企业小程序二维码表 前端控制器
+ *
+ *
+ * @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> list(SearchMpQrcodeQuery query) {
+ PageDTO page = mpQrcodeApplicationService.getQrcodeList(query);
+ return ResponseDTO.ok(page);
+ }
+
+ @Operation(summary = "二维码详情")
+ @GetMapping("/{id}")
+ public ResponseDTO detail(@PathVariable Long id) {
+ // 通过查询接口获取详情
+ SearchMpQrcodeQuery query = new SearchMpQrcodeQuery<>();
+ query.setPageNum(1);
+ query.setPageSize(1);
+ PageDTO 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 add(@Validated @RequestBody AddMpQrcodeCommand command) {
+ mpQrcodeApplicationService.addQrcode(command);
+ return ResponseDTO.ok();
+ }
+
+ @Operation(summary = "修改二维码")
+ @AccessLog(title = "二维码管理", businessType = BusinessTypeEnum.MODIFY)
+ @PutMapping("/{id}")
+ public ResponseDTO 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 remove(@PathVariable @NotNull List ids) {
+ mpQrcodeApplicationService.deleteQrcode(new BulkOperationCommand<>(ids));
+ return ResponseDTO.ok();
+ }
+
+ @Operation(summary = "根据企业ID查询二维码")
+ @GetMapping("/by-corpid/{corpid}")
+ public ResponseDTO> getByCorpid(@PathVariable String corpid) {
+ List list = mpQrcodeApplicationService.getQrcodeListByCorpid(corpid);
+ return ResponseDTO.ok(list);
+ }
+
+ @Operation(summary = "根据企业ID和名称查询或创建二维码")
+ @GetMapping("/get-or-create")
+ public ResponseDTO getOrCreateQrcode(
+ @RequestParam String corpid,
+ @RequestParam @Pattern(regexp = "^[A-Z_]+$", message = "名称必须为大写字母或下划线") String name) {
+
+ QyMpQrcodeEntity qrcode = mpQrcodeApplicationService.getOrCreateQrcode(corpid, name);
+ return ResponseDTO.ok(qrcode);
+ }
+}
diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/mpQrcode/MpQrcodeApplicationService.java b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/mpQrcode/MpQrcodeApplicationService.java
index d6997fc..1681f46 100644
--- a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/mpQrcode/MpQrcodeApplicationService.java
+++ b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/mpQrcode/MpQrcodeApplicationService.java
@@ -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 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);
+ }
}
diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/mpQrcode/db/QyMpQrcodeMapper.java b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/mpQrcode/db/QyMpQrcodeMapper.java
index 6a73f4d..91ac167 100644
--- a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/mpQrcode/db/QyMpQrcodeMapper.java
+++ b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/mpQrcode/db/QyMpQrcodeMapper.java
@@ -46,4 +46,13 @@ public interface QyMpQrcodeMapper extends BaseMapper {
"WHERE corpid = #{corpid} AND deleted = 0 " +
"ORDER BY create_time DESC")
List 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);
}
diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/mpQrcode/db/QyMpQrcodeService.java b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/mpQrcode/db/QyMpQrcodeService.java
index b1e587a..a7654f5 100644
--- a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/mpQrcode/db/QyMpQrcodeService.java
+++ b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/mpQrcode/db/QyMpQrcodeService.java
@@ -29,4 +29,9 @@ public interface QyMpQrcodeService extends IService {
* 根据企业ID查询二维码列表
*/
List selectByCorpid(String corpid);
+
+ /**
+ * 根据企业ID和名称查询二维码
+ */
+ QyMpQrcodeEntity selectByCorpidAndName(String corpid, String name);
}
diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/mpQrcode/db/QyMpQrcodeServiceImpl.java b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/mpQrcode/db/QyMpQrcodeServiceImpl.java
index 0e2464e..abb1e52 100644
--- a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/mpQrcode/db/QyMpQrcodeServiceImpl.java
+++ b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/mpQrcode/db/QyMpQrcodeServiceImpl.java
@@ -33,4 +33,9 @@ public class QyMpQrcodeServiceImpl extends ServiceImpl selectByCorpid(String corpid) {
return baseMapper.selectByCorpid(corpid);
}
+
+ @Override
+ public QyMpQrcodeEntity selectByCorpidAndName(String corpid, String name) {
+ return baseMapper.selectByCorpidAndName(corpid, name);
+ }
}
diff --git a/agileboot-domain/src/main/java/com/agileboot/domain/qywx/mpQrcode/enums/MpQrcodeTypeEnum.java b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/mpQrcode/enums/MpQrcodeTypeEnum.java
new file mode 100644
index 0000000..bd2048e
--- /dev/null
+++ b/agileboot-domain/src/main/java/com/agileboot/domain/qywx/mpQrcode/enums/MpQrcodeTypeEnum.java
@@ -0,0 +1,73 @@
+package com.agileboot.domain.qywx.mpQrcode.enums;
+
+import org.apache.commons.lang3.StringUtils;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ *
+ * 小程序二维码类型枚举
+ *
+ *
+ * @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();
+ }
+}