|
@@ -2,12 +2,14 @@ package com.tzld.piaoquan.risk.control.service.impl;
|
|
|
|
|
|
import com.alibaba.fastjson.JSON;
|
|
|
import com.tzld.piaoquan.risk.control.config.QywxConfig;
|
|
|
+import com.tzld.piaoquan.risk.control.dao.mapper.UserBaseMapper;
|
|
|
import com.tzld.piaoquan.risk.control.dao.mapper.WorkWechatRoomInfoMapper;
|
|
|
import com.tzld.piaoquan.risk.control.model.po.UserBase;
|
|
|
+import com.tzld.piaoquan.risk.control.model.po.UserBaseExample;
|
|
|
import com.tzld.piaoquan.risk.control.model.po.WorkWechatRoomInfo;
|
|
|
import com.tzld.piaoquan.risk.control.model.po.WorkWechatRoomInfoExample;
|
|
|
import com.tzld.piaoquan.risk.control.model.qywx.*;
|
|
|
-import com.tzld.piaoquan.risk.control.service.BlacklistService;
|
|
|
+import com.tzld.piaoquan.risk.control.service.CorpBlacklistService;
|
|
|
import com.tzld.piaoquan.risk.control.util.HttpClientUtil;
|
|
|
import com.tzld.piaoquan.risk.control.util.HttpPoolClient;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
@@ -28,147 +30,182 @@ public class RiskUserOperateService {
|
|
|
@Autowired
|
|
|
private WorkWechatRoomInfoMapper workWechatRoomInfoMapper;
|
|
|
|
|
|
- /**
|
|
|
- * 黑名单管理服务,用于替换硬编码的黑名单检查逻辑
|
|
|
- */
|
|
|
@Autowired
|
|
|
- private BlacklistService blacklistService;
|
|
|
+ private CorpBlacklistService corpBlacklistService;
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private UserBaseMapper userBaseMapper;
|
|
|
+
|
|
|
@Value("${qywx.corpid}")
|
|
|
private long corpid;
|
|
|
private static final HttpPoolClient httpPoolClientDefault = HttpClientUtil.create(10000, 10000, 2000, 5000, 5, 10000);
|
|
|
- private static final Logger logger = LoggerFactory.getLogger(RiskUserHandleService.class);
|
|
|
-
|
|
|
- /*
|
|
|
- * 原硬编码黑名单常量已移除,改为使用数据库动态管理
|
|
|
- *
|
|
|
- * 迁移说明:
|
|
|
- * 1. 原BLACKLIST_VID常量包含约120个用户VID
|
|
|
- * 2. 这些VID已通过管理界面手动迁移到qywx_blacklist_user表中
|
|
|
- * 3. 现在通过BlacklistService.isInBlacklist()方法进行黑名单检查
|
|
|
- * 4. 支持动态添加、删除黑名单用户,无需重启服务
|
|
|
- *
|
|
|
- * 原硬编码数据备份(仅供参考,已迁移到数据库):
|
|
|
- * private static final List<Long> BLACKLIST_VID = Collections.unmodifiableList(
|
|
|
- * Arrays.asList(
|
|
|
- * 7881302926289574L, 7881299753025521L, 7881300466122683L, ...
|
|
|
- * // 共约120个VID,完整列表见数据迁移文档
|
|
|
- * )
|
|
|
- * );
|
|
|
- */
|
|
|
+ private static final Logger LOGGER = LoggerFactory.getLogger(RiskUserHandleService.class);
|
|
|
+// private static final List<Long> BLACKLIST_VID = Collections.unmodifiableList(Arrays.asList(7881302926289574L,7881299753025521L));
|
|
|
+ // 初始化不可变黑名单(合并现有值和JSON中的新值)
|
|
|
+ private static final List<Long> BLACKLIST_VID = Collections.unmodifiableList(
|
|
|
+ Arrays.asList(
|
|
|
+ // 原始值
|
|
|
+ 7881302926289574L,
|
|
|
+ 7881299753025521L,
|
|
|
+ // 从JSON数据中提取的所有blacklist_vid(已去重)
|
|
|
+ 7881300466122683L, 7881300229048031L, 7881302316912185L,
|
|
|
+ 7881301869028248L, 7881301211163085L, 7881302796091767L,
|
|
|
+ 7881301324066702L, 7881300281998722L, 7881301967034720L,
|
|
|
+ 7881301678337366L, 7881300378068659L, 7881302223127319L,
|
|
|
+ 7881299679315751L, 7881302327344543L, 7881303556905502L,
|
|
|
+ 7881300420063544L, 7881303114007258L, 7881299609215302L,
|
|
|
+ 7881303411344664L, 7881300499073181L, 7881302121982115L,
|
|
|
+ 7881301622998984L, 7881302715121782L, 7881301469189163L,
|
|
|
+ 7881301825004114L, 7881303480126257L, 7881303502084260L,
|
|
|
+ 7881300158073529L, 7881300519959317L, 7881301866079037L,
|
|
|
+ 7881303096191474L, 7881303368954132L, 7881302764132335L,
|
|
|
+ 7881302001968032L, 7881302064983891L, 7881303276328299L,
|
|
|
+ 7881302018273598L, 7881303641326502L, 7881301501262524L,
|
|
|
+ 7881301460217143L, 7881299789313131L, 7881300854349663L,
|
|
|
+ 7881301505271795L, 7881300193294802L, 7881300949997575L,
|
|
|
+ 7881302066324600L, 7881301257978308L, 7881303083154066L,
|
|
|
+ 7881300846203333L, 7881301661295267L, 7881300149063751L,
|
|
|
+ 7881303421311587L, 7881300317046467L, 7881301426914509L,
|
|
|
+ 7881301034118168L, 7881303360258534L, 7881300540110306L,
|
|
|
+ 7881303335206573L, 7881301178336419L, 7881299476125798L,
|
|
|
+ 7881302710301946L, 7881301346342921L, 7881300543276102L,
|
|
|
+ 7881302549156012L, 7881300984974896L, 7881300905339945L,
|
|
|
+ 7881300014351503L, 7881299626065317L, 1688858033727935L,
|
|
|
+ 7881303486038982L, 7881301946106537L, 7881303093341615L,
|
|
|
+ 7881300064125930L, 7881301841001514L, 7881303046088935L,
|
|
|
+ 7881302404997826L, 7881302075019397L, 7881300148942179L,
|
|
|
+ 7881299647166280L, 7881299915041354L, 7881300444249607L,
|
|
|
+ 7881300767993441L, 7881302633972298L, 7881300416188902L,
|
|
|
+ 7881303513904106L, 7881300749032748L, 7881300268925255L,
|
|
|
+ 7881303315067789L, 7881300808351909L, 7881300791125480L,
|
|
|
+ 7881302408161558L, 7881302137137423L, 7881301119340365L,
|
|
|
+ 7881302367951032L, 7881301267044221L, 7881301416981370L,
|
|
|
+ 7881300843989486L, 7881302471324028L, 7881301871965471L,
|
|
|
+ 7881301393187836L, 7881301698289157L, 7881303388311144L,
|
|
|
+ 7881300820304366L, 7881299665950989L, 7881301992001273L,
|
|
|
+ 7881302286953152L, 7881299836040719L, 7881300144150372L,
|
|
|
+ 7881302832139110L, 7881302260054092L, 7881300488072079L,
|
|
|
+ 7881299606154483L, 7881302132328341L, 7881301177020994L,
|
|
|
+ 7881299603349260L, 7881301200004722L, 7881300437123751L,
|
|
|
+ 7881301055969437L, 7881301654914877L, 7881300090992802L,
|
|
|
+ 7881300090924829L, 7881300095962888L, 7881301643139014L,
|
|
|
+ 7881303024343207L, 7881302048307942L, 7881303218068450L,
|
|
|
+ 7881301852350459L, 7881301482317014L, 7881301843321743L,
|
|
|
+ 7881302927306074L, 7881302900372515L, 7881302628314176L,
|
|
|
+ 7881302733026214L, 7881303417300674L, 7881301797041258L,
|
|
|
+ 7881302328298167L, 7881299771077377L, 7881301278033375L,
|
|
|
+ 7881299963273148L, 7881299551165243L, 7881302964181882L,
|
|
|
+ 7881300960043108L, 7881303282925921L, 7881301679355925L,
|
|
|
+ 7881303320029350L, 7881299472340204L, 7881303594961434L,
|
|
|
+ 7881300778933287L, 7881303467988227L, 7881299452963981L,
|
|
|
+ 7881301668344400L, 7881303357010444L, 7881303133173876L,
|
|
|
+ 7881303227934766L, 7881300066348945L, 7881301311983065L,
|
|
|
+ 7881302496352111L, 7881299632359909L, 7881303344140343L,
|
|
|
+ 7881301136317724L, 7881301988332126L, 7881300283335703L,
|
|
|
+ 7881301475333555L, 7881301678333870L
|
|
|
+ )
|
|
|
+);
|
|
|
|
|
|
- /**
|
|
|
- * 快速踢人功能 - 当检测到新成员加入群聊时,立即执行踢人操作
|
|
|
- *
|
|
|
- * 重构说明:使用数据库查询替换硬编码的黑名单检查
|
|
|
- *
|
|
|
- * @param message 群聊新增成员消息,包含新加入的成员信息
|
|
|
- * @param uuid 企业微信用户的唯一标识符
|
|
|
- * @return true-踢人成功,false-踢人失败或不满足踢人条件
|
|
|
- */
|
|
|
public boolean quickKick(RoomAddMemberMessage message,String uuid) {
|
|
|
- // 检查是否为群聊消息(isRoom=1表示群聊,0表示私聊)
|
|
|
if(message.getIsRoom() == 1) {
|
|
|
- // 构建请求参数
|
|
|
Map<String, Object> requestBody = new HashMap<>();
|
|
|
- requestBody.put("uuid", uuid); // 操作者的企业微信UUID
|
|
|
- requestBody.put("oprType", 1); // 操作类型:1-踢出用户
|
|
|
-
|
|
|
- // 检查成员列表是否为空
|
|
|
+ requestBody.put("uuid", uuid);
|
|
|
+ requestBody.put("oprType", 1);
|
|
|
if(message.getMemberList().isEmpty()) {
|
|
|
- logger.warn("quickKick: member list is empty, skipping operation");
|
|
|
return false;
|
|
|
}
|
|
|
+ boolean inBlacklist = false;
|
|
|
|
|
|
- // 获取第一个新加入的成员VID(企业微信内部用户ID)
|
|
|
long vid = message.getMemberList().get(0);
|
|
|
+ // 根据 vid 判断是否在全局黑名单中
|
|
|
+ if(BLACKLIST_VID.contains(vid)) {
|
|
|
+ LOGGER.info("quickKick, vid: {} is in blacklist", vid);
|
|
|
+ inBlacklist = true;
|
|
|
+ }
|
|
|
|
|
|
- // 黑名单检查 - 使用数据库查询替换硬编码检查
|
|
|
- try {
|
|
|
- if( !blacklistService.isInBlacklist(vid)) {
|
|
|
- logger.info("quickKick: vid {} is not in blacklist, skip kick operation", vid);
|
|
|
- return false;
|
|
|
+ // 根据 vid 判断是否在企业黑名单中
|
|
|
+ if ( !inBlacklist ) {
|
|
|
+ // 根据 uuid 查询 corp_id
|
|
|
+ Long corpId = corpBlacklistService.getCorpIdByUuid(uuid);
|
|
|
+ // 根据 corp_id 和 vid判断是否在企业黑名单中
|
|
|
+ if( corpBlacklistService.isInCorpBlacklist(vid, corpId)) {
|
|
|
+ LOGGER.info("quickKick, vid: {} is in corp blacklist", vid);
|
|
|
+ inBlacklist = true;
|
|
|
}
|
|
|
- } catch (Exception e) {
|
|
|
- logger.error("quickKick: failed to check blacklist for vid {}, skipping kick for safety", vid, e);
|
|
|
- return false;
|
|
|
}
|
|
|
|
|
|
- // 群聊,直接踢出
|
|
|
- logger.info("quickKick, message: {}, uuid: {}", message, uuid);
|
|
|
+ // 如果不在黑名单中,直接返回
|
|
|
+ if( !inBlacklist ) {
|
|
|
+ LOGGER.info("quickKick, vid: {} is not in any blacklist, skip kick", vid);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
|
|
|
- // 设置要踢出的用户VID列表
|
|
|
+ // 如果是群聊,直接踢出
|
|
|
+ LOGGER.info("quickKick, message: {}, uuid: {},timestamp {}", message, uuid,System.currentTimeMillis());
|
|
|
requestBody.put("blacklist_vid",Arrays.asList(message.getMemberList().get(0)));
|
|
|
-
|
|
|
- // 将请求参数转换为JSON字符串
|
|
|
String params = JSON.toJSONString(requestBody);
|
|
|
-
|
|
|
- // 调用第三方企业微信管理工具的踢人接口
|
|
|
Optional<String> response = httpPoolClientDefault.postJson(qywxConfig.getDomain() + qywxConfig.getPath("kick-external"), params);
|
|
|
-
|
|
|
- // 处理API响应结果
|
|
|
if (response.isPresent()) {
|
|
|
- // 解析响应JSON
|
|
|
QwCommonResModel<RoomListResponse> roomInfo = QwCommonResModel.parseResponse(response.get(), RoomListResponse.class);
|
|
|
-
|
|
|
- // 检查操作是否成功(errcode=0表示成功)
|
|
|
if (roomInfo.getErrcode() == 0) {
|
|
|
- logger.info("quick Kick external user {} from room {} successfully", vid);
|
|
|
+ LOGGER.info("quick Kick external user {} successfully,timestamp {}", vid,System.currentTimeMillis());
|
|
|
+ // 加入黑名单
|
|
|
+ corpBlacklistService.addToCorpBlacklist( vid, corpid);
|
|
|
return true;
|
|
|
} else {
|
|
|
- // 踢人失败,记录错误信息
|
|
|
- logger.error("Failed to kick external user {} f: {}", vid, roomInfo.getErrmsg());
|
|
|
+ LOGGER.error("Failed to kick external user {} f: {},", vid, roomInfo.getErrmsg());
|
|
|
}
|
|
|
} else {
|
|
|
- // API调用失败,没有收到响应
|
|
|
- logger.error("Failed to kick external user {} from : No response", vid);
|
|
|
+ LOGGER.error("Failed to kick external user {} from : No response", vid);
|
|
|
}
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
|
|
|
- // 默认返回失败
|
|
|
+ public boolean checkAndKickExternalUser(UserBase staff,RiskUserInfo riskUserInfo,long vid,long roomId) {
|
|
|
+ LOGGER.info("checkAndKickExternalUser, staff: {}, riskUserInfo: {}, vid: {}, roomId: {}", staff, riskUserInfo, vid, roomId);
|
|
|
+ boolean inRoom = isInRoom(staff, roomId, vid);
|
|
|
+ if (!inRoom) {
|
|
|
+ LOGGER.info("User {} is not in room {}", vid, roomId);
|
|
|
return false;
|
|
|
}
|
|
|
+ LOGGER.info("User {} is in room {}", vid, roomId);
|
|
|
+ kick(staff, roomId, vid);
|
|
|
|
|
|
- // 如果不是群聊消息,直接返回失败
|
|
|
- return false;
|
|
|
+ return true;
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 踢人功能 - 将指定用户从指定群聊中踢出
|
|
|
- *
|
|
|
- * 重构说明:使用数据库查询替换硬编码的黑名单检查
|
|
|
- *
|
|
|
- * @param staff 执行踢人操作的员工信息
|
|
|
- * @param roomId 群聊ID
|
|
|
- * @param vid 要踢出的用户VID
|
|
|
- * @return true-踢人成功或跳过,false-踢人失败
|
|
|
- */
|
|
|
public boolean kick(UserBase staff, long roomId, long vid) {
|
|
|
- // 黑名单检查 - 使用数据库查询替换硬编码检查
|
|
|
- try {
|
|
|
- if(blacklistService.isInBlacklist(vid)) {
|
|
|
- logger.info("kick: vid {} is in blacklist, skip normal kick operation", vid);
|
|
|
- return true;
|
|
|
- }
|
|
|
- logger.debug("kick: vid {} is not in blacklist, proceeding with kick operation", vid);
|
|
|
- } catch (Exception e) {
|
|
|
- logger.error("kick: failed to check blacklist for vid {}", vid, e);
|
|
|
+ if(BLACKLIST_VID.contains(vid)) {
|
|
|
+ LOGGER.info("kick, vid: {} is in blacklist, skip normal kick", vid);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ // 根据 vid 判断是否在企业黑名单中
|
|
|
+ // 根据 uuid 查询 corp_id
|
|
|
+ Long corpId = corpBlacklistService.getCorpIdByUuid(staff.getUuid());
|
|
|
+ // 根据 corp_id 和 vid判断是否在企业黑名单中
|
|
|
+ if( corpBlacklistService.isInCorpBlacklist(vid, corpId)) {
|
|
|
+ LOGGER.info("quickKick, vid: {} is in corp blacklist", vid);
|
|
|
+ return true;
|
|
|
}
|
|
|
Map<String, Object> requestBody = new HashMap<>();
|
|
|
requestBody.put("uuid", staff.getUuid());
|
|
|
requestBody.put("oprType", 1);
|
|
|
requestBody.put("blacklist_vid",Arrays.asList(vid));
|
|
|
String params = JSON.toJSONString(requestBody);
|
|
|
- logger.info("kick, staff: {}, params: {}", staff, params);
|
|
|
+ LOGGER.info("kick, staff: {}, params: {}", staff, params);
|
|
|
Optional<String> response = httpPoolClientDefault.postJson(qywxConfig.getDomain() + qywxConfig.getPath("kick-external"), params);
|
|
|
if (response.isPresent()) {
|
|
|
QwCommonResModel<RoomListResponse> roomInfo = QwCommonResModel.parseResponse(response.get(), RoomListResponse.class);
|
|
|
if (roomInfo.getErrcode() == 0) {
|
|
|
- logger.info("Kick external user {} from room {} successfully", vid, roomId);
|
|
|
+ LOGGER.info("Kick external user {} from room {} successfully", vid, roomId);
|
|
|
return true;
|
|
|
} else {
|
|
|
- logger.error("Failed to kick external user {} from room {}: {}", vid, roomId, roomInfo.getErrmsg());
|
|
|
+ LOGGER.error("Failed to kick external user {} from room {}: {}", vid, roomId, roomInfo.getErrmsg());
|
|
|
}
|
|
|
} else {
|
|
|
- logger.error("Failed to kick external user {} from room {}: No response", vid, roomId);
|
|
|
+ LOGGER.error("Failed to kick external user {} from room {}: No response", vid, roomId);
|
|
|
}
|
|
|
return false;
|
|
|
}
|
|
@@ -177,12 +214,12 @@ public class RiskUserOperateService {
|
|
|
public boolean isInRoom(UserBase staff, long roomId,long vid) {
|
|
|
List<RoomMemberListInfo.Member> memberList = getRoomMemberList(staff, roomId);
|
|
|
if (memberList == null || memberList.isEmpty()) {
|
|
|
- logger.info("isInRoom, memberList is empty");
|
|
|
+ LOGGER.info("isInRoom, memberList is empty");
|
|
|
return false;
|
|
|
}
|
|
|
- logger.info("isInRoom, memberList: {}", memberList);
|
|
|
+ LOGGER.info("isInRoom, memberList: {}", memberList);
|
|
|
for (RoomMemberListInfo.Member member : memberList) {
|
|
|
- logger.info("isInRoom, member: {}", member);
|
|
|
+ LOGGER.info("isInRoom, member: {}", member);
|
|
|
if (member.getUin() == vid && member.getJoinScene() == 3) {
|
|
|
return true;
|
|
|
}
|
|
@@ -197,7 +234,7 @@ public class RiskUserOperateService {
|
|
|
String params = JSON.toJSONString(requestBody);
|
|
|
String url = qywxConfig.getDomain() + qywxConfig.getPath("get-roomMembers");
|
|
|
Optional<String> response = httpPoolClientDefault.postJson(url, params);
|
|
|
- logger.info("getRoomMemberList, params: {}, response: {},url: {}", params, response, url);
|
|
|
+ LOGGER.info("getRoomMemberList, params: {}, response: {},url: {}", params, response, url);
|
|
|
List<RoomMemberListInfo.Member> memberList = new ArrayList<>();
|
|
|
if (response.isPresent()) {
|
|
|
QwCommonResModel<RoomMemberListInfo> memberInfo = QwCommonResModel.parseResponse(response.get(), RoomMemberListInfo.class);
|
|
@@ -222,4 +259,74 @@ public class RiskUserOperateService {
|
|
|
log.info("forceCloseRoomSwitch, roomId: {}, autoRemoveUserSwitch set to 0", roomId);
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 查询群黑名单
|
|
|
+ *
|
|
|
+ * 调用企业微信API获取指定群的黑名单用户列表
|
|
|
+ *
|
|
|
+ * @param uuid 企业微信用户UUID
|
|
|
+ * @return 黑名单查询响应结果
|
|
|
+ */
|
|
|
+ public RoomBlacklistResponse getRoomBlacklist(String uuid) {
|
|
|
+ if (uuid == null || uuid.trim().isEmpty()) {
|
|
|
+ LOGGER.error("getRoomBlacklist called with null or empty uuid");
|
|
|
+ return createErrorResponse(-1, "UUID不能为空");
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 构建请求参数
|
|
|
+ Map<String, Object> requestBody = new HashMap<>();
|
|
|
+ requestBody.put("uuid", uuid);
|
|
|
+ requestBody.put("limit", 1000);
|
|
|
+
|
|
|
+ // 构建API URL
|
|
|
+ String url = qywxConfig.getDomain() + qywxConfig.getPath("get-room-black-list");
|
|
|
+
|
|
|
+ LOGGER.info("Calling getRoomBlacklist API, uuid: {}, limit: {}", uuid, 1000);
|
|
|
+
|
|
|
+ // 调用API
|
|
|
+ Optional<String> response = httpPoolClientDefault.postJson(url, JSON.toJSONString(requestBody.toString()));
|
|
|
+
|
|
|
+ if (response.isPresent()) {
|
|
|
+ String responseBody = response.get();
|
|
|
+ LOGGER.info("getRoomBlacklist API response: {}", responseBody);
|
|
|
+
|
|
|
+ // 解析响应
|
|
|
+ RoomBlacklistResponse result = JSON.parseObject(responseBody, RoomBlacklistResponse.class);
|
|
|
+
|
|
|
+ if (result != null && result.getErrcode() == 0) {
|
|
|
+ LOGGER.info("Successfully retrieved room blacklist, uuid: {}, count: {}",
|
|
|
+ uuid, result.getData() != null ? result.getData().getSum() : 0);
|
|
|
+ } else {
|
|
|
+ LOGGER.warn("getRoomBlacklist API returned error, uuid: {}, errcode: {}, errmsg: {}",
|
|
|
+ uuid, result != null ? result.getErrcode() : -1,
|
|
|
+ result != null ? result.getErrmsg() : "解析失败");
|
|
|
+ }
|
|
|
+
|
|
|
+ return result;
|
|
|
+ } else {
|
|
|
+ LOGGER.error("getRoomBlacklist API call failed, no response received, uuid: {}", uuid);
|
|
|
+ return createErrorResponse(-1, "API调用失败,未收到响应");
|
|
|
+ }
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ LOGGER.error("Exception occurred while calling getRoomBlacklist API, uuid: {}", uuid, e);
|
|
|
+ return createErrorResponse(-1, "API调用异常: " + e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 创建错误响应
|
|
|
+ *
|
|
|
+ * @param errcode 错误码
|
|
|
+ * @param errmsg 错误信息
|
|
|
+ * @return 错误响应对象
|
|
|
+ */
|
|
|
+ private RoomBlacklistResponse createErrorResponse(int errcode, String errmsg) {
|
|
|
+ RoomBlacklistResponse response = new RoomBlacklistResponse();
|
|
|
+ response.setErrcode(errcode);
|
|
|
+ response.setErrmsg(errmsg);
|
|
|
+ return response;
|
|
|
+ }
|
|
|
}
|