SpringBoot集成騰訊IM流程

1.application.yaml中添加IM配置信息

#im模塊
im:  identifier: admin  sdkappid: 1400888888  key: ccf2dc88c1ca232cfabbd24906d5091ab81ba0250224abc

2.封裝IM工具類

Component
@Getter
@RefreshScope
public class ImAdminSignConfig {/*** 簽名*/private String usersig;/*** 管理員id*/@Value("${im.identifier}")private String identifier;/*** im容器id*/@Value("${im.sdkappid}")private long sdkappid;/*** im容器key*/@Value("${im.key}")private String key;/*** im的api請求參數集合*/private HashMap<String, String> uriParams;@PostConstructvoid init() {usersig = TencentImUserSignUtil.genUserSig(identifier, 60 * 60 * 24 * 180, sdkappid, key);uriParams = new HashMap<>(16);// 半年簽名uriParams.put("usersig", usersig);uriParams.put("identifier", identifier);uriParams.put("sdkappid", String.valueOf(sdkappid));}/*** 騰訊im簽名**/public String genUserSig(String usersig, long expire) {return TencentImUserSignUtil.genUserSig(usersig, expire, sdkappid, key);}/*** 騰訊im簽名**/public static class TencentImUserSignUtil {/*** 【功能說明】用于簽發 TRTC 和 IM 服務中必須要使用的 UserSig 鑒權票據* <p>* 【參數說明】** @param userid - 用戶id,限制長度為32字節,只允許包含大小寫英文字母(a-zA-Z)、數字(0-9)及下劃線和連詞符。* @param expire - UserSig 票據的過期時間,單位是秒,比如 86400 代表生成的 UserSig 票據在一天后就無法再使用了。* @return usersig -生成的簽名*/public static String genUserSig(String userid, long expire, long sdkappid, String key) {return genUserSig(userid, expire, null, sdkappid, key);}private static String hmacsha256(String identifier, long currTime, long expire, String base64Userbuf, long sdkappid, String key) {String contentToBeSigned = "TLS.identifier:" + identifier + "\n"+ "TLS.sdkappid:" + sdkappid + "\n"+ "TLS.time:" + currTime + "\n"+ "TLS.expire:" + expire + "\n";if (null != base64Userbuf) {contentToBeSigned += "TLS.userbuf:" + base64Userbuf + "\n";}try {byte[] byteKey = key.getBytes(StandardCharsets.UTF_8);Mac hmac = Mac.getInstance("HmacSHA256");SecretKeySpec keySpec = new SecretKeySpec(byteKey, "HmacSHA256");hmac.init(keySpec);byte[] byteSig = hmac.doFinal(contentToBeSigned.getBytes(StandardCharsets.UTF_8));return (Base64.getEncoder().encodeToString(byteSig)).replaceAll("\\s*", "");} catch (NoSuchAlgorithmException | InvalidKeyException e) {return "";}}private static String genUserSig(String userid, long expire, byte[] userbuf, long sdkappid, String key) {long currTime = System.currentTimeMillis() / 1000;JSONObject sigDoc = new JSONObject();sigDoc.put("TLS.ver", "2.0");sigDoc.put("TLS.identifier", userid);sigDoc.put("TLS.sdkappid", sdkappid);sigDoc.put("TLS.expire", expire);sigDoc.put("TLS.time", currTime);String base64UserBuf = null;if (null != userbuf) {base64UserBuf = Base64.getEncoder().encodeToString(userbuf).replaceAll("\\s*", "");sigDoc.put("TLS.userbuf", base64UserBuf);}String sig = hmacsha256(userid, currTime, expire, base64UserBuf, sdkappid, key);if (sig.length() == 0) {return "";}sigDoc.put("TLS.sig", sig);Deflater compressor = new Deflater();compressor.setInput(sigDoc.toString().getBytes(StandardCharsets.UTF_8));compressor.finish();byte[] compressedBytes = new byte[2048];int compressedBytesLength = compressor.deflate(compressedBytes);compressor.end();return (new String(Base64Url.base64EncodeUrl(Arrays.copyOfRange(compressedBytes,0, compressedBytesLength)))).replaceAll("\\s*", "");}}
}

3.封裝IM API接口路徑

/*** 騰訊im的api路徑**/
@Slf4j
@Component
public class ImApiServer {private static final Logger LOGGER = LoggerFactory.getLogger(ImApiServer.class);@Resourceprivate ImAdminSignConfig signConfig;/*** im的url*/private final String BASE_URL = "https://console.tim.qq.com";/*** uri基本參數模板*/private final String PARAMS_TEM = "?usersig=%s&identifier=%s&sdkappid=%s&contenttype=json";/*** 簽名** @param userId 用戶ID* @param time   時間(單位:秒)* @return 簽名*/public String userSign(String userId, long time) {return signConfig.genUserSig(userId, time);}/*** 創建群組** @param rawJsonStr raw實體* @return 響應參數json*/public String createGroup(String rawJsonStr) {String apiUrl = "/v4/group_open_http_svc/create_group";return postImApi(rawJsonStr, apiUrl);}/*** 解散群組** @param rawJsonStr raw實體* @return 響應參數json*/public String destroyGroup(String rawJsonStr) {String apiUrl = "/v4/group_open_http_svc/destroy_group";return postImApi(rawJsonStr, apiUrl);}/*** 修改群組基本信息** @param rawJsonStr raw實體* @return 響應參數json*/public String modifyGroupBaseInfo(String rawJsonStr) {String apiUrl = "/v4/group_open_http_svc/modify_group_base_info";return postImApi(rawJsonStr, apiUrl);}/*** 增加群成員** @param rawJsonStr raw實體* @return 響應參數json*/public String addGroupMember(String rawJsonStr) {String apiUrl = "/v4/group_open_http_svc/add_group_member";return postImApi(rawJsonStr, apiUrl);}/*** 刪除群成員** @param rawJsonStr raw實體* @return 響應參數json*/public String deleteGroupMember(String rawJsonStr) {String apiUrl = "/v4/group_open_http_svc/delete_group_member";return postImApi(rawJsonStr, apiUrl);}/*** 導入單個賬號** @param rawJsonStr raw實體* @return 響應參數json*/public String accountImport(String rawJsonStr) {String apiUrl = "/v4/im_open_login_svc/account_import";return postImApi(rawJsonStr, apiUrl);}/*** 批量導入賬號** @param rawJsonStr raw實體* @return 響應參數json*/public String accountImportBatch(String rawJsonStr) {String apiUrl = "/v4/im_open_login_svc/multiaccount_import";return postImApi(rawJsonStr, apiUrl);}/*** 批量刪除賬號** @param rawJsonStr raw實體* @return 響應參數json*/public String accountDelete(String rawJsonStr) {String apiUrl = "/v4/im_open_login_svc/account_delete";return postImApi(rawJsonStr, apiUrl);}/*** 設置資料* 支持 標配資料字段 和 自定義資料字段 的設置。** @param rawJsonStr raw實體* @return 響應參數json*/public String portraitSet(String rawJsonStr) {String apiUrl = "/v4/profile/portrait_set";return postImApi(rawJsonStr, apiUrl);}/*** 群里發送消息** @param rawJsonStr raw實體* @return 響應參數json*/public String sendGroupMsg(String rawJsonStr) {String apiUrl = "/v4/group_open_http_svc/send_group_msg";return postImApi(rawJsonStr, apiUrl);}/*** 群里發送系統消息** @param rawJsonStr raw實體* @return 響應參數json*/public String sendGroupSystemMsg(String rawJsonStr) {String apiUrl = "/v4/group_open_http_svc/send_group_system_notification";return postImApi(rawJsonStr, apiUrl);}/*** im請求基礎api** @param rawJsonStr raw實體* @return 響應參數json*/private String postImApi(String rawJsonStr, String apiUrl) {String runningId = IdUtil.simpleUUID();String paramsUrl = String.format(PARAMS_TEM, signConfig.getUsersig(), signConfig.getIdentifier(), signConfig.getSdkappid());String url = BASE_URL + apiUrl + paramsUrl;LOGGER.info("請求im接口, 流水號[{}], url[{}], 入參[{}]", runningId, url, rawJsonStr);String res = this.httpPost(url, rawJsonStr);LOGGER.info("im接口響應, 流水號[{}], url[{}], 響應[{}]", runningId, url, rawJsonStr);return res;}/*** POST** @param url  鏈接* @param data body* @return 響應*/private String httpPost(String url, String data) {String result = null;try (CloseableHttpClient httpClient = HttpClients.createDefault()) {StringEntity stringEntity = new StringEntity(data, "UTF-8");stringEntity.setContentEncoding("UTF-8");HttpPost httpPost = new HttpPost(url);httpPost.addHeader("Content-Type", "application/json");httpPost.setEntity(stringEntity);CloseableHttpResponse response = httpClient.execute(httpPost);if (response != null) {HttpEntity resEntity = response.getEntity();if (resEntity != null) {result = EntityUtils.toString(resEntity, "utf-8");}}} catch (Exception e) {log.error("im請求異常", e);}return result;}/*** 發送自定義消息,用來消息計數** @param rawJsonStr raw實體* @return 響應參數json*/public String sendCustomMessageCount(String rawJsonStr) {String apiUrl = "/v4/openim/sendmsg";return postImApi(rawJsonStr, apiUrl);}/*** 群組批量禁言取消禁言** @param rawJsonStr raw實體* @return 響應參數json*/public String groupForbiddenWordBatch(String rawJsonStr) {String apiUrl = "/v4/group_open_http_svc/forbid_send_msg";return postImApi(rawJsonStr, apiUrl);}
}

4.封裝IM Service接口

/*** im用戶相關服務**/
public interface ImUserService {/*** 給user簽名** @param userId 用戶ID(用戶唯一鍵均可,可以為中文)* @return 簽名*/String userSign(String userId);/*** 注冊User到im** @param user 用戶對象實體類* @return userId*/String registerUser(ImRegisterUserReq user);/*** 批量刪除im中的user** @param userIds 用戶ID集合* @return 是否成功*/List<ImUserResp> removeUser(List<String> userIds);/*** 修改user基本信息** @param user 用戶對象實體類* @return 是否成功*/boolean updateUser(ImUpdateUserReq user);/*** 發送自定義消息,用來消息計數** @param request* @return 發送成功*/boolean sendCustomMessageCount(ImCustomMsgReq request);/*** 批量注冊IM** @param imRegisterUserList*/void registerUserBatch(List<ImRegisterUserReq> imRegisterUserList);
}

5.封裝IM Service接口實現類

/*** im用戶相關服務**/
@Service
public class ImUserServiceImpl implements ImUserService {private static final Logger LOGGER = LoggerFactory.getLogger(ImUserServiceImpl.class);/*** im api*/@Resourceprivate ImApiServer imApiServer;/*** 給user簽名* 默認簽名7天** @param userId 用戶ID(用戶唯一鍵均可,可以為中文)* @return 簽名*/@Overridepublic String userSign(String userId) {// 默認簽名7天long time = 60 * 60 * 24 * 7;String sign = imApiServer.userSign(userId, time);LOGGER.info("給user簽名,userId[{}], 簽名[{}]", userId, sign);return sign;}/*** 注冊User到im** @param user 用戶對象實體類* @return userId*/@Overridepublic String registerUser(ImRegisterUserReq user) {LOGGER.info("注冊User到im,參數[{}]", JacksonUtil.toJsonString(user));ImApiUserModelReq rawJson = new ImApiUserModelReq().setUserId(user.getUserId()).setNick(user.getNick()).setFaceUrl(user.getFaceUrl());String recordJson = imApiServer.accountImport(JacksonUtil.toJsonString(rawJson));//斷言返回體是否存在if (recordJson == null) {throw BizExceptionFactory.newBizException(UserErrorEnum.IM_001.value,UserErrorEnum.IM_001.desc);}ImApiRecordReq imApiRecord = JacksonUtil.fromJson(recordJson, ImApiRecordReq.class);//斷言接口業務是否成功if (imApiRecord.getErrorCode() != ImApiRecordReq.SUCCESS) {throw BizExceptionFactory.newBizException(UserErrorEnum.IM_002.value, UserErrorEnum.IM_002.desc + "-->" + recordJson);}//增加自定義參數,登錄賬號String account = user.getAttrAccount();if (account != null) {ImApiUserModelReq rawAttrJson = new ImApiUserModelReq().setFromAccount(user.getUserId()).setProfileItem(Collections.singletonList(new ImApiUserModelReq.ProfileItem(ImApiUserModelReq.ProfileItem.TAG_ACCOUNT, account)));String recordAttrJson = imApiServer.portraitSet(JacksonUtil.toJsonString(rawAttrJson));}return user.getUserId();}/*** 批量刪除im中的user** @param userIds 用戶ID集合* @return 是否成功*/@Overridepublic List<ImUserResp> removeUser(List<String> userIds) {LOGGER.info("批量刪除im中的user,參數[{}]", JacksonUtil.toJsonString(userIds));ImApiUserModelReq rawJson = new ImApiUserModelReq().setToDelUserList(userIds.stream().map(ImUserResp::new).collect(Collectors.toList()));String recordJson = imApiServer.accountDelete(JacksonUtil.toJsonString(rawJson));//斷言返回體是否存在if (recordJson == null) {throw BizExceptionFactory.newBizException(UserErrorEnum.IM_001.value,UserErrorEnum.IM_001.desc);}ImApiRecordReq imApiRecord = JacksonUtil.fromJson(recordJson, ImApiRecordReq.class);//斷言接口業務是否成功if (imApiRecord.getErrorCode() != ImApiRecordReq.SUCCESS) {throw BizExceptionFactory.newBizException(UserErrorEnum.IM_002.value, UserErrorEnum.IM_002.desc + "-->" + recordJson);}return imApiRecord.getDelUserInfoList();}/*** 修改user基本信息** @param user 用戶對象實體類* @return 是否成功*/@Overridepublic boolean updateUser(ImUpdateUserReq user) {LOGGER.info("修改user基本信息,參數[{}]", JacksonUtil.toJsonString(user));ImApiUserModelReq rawJson = new ImApiUserModelReq().setUserId(user.getUserId()).setNick(user.getNick()).setFaceUrl(user.getFaceUrl());String recordJson = imApiServer.accountImport(JacksonUtil.toJsonString(rawJson));//斷言返回體是否存在if (recordJson == null) {throw BizExceptionFactory.newBizException(UserErrorEnum.IM_001.value,UserErrorEnum.IM_001.desc);}ImApiRecordReq imApiRecord = JacksonUtil.fromJson(recordJson, ImApiRecordReq.class);//斷言接口業務是否成功if (imApiRecord.getErrorCode() != ImApiRecordReq.SUCCESS) {throw BizExceptionFactory.newBizException(UserErrorEnum.IM_002.value, UserErrorEnum.IM_002.desc + "-->" + recordJson);}//增加自定義參數List<ImApiUserModelReq.ProfileItem> profileItem = new ArrayList<>();String attrAccount = user.getAttrAccount();if (attrAccount != null) {profileItem.add(new ImApiUserModelReq.ProfileItem(ImApiUserModelReq.ProfileItem.TAG_ACCOUNT, attrAccount));}ImApiUserModelReq rawAttrJson = new ImApiUserModelReq().setFromAccount(user.getUserId()).setProfileItem(profileItem);String recordAttrJson = imApiServer.portraitSet(JacksonUtil.toJsonString(rawAttrJson));return true;}/*** 發送自定義消息,用來消息計數** @param request* @return 發送成功*/@Overridepublic boolean sendCustomMessageCount(ImCustomMsgReq request) {ImMsgContentReq imMsgContentRequest = new ImMsgContentReq().setDesc(request.getDesc()).setData(request.getData()).setExt(request.getExt()).setSound(request.getSound());ImMsgBodyReq imMsgBodyRequest = new ImMsgBodyReq().setMsgContent(imMsgContentRequest).setMsgType("TIMCustomElem");List<ImMsgBodyReq> imMsgBodyRequestList = new ArrayList<>();imMsgBodyRequestList.add(imMsgBodyRequest);ImCustomMsgReq rawJson = new ImCustomMsgReq().setSyncOtherMachine(2).setToAccount(request.getToAccount()).setMsgRandom(Integer.parseInt(getRandomNickname(8))).setMsgTimeStamp(Integer.parseInt(String.valueOf(LocalDateTime.now().toEpochSecond(ZoneOffset.of("+8"))))).setForbidCallbackControl(Arrays.asList("ForbidBeforeSendMsgCallback", "ForbidAfterSendMsgCallback").toArray()).setMsgBody(imMsgBodyRequestList);String recordJson = imApiServer.sendCustomMessageCount(JacksonUtil.toJsonString(rawJson));//斷言返回體是否存在if (recordJson == null) {throw BizExceptionFactory.newBizException(UserErrorEnum.IM_001.value,UserErrorEnum.IM_001.desc);}ImApiRecordReq imApiRecord = JacksonUtil.fromJson(recordJson, ImApiRecordReq.class);//斷言接口業務是否成功if (imApiRecord.getErrorCode() != ImApiRecordReq.SUCCESS) {throw BizExceptionFactory.newBizException(UserErrorEnum.IM_002.value, UserErrorEnum.IM_002.desc + "-->" + recordJson);}return Boolean.TRUE;}/*** 批量注冊IM** @param systemImRegisterUserList*/@Overridepublic void registerUserBatch(List<ImRegisterUserReq> systemImRegisterUserList) {LOGGER.info("批量注冊User到im,參數[{}]", JacksonUtil.toJsonString(systemImRegisterUserList));List<String> userIdList = systemImRegisterUserList.stream().map(o -> o.getUserId()).collect(Collectors.toList());ImApiUserModelReq rawJson = new ImApiUserModelReq().setAccounts(userIdList);String recordJson = imApiServer.accountImportBatch(JacksonUtil.toJsonString(rawJson));//斷言返回體是否存在if (recordJson == null) {throw BizExceptionFactory.newBizException(UserErrorEnum.IM_001.value,UserErrorEnum.IM_001.desc);}ImApiRecordReq imApiRecord = JacksonUtil.fromJson(recordJson, ImApiRecordReq.class);//斷言接口業務是否成功if (imApiRecord.getErrorCode() != ImApiRecordReq.SUCCESS) {throw BizExceptionFactory.newBizException(UserErrorEnum.IM_002.value, UserErrorEnum.IM_002.desc + "-->" + recordJson);}//增加自定義參數systemImRegisterUserList.forEach(user -> {String account = user.getAttrAccount();if (account != null) {ImApiUserModelReq rawAttrJson = new ImApiUserModelReq().setFromAccount(user.getUserId()).setProfileItem(Collections.singletonList(new ImApiUserModelReq.ProfileItem(ImApiUserModelReq.ProfileItem.TAG_ACCOUNT, account)));String resp = imApiServer.portraitSet(JacksonUtil.toJsonString(rawAttrJson));LOGGER.info("批量注冊User到im,返回信息[{}]", resp);}});}/*** java生成隨機數字10位數** @param length [生成隨機數的長度]* @return*/public static String getRandomNickname(int length) {StringBuilder val = new StringBuilder();Random random = new Random();for (int i = 0; i < length; i++) {val.append(random.nextInt(10));}return val.toString();}}

6.在業務代碼中調用IM接口方法

