Procházet zdrojové kódy

添加群二微码

hxl13994548489 před 7 měsíci
rodič
revize
c4b2bbcec8

+ 34 - 0
linkwe-auth/src/main/java/com/linkwechat/web/controller/system/WeGroupFissionController.java

@@ -5,6 +5,7 @@ import com.linkwechat.common.annotation.InnerAuth;
 import com.linkwechat.common.core.controller.BaseController;
 import com.linkwechat.common.core.controller.BaseController;
 import com.linkwechat.common.core.domain.AjaxResult;
 import com.linkwechat.common.core.domain.AjaxResult;
 import com.linkwechat.common.core.page.TableDataInfo;
 import com.linkwechat.common.core.page.TableDataInfo;
+import com.linkwechat.common.utils.QrcodeUtils;
 import com.linkwechat.domain.weGroupFission.dto.DistDto;
 import com.linkwechat.domain.weGroupFission.dto.DistDto;
 import com.linkwechat.domain.weGroupFission.dto.WeGroupFissionDetailDto;
 import com.linkwechat.domain.weGroupFission.dto.WeGroupFissionDetailDto;
 import com.linkwechat.domain.weGroupFission.dto.WeGroupFissionDto;
 import com.linkwechat.domain.weGroupFission.dto.WeGroupFissionDto;
@@ -17,8 +18,11 @@ import com.linkwechat.web.service.ISysUserService;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import io.swagger.annotations.ApiOperation;
 import lombok.extern.slf4j.Slf4j;
 import lombok.extern.slf4j.Slf4j;
+import me.chanjar.weixin.common.error.WxErrorException;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.bind.annotation.*;
 
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletRequest;
@@ -196,4 +200,34 @@ public class WeGroupFissionController extends BaseController {
             return AjaxResult.error(e.getMessage());
             return AjaxResult.error(e.getMessage());
         }
         }
     }
     }
+
+    /**
+     *  @version
+     *  @author: hxl
+     *  @Date: 2026/2/9 8:34
+     *  @Description: 获取外部群二维码的链接
+     */
+    @GetMapping(value = "/getChartQrCodeByChatId")
+    public AjaxResult getChartQrCodeByChatId(@RequestParam(name="chatId",required=true) String chatId,@RequestParam(name="activityId",required=true) String activityId) throws WxErrorException {
+        String qrCode =  weGroupFissionService.getChartQrCodeByChatId(chatId, activityId);
+        return AjaxResult.success(qrCode);
+    }
+
+    /**
+     *  @version
+     *  @author: hxl
+     *  @Date: 2026/2/9 8:34
+     *  @Description: 获取外部群二维码的链接二进制流
+     */
+    @GetMapping(value = "/getChartQrCodeByChatIdStream")
+    public ResponseEntity<byte[]> getChartQrCodeByChatIdStream(@RequestParam(name="chatId",required=true) String chatId, @RequestParam(name="activityId",required=true) String activityId){
+        try {
+            String qrCode =  weGroupFissionService.getChartQrCodeByChatId(chatId, activityId);
+            byte[] qrcodeBytes = QrcodeUtils.qrcodeUrlToBytes(qrCode);
+            return QrcodeUtils.returnQrcodeToFront(qrcodeBytes);
+        } catch (Exception  e) {
+            e.printStackTrace();
+            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("二维码获取失败:".getBytes());
+        }
+    }
 }
 }

+ 90 - 0
linkwe-common/src/main/java/com/linkwechat/common/utils/QrcodeUtils.java

