package com.xiaopeng.operation.service.impl; import cn.hutool.core.bean.BeanUtil; import com.alibaba.fastjson.JSONObject; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; 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.XpAppVersion; import com.xiaopeng.common.core.domain.R; import com.xiaopeng.common.core.enums.NotificationTypeEnum; import com.xiaopeng.common.core.exception.ServiceException; import com.xiaopeng.common.core.web.page.BasePage; import com.xiaopeng.common.core.web.page.PageInfo; import com.xiaopeng.common.security.utils.SecurityUtils; import com.xiaopeng.operation.mapper.XpAppVersionMapper; import com.xiaopeng.operation.service.IXpAppVersionService; import com.xiaopeng.operation.vo.XpAppVersionVO; 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.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; /** * App版本管理 服务实现类 * * @author mitao * @since 2026-02-14 */ @Slf4j @Service @RequiredArgsConstructor(onConstructor_ = @Lazy) public class XpAppVersionServiceImpl extends ServiceImpl implements IXpAppVersionService { private final XpOperationUserClient xpOperationUserClient; private final XpOperationUserNotificationClient xpOperationUserNotificationClient; private final XpAliPushMessageClient xpAliPushMessageClient; @Override public XpAppVersionVO getLatestVersion() { // 查询最新版本(按ID倒序,取第一条) XpAppVersion latestVersion = this.lambdaQuery() .orderByDesc(XpAppVersion::getVersionCode) .last("LIMIT 1") .one(); if (Objects.isNull(latestVersion)) { log.warn("未找到App版本信息"); return null; } // 转换为 VO XpAppVersionVO vo = BeanUtil.copyProperties(latestVersion, XpAppVersionVO.class); log.info("获取最新App版本成功, version={}", vo.getVersion()); return vo; } @Override public PageInfo getPageList(BasePage basePage) { Page page = lambdaQuery().orderByDesc(XpAppVersion::getVersionCode).page(new Page<>(basePage.getPageNum(), basePage.getPageSize())); PageInfo pageInfo = new PageInfo<>(); BeanUtil.copyProperties(page, pageInfo); return pageInfo; } @Override public void release(XpAppVersion appVersion) { Integer latestVersionCode = 0; //获取最新版本信息 XpAppVersion latestVersion = this.lambdaQuery() .orderByDesc(XpAppVersion::getVersionCode) .last("LIMIT 1") .one(); if (Objects.nonNull(latestVersion)) { latestVersionCode = latestVersion.getVersionCode(); } appVersion.setVersionCode(latestVersionCode + 1); // 版本号自增 save(appVersion); // 异步发送版本更新通知 sendVersionUpdateNotification(appVersion); } /** * 发送版本更新通知 * @param appVersion 版本信息 */ private void sendVersionUpdateNotification(XpAppVersion appVersion) { // 异步处理,避免阻塞主流程 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 message = "新版本上线啦!请前往更新。"; String bodyJson = buildPushBodyJson(message); // 3. 构建并保存通知记录 List notifications = enabledUsers.stream() .map(user -> buildNotification(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(); } /** * 构建推送 body JSON * @param message 消息内容 * @return JSON 字符串 */ private String buildPushBodyJson(String message) { JSONObject body = new JSONObject(); body.put("content", message); body.put("strategy", NotificationTypeEnum.VERSION_UPDATE_TIP.getCode()); // 8 body.put("type", 0); body.put("id", 0); return body.toJSONString(); } /** * 构建通知实体 * @param message 消息内容 * @param bodyJson 推送body内容 * @param user 运维用户 * @return 通知实体 */ private XpOperationsUserNotification buildNotification(String message, String bodyJson, XpOperationsUser user) { XpOperationsUserNotification notification = new XpOperationsUserNotification(); notification.setNotificationType(NotificationTypeEnum.VERSION_UPDATE_TIP.getCode()); // 8 notification.setNotificationContent(message); notification.setOperationsUserId(user.getId()); notification.setDataId(0L); // 版本更新通知 dataId 设置为 0 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("APP版本更新提示"); dto.setBody(buildPushBodyJson(message)); dto.setPushType("NOTICE"); 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; } }