package com.qqflow.engine.domain.ewechat.api; import cn.hutool.core.io.FileUtil; import cn.hutool.core.map.MapBuilder; import cn.hutool.core.map.MapUtil; import com.qqflow.engine.common.exception.BusinessException; import com.qqflow.engine.common.util.Assert; import com.qqflow.engine.domain.ewechat.model.enterprise.req.*; import com.qqflow.engine.domain.ewechat.model.enterprise.rsp.*; import lombok.RequiredArgsConstructor; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.Resource; import org.springframework.http.*; import org.springframework.stereotype.Component; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.client.RestTemplate; import org.springframework.web.multipart.MultipartFile; import java.io.File; import java.net.URI; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; /** * @author WuWenYi * @since 2025/3/17 */ @Component @RequiredArgsConstructor public class EnterpriseWechatApi { private static final String BASE_URL = "https://qyapi.weixin.qq.com"; private final RestTemplate restTemplate; private void checkRsp(EnterpriseWechatBaseRsp rsp){ Assert.notNull(rsp,"返回值为空"); boolean condition = Objects.equals(null, rsp.getErrcode()) || Objects.equals(rsp.getErrcode(), 0); Assert.isTure(condition, rsp.getErrmsg()); } /** * 获取企业微信后台推送的ticket * suiteId 第三方应用id或者代开发应用模板id。第三方应用以ww或wx开头应用id(对应于旧的以tj开头的套件id);代开发应用以dk开头 * @param suiteTicket suite_ticket由企业微信后台定时推送给“指令回调URL”,每十分钟更新一次,见推送suite_ticket。 * @return 第三方应用凭证(用于获取永久授权码) */ public EnterpriseWechatSuiteAccessTokenRsp getSuiteAccessToken(String suiteId, String suiteSecret, String suiteTicket) { EnterpriseWechatSuiteAccessTokenReq req = EnterpriseWechatSuiteAccessTokenReq.of(suiteId, suiteSecret, suiteTicket); return this.commonPost("/cgi-bin/service/get_suite_token", req, EnterpriseWechatSuiteAccessTokenRsp.class); } /** 获取授权信息 */ public EnterpriseWechatPermanentCodeRsp getPermanentCode(String suiteAccessToken, String authCode){ Map req = MapBuilder.create().put("auth_code", authCode).build(); String uri = "/cgi-bin/service/get_permanent_code?suite_access_token=" + suiteAccessToken; return this.commonPost(uri, req, EnterpriseWechatPermanentCodeRsp.class); } /** 获取accesstoken,基本上要调代开发的接口都需要用到这个 */ public EnterpriseWechatAccessTokenRsp getAccessToken(String corpId, String permanentCode) { String uri = "/cgi-bin/gettoken?corpid=" + corpId + "&corpsecret=" + permanentCode; return this.commonGet(uri, EnterpriseWechatAccessTokenRsp.class); } /** 获取成员ID列表(open_userid + 部门ID),支持分页游标 */ public EnterpriseWechatListIdRsp listUserId(String accessToken, String cursor, Integer limit) { EnterpriseWechatListIdReq req = new EnterpriseWechatListIdReq(); req.setCursor(cursor); req.setLimit(limit); String uri = "/cgi-bin/user/list_id?access_token=" + accessToken; return this.commonPost(uri, req, EnterpriseWechatListIdRsp.class); } /** 获取部门列表(递归获取指定部门及其子部门,不填id则获取全量) */ public EnterpriseWechatDepartmentListRsp listDepartment(String accessToken, Long deptId) { String uri = "/cgi-bin/department/list?access_token=" + accessToken; if (deptId != null) uri += "&id=" + deptId; return this.commonGet(uri, EnterpriseWechatDepartmentListRsp.class); } /** 获取部门成员(仅返回本部门直属成员,不含子部门) */ public EnterpriseWechatUserListRsp listDepartmentUsers(String accessToken, Long departmentId) { String uri = "/cgi-bin/user/simplelist?access_token=" + accessToken + "&department_id=" + departmentId; return this.commonGet(uri, EnterpriseWechatUserListRsp.class); } /** 通过手机号获取userid */ public EnterpriseWechatUseridByMobileRsp getUseridByMobile(String accessToken, String mobile) { EnterpriseWechatUseridByMobileReq req = EnterpriseWechatUseridByMobileReq.of(mobile); String uri = "/cgi-bin/user/getuserid?access_token=" + accessToken; return this.commonPost(uri, req, EnterpriseWechatUseridByMobileRsp.class); } /** * 添加客服 * @param name 客服名称 * @param mediaId 素材ID */ public EnterpriseWechatAddCsRsp addCs(String accessToken, String name, String mediaId) { EnterpriseWechatAddCsReq req = EnterpriseWechatAddCsReq.of(name, mediaId); String uri = "/cgi-bin/kf/account/add?access_token=" + accessToken; return this.commonPost(uri, req, EnterpriseWechatAddCsRsp.class); } public EnterpriseWechatCsUrlRsp getCsUrl(String accessToken, String csId) { EnterpriseWechatCsUrlReq req = EnterpriseWechatCsUrlReq.of(csId, "test1"); String uri = "/cgi-bin/kf/add_contact_way?access_token=" + accessToken; return this.commonPost(uri, req, EnterpriseWechatCsUrlRsp.class); } public EnterpriseWechatBaseRsp deleteCs(String accessToken, String openKfid) { String uri = "/cgi-bin/kf/account/del?access_token=" + accessToken; Map req = MapUtil.builder("open_kfid", openKfid).build(); return this.commonPost(uri, req, EnterpriseWechatBaseRsp.class); } /** 同步消息 */ public EnterpriseWechatSyncMsgRsp syncMsg(String accessToken, String cursor, String token, String openKfId) { EnterpriseWechatSyncMsgReq req = EnterpriseWechatSyncMsgReq.of(cursor, token, openKfId); String uri = "/cgi-bin/kf/sync_msg?access_token=" + accessToken; return this.commonPost(uri, req, EnterpriseWechatSyncMsgRsp.class); } /** 获取会话状态 */ public EnterpriseWechatSessionRsp getSessionState(String accessToken, String openKfId, String userId){ EnterpriseWechatSessionReq req = EnterpriseWechatSessionReq.of(openKfId, userId); String uri = "/cgi-bin/kf/service_state/get?access_token=" + accessToken; return this.commonPost(uri, req, EnterpriseWechatSessionRsp.class); } /** 变更会话状态 */ public EnterpriseWechatChangeSessionRsp changeSessionState(String accessToken, String openKfId, String userId, Integer serviceState){ EnterpriseWechatChangeSessionReq req = EnterpriseWechatChangeSessionReq.of(openKfId, userId, serviceState); String uri = "/cgi-bin/kf/service_state/trans?access_token=" + accessToken; return this.commonPost(uri, req, EnterpriseWechatChangeSessionRsp.class); } /** 发送回应消息 */ public EnterpriseWechatSendMsgRsp sendMsgOnEvent(String accessToken, String code, String content){ EnterpriseWechatSendOnEventMsgReq req = EnterpriseWechatSendOnEventMsgReq.of(code, content); String uri = "/cgi-bin/kf/send_msg_on_event?access_token=" + accessToken; return this.commonPost(uri, req, EnterpriseWechatSendMsgRsp.class); } /** 发送客服消息 */ public EnterpriseWechatSendMsgRsp sendKfMsg(String accessToken, EnterpriseWechatSendMsgReq req){ String uri = "/cgi-bin/kf/send_msg?access_token=" + accessToken; return this.commonPost(uri, req, EnterpriseWechatSendMsgRsp.class); } /** 发送应用文本消息 */ public EnterpriseWechatSendTextMsgRsp sendTextMsg(String accessToken, EnterpriseWechatSendTextMsgReq req) { String uri = "/cgi-bin/message/send?access_token=" + accessToken; return this.commonPost(uri, req, EnterpriseWechatSendTextMsgRsp.class); } /** 上传素材文件 */ public EnterpriseWechatUploadRsp upload(String accessToken, String type, File file) { return this.upload(accessToken, type, getResource(file.getPath()), (int) file.length()); } public EnterpriseWechatUploadRsp upload(String accessToken, String type, MultipartFile multipartFile) { return this.upload(accessToken, type, multipartFile.getResource(), (int) multipartFile.getSize()); } private EnterpriseWechatUploadRsp upload(String accessToken, String type, Resource resource, int fileLength) { String url = BASE_URL + "/cgi-bin/media/upload?access_token=" + accessToken + "&type=" + type; MultiValueMap body = new LinkedMultiValueMap<>(); body.add("file", resource); HttpHeaders headers = new HttpHeaders(); headers.set("Content-disposition", "form-data; name=media;filename=" + resource.getFilename() + "; filelength=" + fileLength); headers.setContentType(MediaType.MULTIPART_FORM_DATA); HttpEntity> requestEntity = new HttpEntity<>(body, headers); ResponseEntity response = restTemplate.exchange(url, HttpMethod.POST, requestEntity, EnterpriseWechatUploadRsp.class); this.checkRsp(response.getBody()); return response.getBody(); } private static Resource getResource(String path) { // 先尝试从 classpath 加载 Resource resource = new ClassPathResource(path); if (resource.exists()) { return resource; } // 如果不存在,再尝试从文件系统加载 File file = new File(path); if (file.exists()) { return new FileSystemResource(file); } throw new IllegalArgumentException("File not found: " + path); } /** 下载临时素材 */ public String downloadMedia(String accessToken, String mediaId) { String url = BASE_URL + "/cgi-bin/media/get?access_token=" + accessToken + "&media_id=" + mediaId; HttpHeaders headers = new HttpHeaders(); headers.set(HttpHeaders.USER_AGENT, "Mozilla/5.0"); RequestEntity request = new RequestEntity<>(headers, HttpMethod.GET, URI.create(url)); ResponseEntity response = restTemplate.exchange(request, byte[].class); Assert.isTure(response.getStatusCode() == HttpStatus.OK, "download media failed"); // 获取 Content-Disposition 头 HttpHeaders responseHeaders = response.getHeaders(); String contentType = responseHeaders.getContentType() != null ? responseHeaders.getContentType().toString() : ""; if (contentType.contains("application/json")) { throw new BusinessException("download media failed,error:" + new String(response.getBody())); } String fileName = extractFileName(responseHeaders).orElseThrow(() -> new BusinessException("can not get file name")); // 保存文件 FileUtil.writeBytes(response.getBody(),fileName); return fileName; } private static Optional extractFileName(HttpHeaders headers) { List dispositions = headers.get(HttpHeaders.CONTENT_DISPOSITION); if (dispositions != null) { for (String disposition : dispositions) { if (disposition.contains("filename=")) { return Optional.of(disposition.split("filename=")[1].replaceAll("\"", "")); } } } return Optional.empty(); } private T commonPost(String uri, Object req, Class rspClass) { String url = BASE_URL + uri; HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); HttpEntity requestEntity = new HttpEntity<>(req, headers); T rsp = restTemplate.postForObject(url, requestEntity, rspClass); this.checkRsp(rsp); return rsp; } private T commonGet(String uri, Class rspClass) { String url = BASE_URL + uri; HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); HttpEntity requestEntity = new HttpEntity<>(headers); ResponseEntity exchange = restTemplate.exchange(url, HttpMethod.GET, requestEntity, rspClass); this.checkRsp(exchange.getBody()); return exchange.getBody(); } }