@@ -0,0 +1,90 @@
+package com.linkwechat.common.utils;
+
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Response;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.util.FileCopyUtils;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.concurrent.TimeUnit;
+
+/**
+ *  @version
+ *  @author: hxl
+ *  @Date: 2026/2/11 15:53
+ *  @Description: 企微二微码生成二进制流
+ */
+public class QrcodeUtils {
+    // 初始化 OkHttp 客户端(全局单例,避免重复创建)
+    private static final OkHttpClient OK_HTTP_CLIENT = new OkHttpClient.Builder()
+            .connectTimeout(10, TimeUnit.SECONDS)
+            .readTimeout(10, TimeUnit.SECONDS)
+            .writeTimeout(10, TimeUnit.SECONDS)
+            .build();
+
+    /**
+     * 核心方法:企微二维码URL转二进制流(字节数组)
+     * @param qrcodeUrl 企微返回的二维码链接(如 https://p.qlogo.cn/xxx/)
+     * @return 二维码图片的二进制字节数组
+     * @throws IOException 网络请求/流读取异常
+     */
+    public static byte[] qrcodeUrlToBytes(String qrcodeUrl) throws IOException {
+        // 1. 构建 HTTP 请求
+        Request request = new Request.Builder()
+                .url(qrcodeUrl)
+                .get()
+                // 模拟浏览器请求,避免企微接口拦截
+                .addHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36")
+                .build();
+
+        // 2. 发送请求,获取响应流
+        try (Response response = OK_HTTP_CLIENT.newCall(request).execute()) {
+            if (!response.isSuccessful()) {
+                throw new IOException("企微二维码链接请求失败:" + response.code() + " " + response.message());
+            }
+            // 3. 将响应体(图片流)转换成字节数组
+            try (InputStream inputStream = response.body().byteStream()) {
+                return FileCopyUtils.copyToByteArray(inputStream);
+            }
+        }
+    }
+
+    /**
+     * 二进制流返回给前端(Spring Boot 接口专用)
+     * @param qrcodeBytes 二维码二进制数组
+     * @return ResponseEntity<byte[]> 直接返回图片流,前端可直接显示
+     */
+    public static ResponseEntity<byte[]> returnQrcodeToFront(byte[] qrcodeBytes) {
+        HttpHeaders headers = new HttpHeaders();
+        // 设置响应类型为图片(企微二维码多为 PNG/JPG)
+        headers.setContentType(MediaType.IMAGE_PNG);
+        // 可选:设置文件名,前端下载时使用
+        // headers.setContentDispositionFormData("attachment", "qrcode.png");
+        return new ResponseEntity<>(qrcodeBytes, headers, HttpStatus.OK);
+    }
+
+    /**
+     * 二进制流保存为本地文件(调试/存储用)
+     * @param qrcodeBytes 二维码二进制数组
+     * @param savePath 保存路径(如 D:/qrcode.png)
+     * @throws IOException 文件写入异常
+     */
+    public static void saveQrcodeToFile(byte[] qrcodeBytes, String savePath) throws IOException {
+        File file = new File(savePath);
+        // 确保父目录存在
+        if (!file.getParentFile().exists()) {
+            file.getParentFile().mkdirs();
+        }
+        try (FileOutputStream fos = new FileOutputStream(file)) {
+            fos.write(qrcodeBytes);
+            fos.flush();
+        }
+    }
+}

+ 47 - 0
linkwe-service/src/main/java/com/linkwechat/domain/weGroupFission/entity/WeGroupFissionQrCodeRecord.java

