Commit 796589c357ae146a6ccb16d729da9bbebb27460a
1 parent
a417d65d
新增极光推送
Showing
14 changed files
with
468 additions
and
5 deletions
pom.xml
| @@ -85,6 +85,13 @@ | @@ -85,6 +85,13 @@ | ||
| 85 | <scope>runtime</scope> | 85 | <scope>runtime</scope> |
| 86 | </dependency> | 86 | </dependency> |
| 87 | 87 | ||
| 88 | + <!-- 极光推送 --> | ||
| 89 | + <dependency> | ||
| 90 | + <groupId>cn.jpush.api</groupId> | ||
| 91 | + <artifactId>jpush-client</artifactId> | ||
| 92 | + <version>3.6.6</version> | ||
| 93 | + </dependency> | ||
| 94 | + | ||
| 88 | <!-- Test --> | 95 | <!-- Test --> |
| 89 | <dependency> | 96 | <dependency> |
| 90 | <groupId>org.springframework.boot</groupId> | 97 | <groupId>org.springframework.boot</groupId> |
src/main/java/com/diligrp/rider/config/JPushConfig.java
0 → 100644
| 1 | +package com.diligrp.rider.config; | ||
| 2 | + | ||
| 3 | +import cn.jpush.api.JPushClient; | ||
| 4 | +import lombok.RequiredArgsConstructor; | ||
| 5 | +import lombok.extern.slf4j.Slf4j; | ||
| 6 | +import org.springframework.context.annotation.Bean; | ||
| 7 | +import org.springframework.context.annotation.Configuration; | ||
| 8 | + | ||
| 9 | +/** | ||
| 10 | + * JPushClient Bean 装配 | ||
| 11 | + * | ||
| 12 | + * 即使 jpush.enabled=false 也会创建一个 client(避免依赖注入失败), | ||
| 13 | + * 调用时由 JPushService 检查 enabled 开关决定是否真的发起推送。 | ||
| 14 | + */ | ||
| 15 | +@Slf4j | ||
| 16 | +@Configuration | ||
| 17 | +@RequiredArgsConstructor | ||
| 18 | +public class JPushConfig { | ||
| 19 | + | ||
| 20 | + private final JPushProperties properties; | ||
| 21 | + | ||
| 22 | + @Bean | ||
| 23 | + public JPushClient jPushClient() { | ||
| 24 | + if (!properties.isEnabled()) { | ||
| 25 | + log.warn("JPush 未启用(jpush.enabled=false),将创建占位 client,所有推送会被跳过"); | ||
| 26 | + } | ||
| 27 | + // 即便未启用,也用占位值构造,避免 NPE | ||
| 28 | + String masterSecret = properties.getMasterSecret() != null ? properties.getMasterSecret() : "placeholder"; | ||
| 29 | + String appKey = properties.getAppKey() != null ? properties.getAppKey() : "placeholder"; | ||
| 30 | + return new JPushClient(masterSecret, appKey); | ||
| 31 | + } | ||
| 32 | +} |
src/main/java/com/diligrp/rider/config/JPushProperties.java
0 → 100644
| 1 | +package com.diligrp.rider.config; | ||
| 2 | + | ||
| 3 | +import lombok.Data; | ||
| 4 | +import org.springframework.boot.context.properties.ConfigurationProperties; | ||
| 5 | +import org.springframework.context.annotation.Configuration; | ||
| 6 | + | ||
| 7 | +/** | ||
| 8 | + * 极光推送配置 | ||
| 9 | + */ | ||
| 10 | +@Data | ||
| 11 | +@Configuration | ||
| 12 | +@ConfigurationProperties(prefix = "jpush") | ||
| 13 | +public class JPushProperties { | ||
| 14 | + | ||
| 15 | + /** 是否启用 JPush;false 时所有推送调用静默跳过 */ | ||
| 16 | + private boolean enabled = false; | ||
| 17 | + | ||
| 18 | + /** AppKey */ | ||
| 19 | + private String appKey; | ||
| 20 | + | ||
| 21 | + /** MasterSecret */ | ||
| 22 | + private String masterSecret; | ||
| 23 | + | ||
| 24 | + /** iOS APNs 是否生产环境 */ | ||
| 25 | + private boolean apnsProduction = false; | ||
| 26 | + | ||
| 27 | + /** 离线消息保留秒数 */ | ||
| 28 | + private int timeToLive = 86400; | ||
| 29 | +} |
src/main/java/com/diligrp/rider/controller/RiderDeviceController.java
0 → 100644
| 1 | +package com.diligrp.rider.controller; | ||
| 2 | + | ||
| 3 | +import com.diligrp.rider.common.result.Result; | ||
| 4 | +import com.diligrp.rider.dto.DeviceBindDTO; | ||
| 5 | +import com.diligrp.rider.service.RiderDeviceService; | ||
| 6 | +import jakarta.servlet.http.HttpServletRequest; | ||
| 7 | +import jakarta.validation.Valid; | ||
| 8 | +import lombok.RequiredArgsConstructor; | ||
| 9 | +import org.springframework.web.bind.annotation.PostMapping; | ||
| 10 | +import org.springframework.web.bind.annotation.RequestBody; | ||
| 11 | +import org.springframework.web.bind.annotation.RequestMapping; | ||
| 12 | +import org.springframework.web.bind.annotation.RequestParam; | ||
| 13 | +import org.springframework.web.bind.annotation.RestController; | ||
| 14 | + | ||
| 15 | +/** | ||
| 16 | + * 骑手端推送设备绑定接口 | ||
| 17 | + */ | ||
| 18 | +@RestController | ||
| 19 | +@RequestMapping("/api/rider/device") | ||
| 20 | +@RequiredArgsConstructor | ||
| 21 | +public class RiderDeviceController { | ||
| 22 | + | ||
| 23 | + private final RiderDeviceService deviceService; | ||
| 24 | + | ||
| 25 | + /** | ||
| 26 | + * 登录后绑定 JPush registrationId | ||
| 27 | + */ | ||
| 28 | + @PostMapping("/bind") | ||
| 29 | + public Result<Void> bind(@Valid @RequestBody DeviceBindDTO dto, HttpServletRequest request) { | ||
| 30 | + Long riderId = (Long) request.getAttribute("riderId"); | ||
| 31 | + deviceService.bind(riderId, dto); | ||
| 32 | + return Result.success(); | ||
| 33 | + } | ||
| 34 | + | ||
| 35 | + /** | ||
| 36 | + * 退出登录时解绑 | ||
| 37 | + */ | ||
| 38 | + @PostMapping("/unbind") | ||
| 39 | + public Result<Void> unbind(@RequestParam String registrationId, HttpServletRequest request) { | ||
| 40 | + Long riderId = (Long) request.getAttribute("riderId"); | ||
| 41 | + deviceService.unbind(riderId, registrationId); | ||
| 42 | + return Result.success(); | ||
| 43 | + } | ||
| 44 | +} |
src/main/java/com/diligrp/rider/dto/DeviceBindDTO.java
0 → 100644
| 1 | +package com.diligrp.rider.dto; | ||
| 2 | + | ||
| 3 | +import jakarta.validation.constraints.NotBlank; | ||
| 4 | +import jakarta.validation.constraints.NotNull; | ||
| 5 | +import lombok.Data; | ||
| 6 | + | ||
| 7 | +/** | ||
| 8 | + * 骑手端设备绑定参数 | ||
| 9 | + */ | ||
| 10 | +@Data | ||
| 11 | +public class DeviceBindDTO { | ||
| 12 | + | ||
| 13 | + /** JPush 注册ID */ | ||
| 14 | + @NotBlank(message = "registrationId 不能为空") | ||
| 15 | + private String registrationId; | ||
| 16 | + | ||
| 17 | + /** 平台:1=Android 2=iOS */ | ||
| 18 | + @NotNull(message = "platform 不能为空") | ||
| 19 | + private Integer platform; | ||
| 20 | + | ||
| 21 | + /** 设备信息(可选) */ | ||
| 22 | + private String deviceInfo; | ||
| 23 | + | ||
| 24 | + /** App 版本(可选) */ | ||
| 25 | + private String appVersion; | ||
| 26 | +} |
src/main/java/com/diligrp/rider/entity/RiderDevice.java
0 → 100644
| 1 | +package com.diligrp.rider.entity; | ||
| 2 | + | ||
| 3 | +import com.baomidou.mybatisplus.annotation.IdType; | ||
| 4 | +import com.baomidou.mybatisplus.annotation.TableId; | ||
| 5 | +import com.baomidou.mybatisplus.annotation.TableName; | ||
| 6 | +import lombok.Data; | ||
| 7 | + | ||
| 8 | +/** | ||
| 9 | + * 骑手设备推送绑定表 | ||
| 10 | + */ | ||
| 11 | +@Data | ||
| 12 | +@TableName("rider_device") | ||
| 13 | +public class RiderDevice { | ||
| 14 | + | ||
| 15 | + @TableId(type = IdType.AUTO) | ||
| 16 | + private Long id; | ||
| 17 | + | ||
| 18 | + /** 骑手ID */ | ||
| 19 | + private Long riderId; | ||
| 20 | + | ||
| 21 | + /** JPush 设备注册ID */ | ||
| 22 | + private String registrationId; | ||
| 23 | + | ||
| 24 | + /** 平台:1=Android 2=iOS */ | ||
| 25 | + private Integer platform; | ||
| 26 | + | ||
| 27 | + /** 设备信息(型号/系统版本) */ | ||
| 28 | + private String deviceInfo; | ||
| 29 | + | ||
| 30 | + /** App 版本 */ | ||
| 31 | + private String appVersion; | ||
| 32 | + | ||
| 33 | + /** 状态:0=已解绑 1=正常 */ | ||
| 34 | + private Integer status; | ||
| 35 | + | ||
| 36 | + /** 最近活跃时间 */ | ||
| 37 | + private Long lastActiveTime; | ||
| 38 | + | ||
| 39 | + private Long createTime; | ||
| 40 | + | ||
| 41 | + private Long updateTime; | ||
| 42 | +} |
src/main/java/com/diligrp/rider/mapper/RiderDeviceMapper.java
0 → 100644
src/main/java/com/diligrp/rider/service/JPushService.java
0 → 100644
| 1 | +package com.diligrp.rider.service; | ||
| 2 | + | ||
| 3 | +/** | ||
| 4 | + * 极光推送服务 | ||
| 5 | + * | ||
| 6 | + * 推送目标统一使用 alias = riderId 字符串; | ||
| 7 | + * App 端登录后须调用 JPushFlutter.setAlias(riderId) 完成绑定。 | ||
| 8 | + */ | ||
| 9 | +public interface JPushService { | ||
| 10 | + | ||
| 11 | + /** | ||
| 12 | + * 推送给单个骑手 | ||
| 13 | + * | ||
| 14 | + * @param riderId 骑手ID(作为 JPush alias) | ||
| 15 | + * @param title 通知标题 | ||
| 16 | + * @param content 通知正文 | ||
| 17 | + * @param bizType 业务类型(透传到 App,用于点击跳转) | ||
| 18 | + * @param bizId 业务ID(如订单ID) | ||
| 19 | + */ | ||
| 20 | + void pushToRider(Long riderId, String title, String content, String bizType, Long bizId); | ||
| 21 | + | ||
| 22 | + /** | ||
| 23 | + * 按城市广播(按 tag = "city_<cityId>",App 端登录时按所属城市设置 tag) | ||
| 24 | + */ | ||
| 25 | + void pushToCity(Long cityId, String title, String content, String bizType); | ||
| 26 | +} |
src/main/java/com/diligrp/rider/service/RiderDeviceService.java
0 → 100644
| 1 | +package com.diligrp.rider.service; | ||
| 2 | + | ||
| 3 | +import com.diligrp.rider.dto.DeviceBindDTO; | ||
| 4 | + | ||
| 5 | +/** | ||
| 6 | + * 骑手设备绑定服务 | ||
| 7 | + */ | ||
| 8 | +public interface RiderDeviceService { | ||
| 9 | + | ||
| 10 | + /** | ||
| 11 | + * 登录后绑定设备(同 registrationId 已存在则刷新归属与活跃时间) | ||
| 12 | + */ | ||
| 13 | + void bind(Long riderId, DeviceBindDTO dto); | ||
| 14 | + | ||
| 15 | + /** | ||
| 16 | + * 退出登录时解绑(按 registrationId) | ||
| 17 | + */ | ||
| 18 | + void unbind(Long riderId, String registrationId); | ||
| 19 | +} |
src/main/java/com/diligrp/rider/service/impl/AdminMessageServiceImpl.java
| @@ -10,6 +10,7 @@ import com.diligrp.rider.mapper.RiderMapper; | @@ -10,6 +10,7 @@ import com.diligrp.rider.mapper.RiderMapper; | ||
| 10 | import com.diligrp.rider.mapper.RiderMessageMapper; | 10 | import com.diligrp.rider.mapper.RiderMessageMapper; |
| 11 | import com.diligrp.rider.mapper.RiderMessageTemplateMapper; | 11 | import com.diligrp.rider.mapper.RiderMessageTemplateMapper; |
| 12 | import com.diligrp.rider.service.AdminMessageService; | 12 | import com.diligrp.rider.service.AdminMessageService; |
| 13 | +import com.diligrp.rider.service.JPushService; | ||
| 13 | import lombok.RequiredArgsConstructor; | 14 | import lombok.RequiredArgsConstructor; |
| 14 | import lombok.extern.slf4j.Slf4j; | 15 | import lombok.extern.slf4j.Slf4j; |
| 15 | import org.springframework.stereotype.Service; | 16 | import org.springframework.stereotype.Service; |
| @@ -28,6 +29,7 @@ public class AdminMessageServiceImpl implements AdminMessageService { | @@ -28,6 +29,7 @@ public class AdminMessageServiceImpl implements AdminMessageService { | ||
| 28 | private final RiderMessageMapper messageMapper; | 29 | private final RiderMessageMapper messageMapper; |
| 29 | private final RiderMessageTemplateMapper templateMapper; | 30 | private final RiderMessageTemplateMapper templateMapper; |
| 30 | private final RiderMapper riderMapper; | 31 | private final RiderMapper riderMapper; |
| 32 | + private final JPushService jPushService; | ||
| 31 | 33 | ||
| 32 | private static final int PAGE_SIZE = 20; | 34 | private static final int PAGE_SIZE = 20; |
| 33 | 35 | ||
| @@ -37,6 +39,15 @@ public class AdminMessageServiceImpl implements AdminMessageService { | @@ -37,6 +39,15 @@ public class AdminMessageServiceImpl implements AdminMessageService { | ||
| 37 | ensureCitySelected(cityId); | 39 | ensureCitySelected(cityId); |
| 38 | ensureRiderInCity(cityId, riderId); | 40 | ensureRiderInCity(cityId, riderId); |
| 39 | 41 | ||
| 42 | + long messageId = insertMessage(cityId, riderId, type, title, content, bizType, bizId); | ||
| 43 | + log.info("发送消息成功,riderId={} messageId={}", riderId, messageId); | ||
| 44 | + | ||
| 45 | + // 写库后触发推送(管理员手动单发:总是推送) | ||
| 46 | + jPushService.pushToRider(riderId, title, content, bizType, bizId); | ||
| 47 | + } | ||
| 48 | + | ||
| 49 | + private long insertMessage(Long cityId, Long riderId, Integer type, String title, | ||
| 50 | + String content, String bizType, Long bizId) { | ||
| 40 | RiderMessage message = new RiderMessage(); | 51 | RiderMessage message = new RiderMessage(); |
| 41 | message.setCityId(cityId); | 52 | message.setCityId(cityId); |
| 42 | message.setRiderId(riderId); | 53 | message.setRiderId(riderId); |
| @@ -48,9 +59,8 @@ public class AdminMessageServiceImpl implements AdminMessageService { | @@ -48,9 +59,8 @@ public class AdminMessageServiceImpl implements AdminMessageService { | ||
| 48 | message.setIsRead(0); | 59 | message.setIsRead(0); |
| 49 | message.setCreateTime(System.currentTimeMillis()); | 60 | message.setCreateTime(System.currentTimeMillis()); |
| 50 | message.setReadTime(0L); | 61 | message.setReadTime(0L); |
| 51 | - | ||
| 52 | messageMapper.insert(message); | 62 | messageMapper.insert(message); |
| 53 | - log.info("发送消息成功,riderId={} messageId={}", riderId, message.getId()); | 63 | + return message.getId(); |
| 54 | } | 64 | } |
| 55 | 65 | ||
| 56 | @Override | 66 | @Override |
| @@ -96,6 +106,9 @@ public class AdminMessageServiceImpl implements AdminMessageService { | @@ -96,6 +106,9 @@ public class AdminMessageServiceImpl implements AdminMessageService { | ||
| 96 | } | 106 | } |
| 97 | 107 | ||
| 98 | log.info("群发消息成功,cityId={} 骑手数={}", cityId, riders.size()); | 108 | log.info("群发消息成功,cityId={} 骑手数={}", cityId, riders.size()); |
| 109 | + | ||
| 110 | + // 写库后按城市 tag 触发广播推送 | ||
| 111 | + jPushService.pushToCity(cityId, title, content, "broadcast"); | ||
| 99 | } | 112 | } |
| 100 | 113 | ||
| 101 | @Override | 114 | @Override |
| @@ -164,9 +177,15 @@ public class AdminMessageServiceImpl implements AdminMessageService { | @@ -164,9 +177,15 @@ public class AdminMessageServiceImpl implements AdminMessageService { | ||
| 164 | String title = replacePlaceholders(template.getTitleTemplate(), params); | 177 | String title = replacePlaceholders(template.getTitleTemplate(), params); |
| 165 | String content = replacePlaceholders(template.getContentTemplate(), params); | 178 | String content = replacePlaceholders(template.getContentTemplate(), params); |
| 166 | 179 | ||
| 167 | - // 发送消息 | 180 | + // 模板的 is_push=1 时同步推送,否则仅入库 |
| 168 | Long bizId = params.get("bizId") != null ? Long.valueOf(params.get("bizId").toString()) : 0L; | 181 | Long bizId = params.get("bizId") != null ? Long.valueOf(params.get("bizId").toString()) : 0L; |
| 169 | - sendToRider(cityId, riderId, template.getType(), title, content, bizType, bizId); | 182 | + if (Integer.valueOf(1).equals(template.getIsPush())) { |
| 183 | + sendToRider(cityId, riderId, template.getType(), title, content, bizType, bizId); | ||
| 184 | + } else { | ||
| 185 | + ensureCitySelected(cityId); | ||
| 186 | + ensureRiderInCity(cityId, riderId); | ||
| 187 | + insertMessage(cityId, riderId, template.getType(), title, content, bizType, bizId); | ||
| 188 | + } | ||
| 170 | } | 189 | } |
| 171 | 190 | ||
| 172 | /** | 191 | /** |
src/main/java/com/diligrp/rider/service/impl/JPushServiceImpl.java
0 → 100644
| 1 | +package com.diligrp.rider.service.impl; | ||
| 2 | + | ||
| 3 | +import cn.jiguang.common.resp.APIConnectionException; | ||
| 4 | +import cn.jiguang.common.resp.APIRequestException; | ||
| 5 | +import cn.jpush.api.JPushClient; | ||
| 6 | +import cn.jpush.api.push.PushResult; | ||
| 7 | +import cn.jpush.api.push.model.Options; | ||
| 8 | +import cn.jpush.api.push.model.Platform; | ||
| 9 | +import cn.jpush.api.push.model.PushPayload; | ||
| 10 | +import cn.jpush.api.push.model.audience.Audience; | ||
| 11 | +import cn.jpush.api.push.model.notification.AndroidNotification; | ||
| 12 | +import cn.jpush.api.push.model.notification.IosNotification; | ||
| 13 | +import cn.jpush.api.push.model.notification.Notification; | ||
| 14 | +import com.diligrp.rider.config.JPushProperties; | ||
| 15 | +import com.diligrp.rider.service.JPushService; | ||
| 16 | +import lombok.RequiredArgsConstructor; | ||
| 17 | +import lombok.extern.slf4j.Slf4j; | ||
| 18 | +import org.springframework.stereotype.Service; | ||
| 19 | + | ||
| 20 | +@Slf4j | ||
| 21 | +@Service | ||
| 22 | +@RequiredArgsConstructor | ||
| 23 | +public class JPushServiceImpl implements JPushService { | ||
| 24 | + | ||
| 25 | + private final JPushClient jPushClient; | ||
| 26 | + private final JPushProperties properties; | ||
| 27 | + | ||
| 28 | + @Override | ||
| 29 | + public void pushToRider(Long riderId, String title, String content, String bizType, Long bizId) { | ||
| 30 | + if (!properties.isEnabled()) { | ||
| 31 | + log.debug("JPush 未启用,跳过推送 riderId={} title={}", riderId, title); | ||
| 32 | + return; | ||
| 33 | + } | ||
| 34 | + if (riderId == null || riderId <= 0) { | ||
| 35 | + log.warn("riderId 无效,跳过推送 riderId={}", riderId); | ||
| 36 | + return; | ||
| 37 | + } | ||
| 38 | + try { | ||
| 39 | + PushPayload payload = buildPayload( | ||
| 40 | + Audience.alias(String.valueOf(riderId)), | ||
| 41 | + title, content, bizType, bizId); | ||
| 42 | + PushResult result = jPushClient.sendPush(payload); | ||
| 43 | + log.info("JPush 推送成功 riderId={} msgId={} sendno={}", | ||
| 44 | + riderId, result.msg_id, result.sendno); | ||
| 45 | + } catch (APIConnectionException e) { | ||
| 46 | + log.error("JPush 网络异常 riderId={}", riderId, e); | ||
| 47 | + } catch (APIRequestException e) { | ||
| 48 | + log.error("JPush 业务异常 riderId={} status={} errorCode={} msg={}", | ||
| 49 | + riderId, e.getStatus(), e.getErrorCode(), e.getErrorMessage()); | ||
| 50 | + } catch (Exception e) { | ||
| 51 | + log.error("JPush 推送未知异常 riderId={}", riderId, e); | ||
| 52 | + } | ||
| 53 | + } | ||
| 54 | + | ||
| 55 | + @Override | ||
| 56 | + public void pushToCity(Long cityId, String title, String content, String bizType) { | ||
| 57 | + if (!properties.isEnabled()) { | ||
| 58 | + log.debug("JPush 未启用,跳过广播 cityId={} title={}", cityId, title); | ||
| 59 | + return; | ||
| 60 | + } | ||
| 61 | + if (cityId == null || cityId <= 0) { | ||
| 62 | + log.warn("cityId 无效,跳过广播 cityId={}", cityId); | ||
| 63 | + return; | ||
| 64 | + } | ||
| 65 | + try { | ||
| 66 | + PushPayload payload = buildPayload( | ||
| 67 | + Audience.tag("city_" + cityId), | ||
| 68 | + title, content, bizType, 0L); | ||
| 69 | + PushResult result = jPushClient.sendPush(payload); | ||
| 70 | + log.info("JPush 城市广播成功 cityId={} msgId={}", cityId, result.msg_id); | ||
| 71 | + } catch (APIConnectionException e) { | ||
| 72 | + log.error("JPush 城市广播网络异常 cityId={}", cityId, e); | ||
| 73 | + } catch (APIRequestException e) { | ||
| 74 | + log.error("JPush 城市广播业务异常 cityId={} status={} errorCode={} msg={}", | ||
| 75 | + cityId, e.getStatus(), e.getErrorCode(), e.getErrorMessage()); | ||
| 76 | + } catch (Exception e) { | ||
| 77 | + log.error("JPush 城市广播未知异常 cityId={}", cityId, e); | ||
| 78 | + } | ||
| 79 | + } | ||
| 80 | + | ||
| 81 | + private PushPayload buildPayload(Audience audience, String title, String content, | ||
| 82 | + String bizType, Long bizId) { | ||
| 83 | + AndroidNotification.Builder androidBuilder = AndroidNotification.newBuilder() | ||
| 84 | + .setAlert(content) | ||
| 85 | + .setTitle(title); | ||
| 86 | + IosNotification.Builder iosBuilder = IosNotification.newBuilder() | ||
| 87 | + .setAlert(content) | ||
| 88 | + .incrBadge(1) | ||
| 89 | + .setSound("default"); | ||
| 90 | + | ||
| 91 | + if (bizType != null && !bizType.isEmpty()) { | ||
| 92 | + androidBuilder.addExtra("bizType", bizType); | ||
| 93 | + iosBuilder.addExtra("bizType", bizType); | ||
| 94 | + } | ||
| 95 | + if (bizId != null) { | ||
| 96 | + androidBuilder.addExtra("bizId", String.valueOf(bizId)); | ||
| 97 | + iosBuilder.addExtra("bizId", String.valueOf(bizId)); | ||
| 98 | + } | ||
| 99 | + | ||
| 100 | + return PushPayload.newBuilder() | ||
| 101 | + .setPlatform(Platform.android_ios()) | ||
| 102 | + .setAudience(audience) | ||
| 103 | + .setNotification(Notification.newBuilder() | ||
| 104 | + .addPlatformNotification(androidBuilder.build()) | ||
| 105 | + .addPlatformNotification(iosBuilder.build()) | ||
| 106 | + .build()) | ||
| 107 | + .setOptions(Options.newBuilder() | ||
| 108 | + .setApnsProduction(properties.isApnsProduction()) | ||
| 109 | + .setTimeToLive(properties.getTimeToLive()) | ||
| 110 | + .build()) | ||
| 111 | + .build(); | ||
| 112 | + } | ||
| 113 | +} |
src/main/java/com/diligrp/rider/service/impl/RiderDeviceServiceImpl.java
0 → 100644
| 1 | +package com.diligrp.rider.service.impl; | ||
| 2 | + | ||
| 3 | +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; | ||
| 4 | +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; | ||
| 5 | +import com.diligrp.rider.dto.DeviceBindDTO; | ||
| 6 | +import com.diligrp.rider.entity.RiderDevice; | ||
| 7 | +import com.diligrp.rider.mapper.RiderDeviceMapper; | ||
| 8 | +import com.diligrp.rider.service.RiderDeviceService; | ||
| 9 | +import lombok.RequiredArgsConstructor; | ||
| 10 | +import lombok.extern.slf4j.Slf4j; | ||
| 11 | +import org.springframework.stereotype.Service; | ||
| 12 | +import org.springframework.transaction.annotation.Transactional; | ||
| 13 | + | ||
| 14 | +@Slf4j | ||
| 15 | +@Service | ||
| 16 | +@RequiredArgsConstructor | ||
| 17 | +public class RiderDeviceServiceImpl implements RiderDeviceService { | ||
| 18 | + | ||
| 19 | + private final RiderDeviceMapper deviceMapper; | ||
| 20 | + | ||
| 21 | + @Override | ||
| 22 | + @Transactional | ||
| 23 | + public void bind(Long riderId, DeviceBindDTO dto) { | ||
| 24 | + long now = System.currentTimeMillis(); | ||
| 25 | + | ||
| 26 | + // 同一 registrationId 全局唯一:若已存在则覆盖归属 | ||
| 27 | + LambdaQueryWrapper<RiderDevice> wrapper = new LambdaQueryWrapper<>(); | ||
| 28 | + wrapper.eq(RiderDevice::getRegistrationId, dto.getRegistrationId()); | ||
| 29 | + RiderDevice existing = deviceMapper.selectOne(wrapper); | ||
| 30 | + | ||
| 31 | + if (existing != null) { | ||
| 32 | + existing.setRiderId(riderId); | ||
| 33 | + existing.setPlatform(dto.getPlatform()); | ||
| 34 | + existing.setDeviceInfo(dto.getDeviceInfo() == null ? "" : dto.getDeviceInfo()); | ||
| 35 | + existing.setAppVersion(dto.getAppVersion() == null ? "" : dto.getAppVersion()); | ||
| 36 | + existing.setStatus(1); | ||
| 37 | + existing.setLastActiveTime(now); | ||
| 38 | + existing.setUpdateTime(now); | ||
| 39 | + deviceMapper.updateById(existing); | ||
| 40 | + log.info("设备绑定刷新 riderId={} registrationId={}", riderId, dto.getRegistrationId()); | ||
| 41 | + return; | ||
| 42 | + } | ||
| 43 | + | ||
| 44 | + RiderDevice device = new RiderDevice(); | ||
| 45 | + device.setRiderId(riderId); | ||
| 46 | + device.setRegistrationId(dto.getRegistrationId()); | ||
| 47 | + device.setPlatform(dto.getPlatform()); | ||
| 48 | + device.setDeviceInfo(dto.getDeviceInfo() == null ? "" : dto.getDeviceInfo()); | ||
| 49 | + device.setAppVersion(dto.getAppVersion() == null ? "" : dto.getAppVersion()); | ||
| 50 | + device.setStatus(1); | ||
| 51 | + device.setLastActiveTime(now); | ||
| 52 | + device.setCreateTime(now); | ||
| 53 | + device.setUpdateTime(now); | ||
| 54 | + deviceMapper.insert(device); | ||
| 55 | + log.info("设备绑定新增 riderId={} registrationId={}", riderId, dto.getRegistrationId()); | ||
| 56 | + } | ||
| 57 | + | ||
| 58 | + @Override | ||
| 59 | + @Transactional | ||
| 60 | + public void unbind(Long riderId, String registrationId) { | ||
| 61 | + if (registrationId == null || registrationId.isEmpty()) { | ||
| 62 | + return; | ||
| 63 | + } | ||
| 64 | + long now = System.currentTimeMillis(); | ||
| 65 | + LambdaUpdateWrapper<RiderDevice> wrapper = new LambdaUpdateWrapper<>(); | ||
| 66 | + wrapper.eq(RiderDevice::getRegistrationId, registrationId) | ||
| 67 | + .eq(RiderDevice::getRiderId, riderId) | ||
| 68 | + .set(RiderDevice::getStatus, 0) | ||
| 69 | + .set(RiderDevice::getUpdateTime, now); | ||
| 70 | + deviceMapper.update(null, wrapper); | ||
| 71 | + log.info("设备解绑 riderId={} registrationId={}", riderId, registrationId); | ||
| 72 | + } | ||
| 73 | +} |
src/main/resources/application.yml
| @@ -31,6 +31,13 @@ jwt: | @@ -31,6 +31,13 @@ jwt: | ||
| 31 | secret: diligrp-rider-secret-key-2024-please-change-this | 31 | secret: diligrp-rider-secret-key-2024-please-change-this |
| 32 | expire: 604800 # 7天,单位秒 | 32 | expire: 604800 # 7天,单位秒 |
| 33 | 33 | ||
| 34 | +jpush: | ||
| 35 | + enabled: false # 未配置 AppKey 前置 false,避免启动时报错 | ||
| 36 | + app-key: your-jpush-app-key | ||
| 37 | + master-secret: your-jpush-master-secret | ||
| 38 | + apns-production: false # iOS APNs:true=生产 false=开发 | ||
| 39 | + time-to-live: 86400 # 离线消息保留秒数(默认 1 天) | ||
| 40 | + | ||
| 34 | logging: | 41 | logging: |
| 35 | level: | 42 | level: |
| 36 | com.diligrp.rider: debug | 43 | com.diligrp.rider: debug |
src/main/resources/schema.sql
| @@ -581,4 +581,21 @@ INSERT INTO `rider_message_template` (`biz_type`, `type`, `title_template`, `con | @@ -581,4 +581,21 @@ INSERT INTO `rider_message_template` (`biz_type`, `type`, `title_template`, `con | ||
| 581 | ('order_cancelled', 1, '订单取消', '订单 #{orderNo} 已被取消', 1), | 581 | ('order_cancelled', 1, '订单取消', '订单 #{orderNo} 已被取消', 1), |
| 582 | ('system_notice', 2, '系统通知', '#{content}', 1), | 582 | ('system_notice', 2, '系统通知', '#{content}', 1), |
| 583 | ('level_upgrade', 2, '等级提升', '恭喜您,等级已提升至 #{levelName}', 1), | 583 | ('level_upgrade', 2, '等级提升', '恭喜您,等级已提升至 #{levelName}', 1), |
| 584 | -('balance_change', 2, '余额变动', '您的余额发生变动,当前余额:#{balance} 元', 0); | ||
| 585 | \ No newline at end of file | 584 | \ No newline at end of file |
| 585 | +('balance_change', 2, '余额变动', '您的余额发生变动,当前余额:#{balance} 元', 0); | ||
| 586 | + | ||
| 587 | +-- 骑手设备推送绑定表(极光推送) | ||
| 588 | +CREATE TABLE `rider_device` ( | ||
| 589 | + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, | ||
| 590 | + `rider_id` BIGINT UNSIGNED NOT NULL COMMENT '骑手ID', | ||
| 591 | + `registration_id` VARCHAR(64) NOT NULL DEFAULT '' COMMENT 'JPush 设备注册ID', | ||
| 592 | + `platform` TINYINT NOT NULL DEFAULT 0 COMMENT '平台:1=Android 2=iOS', | ||
| 593 | + `device_info` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '设备信息(型号/系统版本)', | ||
| 594 | + `app_version` VARCHAR(32) NOT NULL DEFAULT '' COMMENT 'App 版本', | ||
| 595 | + `status` TINYINT NOT NULL DEFAULT 1 COMMENT '状态:0=已解绑 1=正常', | ||
| 596 | + `last_active_time` BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT '最近活跃时间', | ||
| 597 | + `create_time` BIGINT UNSIGNED NOT NULL DEFAULT 0, | ||
| 598 | + `update_time` BIGINT UNSIGNED NOT NULL DEFAULT 0, | ||
| 599 | + PRIMARY KEY (`id`), | ||
| 600 | + UNIQUE KEY `uk_registration_id` (`registration_id`), | ||
| 601 | + KEY `idx_rider_status` (`rider_id`, `status`) | ||
| 602 | +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='骑手设备推送绑定表'; | ||
| 586 | \ No newline at end of file | 603 | \ No newline at end of file |