package com.xiaopeng.operation.service.impl;
import cn.hutool.core.bean.BeanUtil;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xiaopeng.api.dto.pushMessage.AliPushMessageDTO;
import com.xiaopeng.api.feignClient.account.XpOperationUserClient;
import com.xiaopeng.api.feignClient.account.XpOperationUserNotificationClient;
import com.xiaopeng.api.feignClient.pushMessage.XpAliPushMessageClient;
import com.xiaopeng.api.model.account.XpOperationsUser;
import com.xiaopeng.api.model.account.XpOperationsUserNotification;
import com.xiaopeng.api.model.operation.XpAgreement;
import com.xiaopeng.common.core.constant.AliPushTypeConstant;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.common.core.enums.NotificationTypeEnum;
import com.xiaopeng.operation.dto.XpAgreementDTO;
import com.xiaopeng.operation.mapper.XpAgreementMapper;
import com.xiaopeng.operation.service.XpAgreementService;
import com.xiaopeng.operation.vo.XpAgreementVo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
*
* 协议设置 服务实现类
*
*
* @author liu
* @since 2026-02-03
*/
@Slf4j
@Service
@RequiredArgsConstructor(onConstructor_ = @Lazy)
public class XpAgreementServiceImpl extends ServiceImpl implements XpAgreementService {
private final XpOperationUserClient xpOperationUserClient;
private final XpOperationUserNotificationClient xpOperationUserNotificationClient;
private final XpAliPushMessageClient xpAliPushMessageClient;
@Override
public XpAgreementVo getContentByType(Integer type) {
XpAgreement xpAgreement = this.getOne(new LambdaQueryWrapper().eq(XpAgreement::getContentType,type));
if (xpAgreement != null) {
XpAgreementVo xpAgreementVo = new XpAgreementVo();
xpAgreementVo.setId(xpAgreement.getId());
xpAgreementVo.setContent(xpAgreement.getContent());
xpAgreementVo.setType(xpAgreement.getContentType());
xpAgreementVo.setVersion(xpAgreement.getVersion());
return xpAgreementVo;
}
return null;
}
@Override
public List getContentByTypeAll() {
List list = this.list(new LambdaQueryWrapper().in(XpAgreement::getContentType, Arrays.asList(1,2,3,4)));
List xpAgreementVos = new ArrayList<>();
for (XpAgreement xpAgreement : list) {
XpAgreementVo xpAgreementVo = new XpAgreementVo();
xpAgreementVo.setId(xpAgreement.getId());
xpAgreementVo.setContent(xpAgreement.getContent());
xpAgreementVo.setType(xpAgreement.getContentType());
xpAgreementVo.setVersion(xpAgreement.getVersion());
xpAgreementVos.add(xpAgreementVo);
}
return xpAgreementVos;
}
/**
* 根据类型获取配置
* @param type
* @return
*/
@Override
public XpAgreement getAgreementContent(Integer type) {
return this.lambdaQuery().eq(XpAgreement::getContentType, type).last("limit 1").one();
}
@Override
public void saveAgreement(XpAgreementDTO dto) {
//根据类型查询协议
Optional opt = this.lambdaQuery().eq(XpAgreement::getContentType, dto.getContentType()).last("limit 1").oneOpt();
//如果存在则更新,不存在则新增
if (opt.isPresent()) {
XpAgreement xpAgreement = opt.get();
xpAgreement.setContent(dto.getContent());
updateById(xpAgreement);
} else {
XpAgreement xpAgreement = BeanUtil.copyProperties(dto, XpAgreement.class);
this.save(xpAgreement);
}
// 判断是否需要发送通知 8=运维APP-小鹏Robotaxi自动驾驶出行运维隐私政策 9=运维APP-第三方共享信息清单 10=运维APP-个人信息收集清单 11=运维APP-权限调用
if (dto.getContentType() != null && (dto.getContentType() == 8 || dto.getContentType() == 9 || dto.getContentType() == 10 || dto.getContentType() == 11)) {
// 发送协议更新提示通知(type=9)
sendAgreementUpdateNotification(dto.getContentType());
// 发送协议授权通知(type=10)
sendAgreementAuthNotification(dto.getContentType());
}
}
/**
* 发送协议更新通知
* @param contentType 协议类型
*/
private void sendAgreementUpdateNotification(Integer contentType) {
// 异步处理,避免阻塞主流程
ThreadPoolExecutor executor = new ThreadPoolExecutor(
1, 1, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue()
);
executor.execute(() -> {
try {
// 1. 获取所有启用状态的运维用户
R> result = xpOperationUserClient.listAllEnabled();
if (result == null || result.getCode() != 200 || result.getData() == null || result.getData().isEmpty()) {
log.warn("没有需要通知的启用状态用户");
return;
}
List enabledUsers = result.getData();
log.info("协议更新通知,共需通知 {} 位用户", enabledUsers.size());
// 2. 构建通知内容
String agreementTypeName = getAgreementTypeName(contentType);
String currentDate = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
String message = String.format("尊敬的用户,你好!%s已于%s更新了内容,可前往个人中心-协议与政策查看详情", agreementTypeName, currentDate);
String bodyJson = buildAgreementPushBodyJson(message);
// 3. 构建并保存通知记录
List notifications = enabledUsers.stream()
.map(user -> buildAgreementNotification(message, bodyJson, user))
.collect(Collectors.toList());
// 批量保存通知记录
xpOperationUserNotificationClient.addNotificationBatch(notifications);
log.info("协议更新通知记录保存成功,共 {} 条", notifications.size());
// 4. 按设备类型分组并批量推送
pushMessageByDeviceType(enabledUsers, message);
} catch (Exception e) {
log.error("发送协议更新通知失败", e);
}
});
executor.shutdown();
}
/**
* 获取协议类型名称
* @param contentType 协议类型
* @return 协议类型名称
*/
private String getAgreementTypeName(Integer contentType) {
switch (contentType) {
case 8:
return "小鹏Robotaxi自动驾驶出行运维隐私政策";
case 9:
return "第三方共享信息清单";
case 10:
return "个人信息收集清单";
case 11:
return "权限调用";
}
return "协议";
}
/**
* 构建推送 body JSON
* @param message 消息内容
* @return JSON 字符串
*/
private String buildAgreementPushBodyJson(String message) {
JSONObject body = new JSONObject();
body.put("content", message);
body.put("strategy", NotificationTypeEnum.POLICY_CONTENT_CHANGED.getCode()); // 9
body.put("type", 0);
body.put("id", 0);
return body.toJSONString();
}
/**
* 构建通知实体
* @param message 消息内容
* @param bodyJson 推送body内容
* @param user 运维用户
* @return 通知实体
*/
private XpOperationsUserNotification buildAgreementNotification(String message, String bodyJson, XpOperationsUser user) {
XpOperationsUserNotification notification = new XpOperationsUserNotification();
notification.setNotificationType(NotificationTypeEnum.POLICY_CONTENT_CHANGED.getCode()); // 9
notification.setNotificationContent(message);
notification.setOperationsUserId(user.getId());
notification.setDataId(0L);
notification.setReadStatus(0); // 未读
notification.setNotificationExt(bodyJson);
return notification;
}
/**
* 按设备类型分组推送消息
* @param users 用户列表
* @param message 消息内容
*/
private void pushMessageByDeviceType(List users, String message) {
// 按设备类型分组 (1=Android, 2=iOS)
Map> usersByDeviceType = users.stream()
.filter(u -> u.getDeviceType() != null && u.getPhoneNumber() != null)
.collect(Collectors.groupingBy(XpOperationsUser::getDeviceType));
// 分别推送 Android 和 iOS
for (Map.Entry> entry : usersByDeviceType.entrySet()) {
Integer deviceType = entry.getKey();
List deviceUsers = entry.getValue();
// 按 1000 个手机号一批进行推送
List> batches = partition(deviceUsers, 1000);
for (List batch : batches) {
String accounts = batch.stream()
.map(XpOperationsUser::getPhoneNumber)
.collect(Collectors.joining(","));
AliPushMessageDTO pushDTO = buildPushMessageDTO(deviceType, accounts, message);
try {
xpAliPushMessageClient.pushMessage(pushDTO);
log.info("协议更新推送成功,设备类型={},用户数={}",
deviceType == 1 ? "Android" : "iOS", batch.size());
} catch (Exception e) {
log.error("协议更新推送失败,设备类型={}", deviceType, e);
}
}
}
}
/**
* 构建推送消息 DTO
* @param deviceType 设备类型
* @param accounts 手机号(逗号分隔)
* @param message 消息内容
* @return 推送 DTO
*/
private AliPushMessageDTO buildPushMessageDTO(Integer deviceType, String accounts, String message) {
AliPushMessageDTO dto = new AliPushMessageDTO();
dto.setDeviceType(deviceType);
dto.setAccount(accounts);
dto.setTitle("协议政策内容更新提示");
dto.setBody(buildAgreementPushBodyJson(message));
dto.setPushType(AliPushTypeConstant.MESSAGE);
return dto;
}
/**
* 将列表按指定大小分批
* @param list 原始列表
* @param batchSize 批次大小
* @return 分批后的列表
*/
private List> partition(List list, int batchSize) {
List> batches = new ArrayList<>();
for (int i = 0; i < list.size(); i += batchSize) {
batches.add(list.subList(i, Math.min(i + batchSize, list.size())));
}
return batches;
}
/**
* 发送协议更新授权通知
* @param contentType 协议类型
*/
private void sendAgreementAuthNotification(Integer contentType) {
// 异步处理,避免阻塞主流程
ThreadPoolExecutor executor = new ThreadPoolExecutor(
1, 1, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue()
);
executor.execute(() -> {
try {
// 1. 获取所有启用状态的运维用户
R> result = xpOperationUserClient.listAllEnabled();
if (result == null || result.getCode() != 200 || result.getData() == null || result.getData().isEmpty()) {
log.warn("没有需要通知的启用状态用户");
return;
}
List enabledUsers = result.getData();
log.info("协议授权通知,共需通知 {} 位用户", enabledUsers.size());
// 2. 构建通知内容
String agreementTypeName = getAgreementTypeName(contentType);
String currentDate = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
String message = String.format("尊敬的用户,你好!%s已于%s更新了内容,需要你前往授权", agreementTypeName, currentDate);
String bodyJson = buildAuthPushBodyJson(message);
// 3. 构建并保存通知记录
List notifications = enabledUsers.stream()
.map(user -> buildAuthNotification(message, bodyJson, user))
.collect(Collectors.toList());
// 批量保存通知记录
xpOperationUserNotificationClient.addNotificationBatch(notifications);
log.info("协议授权通知记录保存成功,共 {} 条", notifications.size());
// 4. 按设备类型分组并批量推送
pushAuthMessageByDeviceType(enabledUsers, message);
} catch (Exception e) {
log.error("发送协议授权通知失败", e);
}
});
executor.shutdown();
}
/**
* 构建授权推送 body JSON
* @param message 消息内容
* @return JSON 字符串
*/
private String buildAuthPushBodyJson(String message) {
JSONObject body = new JSONObject();
body.put("content", message);
body.put("strategy", NotificationTypeEnum.AGREEMENT_CHANGED_NEED_AUTH.getCode()); // 10
body.put("type", 0);
body.put("id", 0);
return body.toJSONString();
}
/**
* 构建授权通知实体
* @param message 消息内容
* @param bodyJson 推送body内容
* @param user 运维用户
* @return 通知实体
*/
private XpOperationsUserNotification buildAuthNotification(String message, String bodyJson, XpOperationsUser user) {
XpOperationsUserNotification notification = new XpOperationsUserNotification();
notification.setNotificationType(NotificationTypeEnum.AGREEMENT_CHANGED_NEED_AUTH.getCode()); // 10
notification.setNotificationContent(message);
notification.setOperationsUserId(user.getId());
notification.setDataId(0L);
notification.setReadStatus(0); // 未读
notification.setNotificationExt(bodyJson);
return notification;
}
/**
* 按设备类型分组推送授权消息
* @param users 用户列表
* @param message 消息内容
*/
private void pushAuthMessageByDeviceType(List users, String message) {
// 按设备类型分组 (1=Android, 2=iOS)
Map> usersByDeviceType = users.stream()
.filter(u -> u.getDeviceType() != null && u.getPhoneNumber() != null)
.collect(Collectors.groupingBy(XpOperationsUser::getDeviceType));
// 分别推送 Android 和 iOS
for (Map.Entry> entry : usersByDeviceType.entrySet()) {
Integer deviceType = entry.getKey();
List deviceUsers = entry.getValue();
// 按 1000 个手机号一批进行推送
List> batches = partition(deviceUsers, 1000);
for (List batch : batches) {
String accounts = batch.stream()
.map(XpOperationsUser::getPhoneNumber)
.collect(Collectors.joining(","));
AliPushMessageDTO pushDTO = buildAuthPushMessageDTO(deviceType, accounts, message);
try {
xpAliPushMessageClient.pushMessage(pushDTO);
log.info("协议授权推送成功,设备类型={},用户数={}",
deviceType == 1 ? "Android" : "iOS", batch.size());
} catch (Exception e) {
log.error("协议授权推送失败,设备类型={}", deviceType, e);
}
}
}
}
/**
* 构建授权推送消息 DTO
* @param deviceType 设备类型
* @param accounts 手机号(逗号分隔)
* @param message 消息内容
* @return 推送 DTO
*/
private AliPushMessageDTO buildAuthPushMessageDTO(Integer deviceType, String accounts, String message) {
AliPushMessageDTO dto = new AliPushMessageDTO();
dto.setDeviceType(deviceType);
dto.setAccount(accounts);
dto.setTitle("协议政策内容更新授权");
dto.setBody(buildAuthPushBodyJson(message));
dto.setPushType(AliPushTypeConstant.NOTICE);
return dto;
}
}