@@ -0,0 +1,47 @@
+package com.linkwechat.domain.weGroupFission.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.util.Date;
+
+/**
+ * @Description: 用戶专属链接二维码
+ * @Author: jeecg-boot
+ * @Date:   2026-02-10
+ * @Version: V1.0
+ */
+@Data
+@TableName("we_group_fission_qr_code_record")
+@EqualsAndHashCode(callSuper = false)
+@Accessors(chain = true)
+@ApiModel(value="we_group_fission_qr_code_record对象", description="用戶专属链接二维码")
+public class WeGroupFissionQrCodeRecord {
+    
+	/**主键*/
+	@TableId(type = IdType.ASSIGN_ID)
+    @ApiModelProperty(value = "主键")
+	private String id;
+	/**群裂变活动id*/
+    @ApiModelProperty(value = "群裂变活动id")
+	private String groupFissionId;
+	/**分享人id*/
+    @ApiModelProperty(value = "分享人id")
+	private String shareUserId;
+	/**创建时间*/
+	@JsonFormat(timezone = "GMT+8",pattern = "yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
+    @ApiModelProperty(value = "创建时间")
+	private Date createTime;
+	/**二微码专属链接*/
+    @ApiModelProperty(value = "二微码专属链接")
+	private String qrCode;
+}

+ 15 - 0
linkwe-service/src/main/java/com/linkwechat/mapper/WeGroupFissionQrCodeRecordMapper.java

@@ -0,0 +1,15 @@
+package com.linkwechat.mapper;
+
+import com.baomidou.dynamic.datasource.annotation.DS;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.linkwechat.domain.weGroupFission.entity.WeGroupFissionQrCodeRecord;
+
+/**
+ * @Description: 用戶专属链接二维码
+ * @Author: jeecg-boot
+ * @Date:   2026-02-10
+ * @Version: V1.0
+ */
+public interface WeGroupFissionQrCodeRecordMapper extends BaseMapper<WeGroupFissionQrCodeRecord> {
+
+}

+ 21 - 0
linkwe-service/src/main/java/com/linkwechat/service/IWeGroupFissionQrCodeRecordService.java

@@ -0,0 +1,21 @@
+package com.linkwechat.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.linkwechat.domain.weGroupFission.entity.WeGroupFissionQrCodeRecord;
+
+
+/**
+ * @Description: 用戶专属链接二维码
+ * @Author: jeecg-boot
+ * @Date:   2026-02-10
+ * @Version: V1.0
+ */
+public interface IWeGroupFissionQrCodeRecordService extends IService<WeGroupFissionQrCodeRecord> {
+    /**
+     *  @version
+     *  @author: hxl
+     *  @Date: 2026/2/10 16:02
+     *  @Description: 通过参数查询唯一数据
+     */
+    WeGroupFissionQrCodeRecord findEntityByActivityIdAndShareId(String activityId, String shareId);
+}

+ 7 - 0
linkwe-service/src/main/java/com/linkwechat/service/IWeGroupFissionService.java

@@ -96,4 +96,11 @@ public interface IWeGroupFissionService extends IService<WeGroupFission> {
      *  @Description: 查询负责人列表
      *  @Description: 查询负责人列表
      */
      */
     List<DistDto> getUserDictDtos();
     List<DistDto> getUserDictDtos();
+    /**
+     *  @version
+     *  @author: hxl
+     *  @Date: 2026/2/11 17:25
+     *  @Description: 获取群裂变二维码链接
+     */
+    String getChartQrCodeByChatId(String chatId, String activityId);
 }
 }

+ 40 - 0
linkwe-service/src/main/java/com/linkwechat/service/impl/WeGroupFissionQrCodeRecordServiceImpl.java

@@ -0,0 +1,40 @@
+package com.linkwechat.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.linkwechat.domain.weGroupFission.entity.WeGroupFissionQrCodeRecord;
+import com.linkwechat.mapper.WeGroupFissionQrCodeRecordMapper;
+import com.linkwechat.service.IWeGroupFissionQrCodeRecordService;
+import org.springframework.stereotype.Service;
+
+import java.util.Date;
+
+/**
+ * @Description: 用戶专属链接二维码
+ * @Author: jeecg-boot
+ * @Date:   2026-02-10
+ * @Version: V1.0
+ */
+@Service
+public class WeGroupFissionQrCodeRecordServiceImpl extends ServiceImpl<WeGroupFissionQrCodeRecordMapper, WeGroupFissionQrCodeRecord> implements IWeGroupFissionQrCodeRecordService {
+
+    /**
+     *  @version
+     *  @author: hxl
+     *  @Date: 2026/2/10 16:03
+     *  @Description: 查询唯一数据
+     */
+    @Override
+    public WeGroupFissionQrCodeRecord findEntityByActivityIdAndShareId(String groupFissionId, String shareId) {
+        WeGroupFissionQrCodeRecord one = this.getOne(new QueryWrapper<WeGroupFissionQrCodeRecord>()
+                .eq("share_user_id", shareId).eq("group_fission_id", groupFissionId));
+        if(one!=null){
+            return one;
+        }
+        WeGroupFissionQrCodeRecord record =new WeGroupFissionQrCodeRecord()
+                .setGroupFissionId(groupFissionId).setShareUserId(shareId)
+                .setCreateTime(new Date());
+        this.save(record);
+        return record;
+    }
+}