 //發送系統消息
ImCustomMsgReq imCustomMsgReq = new ImCustomMsgReq();
imCustomMsgReq.setData(desc).setDesc(desc).setToAccount(userId.toString());imUserService.sendCustomMessageCount(imCustomMsgReq);

7.集成過程中用到的相關工具類

ImMsgBodyReq.java

@Getter
@Setter
@ToString
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = false)
public class ImMsgBodyReq {/*** 標簽名*/@JsonProperty("MsgType")private String msgType;/*** 值*/@JsonProperty("MsgContent")private ImMsgContentReq msgContent;
}

ImMsgContentReq.java

@Getter
@Setter
@ToString
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = false)
public class ImMsgContentReq {/*** 自定義消息數據。 不作為 APNs 的 payload 字段下發,故從 payload 中無法獲取 Data 字段*/@JsonProperty("Data")private String data;/*** 說明* 當消息中只有一個 TIMCustomElem 自定義消息元素時,如果 Desc 字段和 OfflinePushInfo.Desc 字段都不填寫,* 將收不到該條消息的離線推送,需要填寫 OfflinePushInfo.Desc 字段才能收到該消息的離線推送。*/@JsonProperty("Desc")private String desc;/*** 擴展字段。當接收方為 iOS 系統且應用處在后臺時,此字段作為 APNs 請求包 Payloads 中的 Ext 鍵值下發,Ext 的協議格式由業務方確定,APNs 只做透傳。*/@JsonProperty("Ext")private String ext;/*** 自定義 APNs 推送鈴音。*/@JsonProperty("Sound")private String sound;
}

ImCustomMsgReq.java

/*** im 自定義單聊消息*/
@Getter
@Setter
@ToString
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class ImCustomMsgReq implements Serializable {private static final long serialVersionUID = 1L;/*** 1:把消息同步到 From_Account 在線終端和漫游上;* 2:消息不同步至 From_Account;* 若不填寫默認情況下會將消息存 From_Account 漫游* 選填*/@JsonProperty("SyncOtherMachine")private Integer syncOtherMachine;/*** 消息接收方 UserID* 必填*/@JsonProperty("To_Account")private String toAccount;/*** 消息離線保存時長(單位:秒),最長為7天(604800秒)* 若設置該字段為0,則消息只發在線用戶,不保存離線* 若設置該字段超過7天(604800秒),仍只保存7天* 若不設置該字段,則默認保存7天* 選填*/@JsonProperty("MsgLifeTime")private Integer msgLifeTime;/*** 消息序列號,后臺會根據該字段去重及進行同秒內消息的排序,詳細規則請看本接口的功能說明。若不填該字段,則由后臺填入隨機數。* 選填*/@JsonProperty("MsgSeq")private Integer msgSeq;/*** 消息隨機數,后臺用于同一秒內的消息去重。請確保該字段填的是隨機數* 必填*/@JsonProperty("MsgRandom")private Integer msgRandom;/*** 消息時間戳,UNIX 時間戳(單位:秒)* 選填*/@JsonProperty("MsgTimeStamp")private Integer msgTimeStamp;/*** 消息回調禁止開關,只對本條消息有效,ForbidBeforeSendMsgCallback 表示禁止發消息前回調,* ForbidAfterSendMsgCallback 表示禁止發消息后回調* 選填*/@JsonProperty("ForbidCallbackControl")private Object[] forbidCallbackControl;/*** 消息發送控制選項,是一個 String 數組,只對本條消息有效。"NoUnread"表示該條消息不計入未讀數。* "NoLastMsg"表示該條消息不更新會話列表。"WithMuteNotifications"表示該條消息的接收方對發送方設置的免打擾選項生效(默認不生效)。* 示例:"SendMsgControl": ["NoUnread","NoLastMsg","WithMuteNotifications"]* 選填*/@JsonProperty("SendMsgControl")private Array sendMsgControl;/*** 消息內容,具體格式請參考 消息格式描述(注意,一條消息可包括多種消息元素,MsgBody 為 Array 類型)* 必填*/@JsonProperty("MsgBody")private List<ImMsgBodyReq> msgBody;/*** TIM 消息對象類型,目前支持的消息對象包括:* TIMTextElem(文本消息)* TIMLocationElem(位置消息)* TIMFaceElem(表情消息)* TIMCustomElem(自定義消息)* TIMSoundElem(語音消息)* TIMImageElem(圖像消息)* TIMFileElem(文件消息)* TIMVideoFileElem(視頻消息)* 必填*/@JsonProperty("MsgType")private String msgType;/*** 對于每種 MsgType 用不同的 MsgContent 格式,具體可參考* 必填*/@JsonProperty("MsgContent")private Object msgContent;/*** 消息自定義數據(云端保存,會發送到對端,程序卸載重裝后還能拉取到)* 選填*/@JsonProperty("CloudCustomData")private String cloudCustomData;/*** 離線推送信息配置,具體可參考* 選填*/@JsonProperty("OfflinePushInfo")private Object offlinePushInfo;/*** 自定義消息數據。 不作為 APNs 的 payload 字段下發,故從 payload 中無法獲取 Data 字段*/@JsonProperty("Data")private String data;/*** 說明* 當消息中只有一個 TIMCustomElem 自定義消息元素時,如果 Desc 字段和 OfflinePushInfo.Desc 字段都不填寫,* 將收不到該條消息的離線推送,需要填寫 OfflinePushInfo.Desc 字段才能收到該消息的離線推送。*/@JsonProperty("Desc")private String desc;/*** 擴展字段。當接收方為 iOS 系統且應用處在后臺時,此字段作為 APNs 請求包 Payloads 中的 Ext 鍵值下發,Ext 的協議格式由業務方確定,APNs 只做透傳。*/@JsonProperty("Ext")private String ext;/*** 自定義 APNs 推送鈴音。*/@JsonProperty("Sound")private String sound;}
Base64Url.java
public class Base64Url {public static byte[] base64EncodeUrl(byte[] input) {byte[] base64 = Base64.getEncoder().encode(input);for (int i = 0; i < base64.length; ++i) {switch (base64[i]) {case '+':base64[i] = '*';break;case '/':base64[i] = '-';break;case '=':base64[i] = '_';break;default:break;}}return base64;}
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/16896.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/16896.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/16896.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Apache Doris 基礎(一) -- Getting Started

Apache Doris 開源、實時數據倉庫 Apache Doris是一個用于實時分析的現代數據倉庫。 它提供大規模閃電般的實時數據分析。 實時獲取和存儲 在一秒鐘內基于推的微批處理和基于拉的流數據獲取。實時更新&#xff0c;追加和預聚合的存儲引擎閃電般的查詢 使用列式存儲引擎、MPP架構…

CDGP|數據治理策略揭秘:因企制宜,實現精準管控新高度

隨著信息化、數字化的深入推進&#xff0c;數據已經成為企業最重要的資產之一。如何高效、安全地管理和利用數據&#xff0c;成為企業面臨的重要課題。數據治理策略的制定與實施&#xff0c;成為解決這一問題的關鍵所在。本文將探討如何因企制宜&#xff0c;制定符合企業實際情…

23種設計模式全面總結 | 快速復習(附PDF+MD版本)

本篇文章是對于23種設計模式的一個全面的總結&#xff0c;受限于文章篇幅無法對每個設計模式做到全面的解析&#xff0c;但幾乎每個設計模式都提供了案例和類圖結構&#xff0c;非常適合快速復習和在學習設計模式之前的全預習把握。 &#x1f4a1;文章的 pdf markdown 版本可通…

Mysql的復制技術

一、異步復制&#xff1a; 主服務器上的事務更新了數據后&#xff0c;就不管從服務器是否立刻跟上&#xff0c;主服務器繼續處理其他事務&#xff0c;而從服務器會在它空閑的時候去檢查并應用這些更新。 ——老師&#xff08;源服務器&#xff09;給學生&#xff08;從服務器&…

JSP期末要點復習

一、JSP工作原理 1.客戶端請求JSP頁面&#xff1a;用戶通過瀏覽器發送一個請求到服務器&#xff0c;請求一個特定的JSP頁面。這個請求被服務器上的Web容器&#xff08;如Apache Tomcat&#xff09;接收。 2.JSP轉換為Servlet&#xff1a;當JSP頁面第一次被請求時&#xff0…

一戰中海洋敗,二戰梭哈兩電一郵!

這個系列會邀請上岸學長學姐進行經驗分享~ 經驗分享 大家好哇&#xff0c;能寫這個帖子&#xff0c;本人倍感榮幸。 先介紹一下個人情況吧&#xff0c;我本科就讀于中北大學&#xff0c;GPA&#xff1a;3.61/5&#xff0c;有電子類競賽&#xff0c;大創項目&#xff0c;大學…

JavaSE 字符串String及相關API StringBuilder StringJoiner 底層原理 詳解

字符串和相關API java不會字符串即涼一半 學好字符串很重要 API 為應用程序編程接口 獲得字符串對象 1.直接賦值 空參構造 string s1“abc”; s1 記錄的是串池里的地址 2.用new的方式 string s2new string&#xff08;&#xff09;; new&#xff08;在堆內存里開辟空…

opencv調用攝像頭保存視頻

opencv調用攝像頭保存視頻 文章目錄 opencv調用攝像頭保存視頻保存視頻&#xff08;采用默認分辨率640 x 480)保存視頻&#xff08;指定分辨率&#xff0c;例1280720) 保存視頻&#xff08;采用默認分辨率640 x 480) import cv2 import time # 定義視頻捕捉對象 cap cv2.Vide…

SD4054單節鋰電子恒定電壓線性充電器SOT-23-5封裝電源適配器

SD4054是一款完整的單節鋰離子電池采用恒定電流/恒定電壓線性充電器。它采用的 SOT-23-5封裝&#xff0c;只需外接極少的外部元件&#xff0c;使得SD4054成為便攜式應用的理想選擇。 SD4054可以適合USB電源和適配器電源工作。 采用了內部PMOSFET架構&#xff0c;加上防倒充電路…

數據安全革命:Web3帶來的隱私保護創新

隨著數字化時代的發展&#xff0c;數據安全和隱私保護問題日益突出。傳統的中心化數據存儲和管理方式已經無法滿足日益增長的數據安全需求&#xff0c;而Web3作為下一代互聯網的新興力量&#xff0c;正以其去中心化、加密安全的特性&#xff0c;引領著一場數據安全革命。本文將…

pyinstaller打包提示“文件所在的卷已被外部更改,因此打開的文件不再有效。”

環境 anaconda : 24.1.2python : 3.7.13pyinstaller : 5.13.0 問題描述 之前使用pyintaller執行spec文件打包都是能成功&#xff0c;今天打包報了“文件所在的卷已被外部更改&#xff0c;因此打開的文件不再有效。”的錯誤 Traceback (most recent call last):File "C…

「架構」微服務

微服務架構是一種軟件開發架構,它將應用程序作為一組小的服務構建,每個服務實現特定的業務功能,并通過輕量級的通信機制(通常是HTTP RESTful API)進行交互。這些服務是松耦合的,可以獨立部署、擴展和更新。 核心功能: 服務分解:將應用程序分解為一組小型、獨立的服務。…

ecc dsa rsa des

ECC&#xff08;橢圓曲線密碼學&#xff09;、DSA&#xff08;數字簽名算法&#xff09;、RSA&#xff08;一種公鑰加密技術&#xff09;和DES&#xff08;數據加密標準&#xff09;都是密碼學領域中重要的加密和安全技術。下面是對這四種技術的簡要介紹&#xff1a; 橢圓曲線密…

想提升,應該學PMP還是NPDP?

NPDP&#xff08;新產品開發專業認證&#xff09;是由美國產品開發與管理協會&#xff08;PDMA&#xff09;發起的國際認證&#xff0c;涵蓋新產品開發的理論、方法和實踐&#xff0c;為公司提供全方位的知識體系支持。通過考試獲得NPDP認證證書&#xff0c;能夠提升個人工作能…

條款8:了解各種不同意義的new和delete

有時候我們覺得&#xff0c;C的術語仿佛是要故意讓人難以理解似的。 這里就有一個例子&#xff1a;請說明new operator 和operator new 之間的差異&#xff08;譯注&#xff1a;本書所說的new operator&#xff0c;即某些C教程如C Primer 所謂的new expression) 當你寫出這樣…

粒子愛心特效||輕松實現浪漫效果||完整代碼

關注微信公眾號「ClassmateJie」有完整代碼以及更多驚喜等待你的發現。 簡介/效果展示 你是否曾經想過&#xff0c;在特殊的日子里給你的愛人一個驚喜&#xff1f;或者在朋友的生日派對上&#xff0c;給他們展示一個充滿愛意的特效&#xff1f;今天&#xff0c;我要分享一個我…

VUE3-form表單保存附件與基本信息

element-ui代碼 <el-dialog :title"上傳附件" v-model"dialogAdds.visible" width"500px" append-to-body> <el-form-item label"唯一標識"> <dict-tag v-if"form.groupId" :options"unique_identifica…

[大師C語言(第十二篇)]C語言堆排序技術詳解

引言 堆排序&#xff08;Heap Sort&#xff09;是一種基于比較的排序算法&#xff0c;它利用堆這種數據結構的特點來進行排序。堆是一種近似完全二叉樹的結構&#xff0c;并同時滿足堆積的性質&#xff1a;即子節點的鍵值或索引總是小于&#xff08;或者大于&#xff09;它的父…

性能怪獸!香橙派 Kunpeng Pro 開發板深度測評,帶你解鎖無限可能

性能怪獸&#xff01;香橙派 Kunpeng Pro 開發板深度測評&#xff0c;帶你解鎖無限可能 文章目錄 性能怪獸&#xff01;香橙派 Kunpeng Pro 開發板深度測評&#xff0c;帶你解鎖無限可能一、背景二、香橙派 Kunpeng Pro 硬件規格概述三、使用準備與系統安裝1??、系統安裝步驟…