+ 72 - 4
linkwe-service/src/main/java/com/linkwechat/service/impl/WeGroupFissionServiceImpl.java

@@ -22,10 +22,7 @@ import com.linkwechat.domain.weGroupFission.bdsh.LotteryActivity;
 import com.linkwechat.domain.weGroupFission.dto.DistDto;
 import com.linkwechat.domain.weGroupFission.dto.DistDto;
 import com.linkwechat.domain.weGroupFission.dto.WeGroupFissionDetailDto;
 import com.linkwechat.domain.weGroupFission.dto.WeGroupFissionDetailDto;
 import com.linkwechat.domain.weGroupFission.dto.WeGroupFissionDto;
 import com.linkwechat.domain.weGroupFission.dto.WeGroupFissionDto;
-import com.linkwechat.domain.weGroupFission.entity.WeExternalChat;
-import com.linkwechat.domain.weGroupFission.entity.WeGroupFission;
-import com.linkwechat.domain.weGroupFission.entity.WeGroupFissionExtraRecord;
-import com.linkwechat.domain.weGroupFission.entity.WeGroupFissionStepRecord;
+import com.linkwechat.domain.weGroupFission.entity.*;
 import com.linkwechat.domain.weGroupFission.vo.*;
 import com.linkwechat.domain.weGroupFission.vo.*;
 import com.linkwechat.domain.wecom.query.WeBaseQuery;
 import com.linkwechat.domain.wecom.query.WeBaseQuery;
 import com.linkwechat.domain.wecom.query.customer.WeCustomerQuery;
 import com.linkwechat.domain.wecom.query.customer.WeCustomerQuery;
@@ -39,10 +36,13 @@ import com.linkwechat.fegin.QwCustomerClient;
 import com.linkwechat.framework.service.TokenService;
 import com.linkwechat.framework.service.TokenService;
 import com.linkwechat.mapper.WeGroupFissionMapper;
 import com.linkwechat.mapper.WeGroupFissionMapper;
 import com.linkwechat.service.*;
 import com.linkwechat.service.*;
+import me.chanjar.weixin.common.error.WxErrorException;
 import me.chanjar.weixin.cp.api.WxCpExternalContactService;
 import me.chanjar.weixin.cp.api.WxCpExternalContactService;
 import me.chanjar.weixin.cp.api.WxCpService;
 import me.chanjar.weixin.cp.api.WxCpService;
 import me.chanjar.weixin.cp.api.WxCpUserService;
 import me.chanjar.weixin.cp.api.WxCpUserService;
 import me.chanjar.weixin.cp.bean.WxCpUser;
 import me.chanjar.weixin.cp.bean.WxCpUser;
+import me.chanjar.weixin.cp.bean.external.WxCpContactWayInfo;
+import me.chanjar.weixin.cp.bean.external.WxCpContactWayResult;
 import me.chanjar.weixin.cp.bean.external.WxCpUserExternalGroupChatInfo;
 import me.chanjar.weixin.cp.bean.external.WxCpUserExternalGroupChatInfo;
 import me.chanjar.weixin.cp.bean.external.WxCpUserExternalGroupChatList;
 import me.chanjar.weixin.cp.bean.external.WxCpUserExternalGroupChatList;
 import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
 import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
@@ -92,6 +92,8 @@ public class WeGroupFissionServiceImpl extends ServiceImpl<WeGroupFissionMapper,
     @Autowired
     @Autowired
     private IWeExternalChatService weExternalChatService;
     private IWeExternalChatService weExternalChatService;
 
 
+    @Autowired
+    private IWeGroupFissionQrCodeRecordService weGroupFissionQrCodeRecordService;
 
 
     /**
     /**
      *  @version
      *  @version
@@ -533,6 +535,72 @@ public class WeGroupFissionServiceImpl extends ServiceImpl<WeGroupFissionMapper,
         }
         }
         return list;
         return list;
     }
     }
+    /**
+     *  @version
+     *  @author: hxl
+     *  @Date: 2026/2/11 17:26
+     *  @Description: 获取群二维码链接
+     */
+    @Override
+    public String getChartQrCodeByChatId(String chatId, String activityId) {
+        WeGroupFissionQrCodeRecord weGroupFissionQrCodeRecord =  weGroupFissionQrCodeRecordService.findEntityByActivityIdAndShareId(activityId, null);
+        WxCpExternalContactService service = wxCpService.getExternalContactService();
+        WxCpContactWayInfo contactWayInfo = new WxCpContactWayInfo();
+        WxCpContactWayInfo.ContactWay contactWay = new WxCpContactWayInfo.ContactWay();
+        // ========== 基础必填参数(内部类已有属性) ==========
+        // 类型:MULTI(2) 表示群聊(SINGLE(1)是单人)
+        contactWay.setType(WxCpContactWayInfo.TYPE.MULTI);
+        // 场景:QRCODE(2) 对应接口的「链接入群」场景(枚举命名虽为二维码,但值是2,接口兼容)
+        contactWay.setScene(WxCpContactWayInfo.SCENE.QRCODE);
+        // 自定义状态值(关联你的裂变记录ID)
+        contactWay.setState(weGroupFissionQrCodeRecord.getId().toString());
+        // 跳过验证:用户扫码直接入群
+        contactWay.setSkipVerify(Boolean.TRUE);
+        contactWayInfo.setContactWay(contactWay);
+        // ========== 兼容群聊专属属性(SDK封装不全,手动补充JSON) ==========
+        // 4.1.0 SDK的ContactWay类未封装roomBaseName/chatIdList等群聊属性,需手动拼接JSON
+        String baseJson = contactWayInfo.toJson();
+        JsonObject jsonObject = JsonParser.parseString(baseJson).getAsJsonObject();
+        JsonObject contactWayJson = jsonObject.getAsJsonObject("contact_way");
+
+        // 添加群聊专属属性(接口要求的核心参数)
+        DistDto distDto = querySingleChatDetail(chatId);
+        String roomBaseName = distDto.getName();
+        contactWayJson.addProperty("room_base_name", roomBaseName); // 群聊基础名称
+        contactWayJson.addProperty("room_base_id", 0); // 固定传0
+        contactWayJson.addProperty("auto_create_room", 0); // 关闭自动建群(用已有群)
+        List<String> chatIdList = new ArrayList<>();
+        chatIdList.add(chatId);
+        contactWayJson.add("chat_id_list", JsonParser.parseString(com.alibaba.fastjson.JSON.toJSONString(chatIdList)).getAsJsonArray());
+        String finalJson = jsonObject.toString();
+        try{
+            WxCpContactWayResult result = service.addContactWay(WxCpContactWayInfo.fromJson(finalJson));
+            WxCpContactWayInfo contactWayCode = service.getContactWay(result.getConfigId());
+            String qrCode = contactWayCode.getContactWay().getQrCode();
+            return qrCode;
+        }catch (Exception e){
+        }
+        return null;
+    }
+    /**
+     *  @version
+     *  @author: hxl
+     *  @Date: 2026/2/10 19:00
+     *  @Description: 查询单个群详情
+     */
+    private DistDto querySingleChatDetail(String chatId) {
+        try {
+            WxCpUserExternalGroupChatInfo groupChat =
+                    wxCpService.getExternalContactService().getGroupChat(chatId);
+            WxCpUserExternalGroupChatInfo.GroupChat detail = groupChat.getGroupChat();
+            DistDto dto = new DistDto();
+            dto.setVal(detail.getChatId());
+            dto.setName(detail.getName());
+            return dto;
+        } catch (WxErrorException e) {
+            return null;
+        }
+    }
     /**
     /**
      *  @version
      *  @version
      *  @author: hxl
      *  @author: hxl

+ 5 - 0
linkwe-service/src/main/resources/mapper/wecom/WeGroupFissionQrCodeRecordMapper.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.linkwechat.mapper.WeGroupFissionQrCodeRecordMapper">
+
+</mapper>