Mr.qian 2 settimane fa
parent
commit
afebef37cd

+ 26 - 18
mall-api/src/main/java/com/txz/mall/enums/NoticeEnum.java

@@ -15,91 +15,91 @@ public enum NoticeEnum {
     /**
      * 订单通知_拼团支付成功
      */
-    ORDER_GROUP_BUY_PAYMENT_SUCCESS("ORDER_GROUP_BUY_PAYMENT_SUCCESS", "订单通知_拼团支付成功"),
+    ORDER_GROUP_BUY_PAYMENT_SUCCESS("ORDER_GROUP_BUY_PAYMENT_SUCCESS", "订单通知_拼团支付成功", "notifications.order.paymentSuccess.title", "notifications.order.paymentSuccess.content"),
     
     /**
      * 订单通知_订单拼团成功&抽中
      */
-    ORDER_GROUP_BUY_SUCCESS_WIN("ORDER_GROUP_BUY_SUCCESS_WIN", "订单通知_订单拼团成功&抽中"),
+    ORDER_GROUP_BUY_SUCCESS_WIN("ORDER_GROUP_BUY_SUCCESS_WIN", "订单通知_订单拼团成功&抽中", "notifications.order.groupBuyWin.title", "notifications.order.groupBuyWin.content"),
     
     /**
      * 订单通知_订单拼团成功&未抽中
      */
-    ORDER_GROUP_BUY_SUCCESS_LOSE("ORDER_GROUP_BUY_SUCCESS_LOSE", "订单通知_订单拼团成功&未抽中"),
+    ORDER_GROUP_BUY_SUCCESS_LOSE("ORDER_GROUP_BUY_SUCCESS_LOSE", "订单通知_订单拼团成功&未抽中", "notifications.order.groupBuyLose.title", "notifications.order.groupBuyLose.content"),
     
     /**
      * 订单通知_订单补充收货地址
      */
-    ORDER_PROVIDE_SHIPPING_ADDRESS("ORDER_PROVIDE_SHIPPING_ADDRESS", "订单通知_订单补充收货地址"),
+    ORDER_PROVIDE_SHIPPING_ADDRESS("ORDER_PROVIDE_SHIPPING_ADDRESS", "订单通知_订单补充收货地址", "notifications.order.provideAddress.title", "notifications.order.provideAddress.content"),
     
     /**
      * 订单通知_订单拼团失败
      */
-    ORDER_GROUP_BUY_FAIL("ORDER_GROUP_BUY_FAIL", "订单通知_订单拼团失败"),
+    ORDER_GROUP_BUY_FAIL("ORDER_GROUP_BUY_FAIL", "订单通知_订单拼团失败", "notifications.order.groupBuyFail.title", "notifications.order.groupBuyFail.content"),
     
     /**
      * 订单通知_订单发货
      */
-    ORDER_SHIPPED_SUCCESS("ORDER_SHIPPED_SUCCESS", "订单通知_订单发货"),
+    ORDER_SHIPPED_SUCCESS("ORDER_SHIPPED_SUCCESS", "订单通知_订单发货", "notifications.order.shipped.title", "notifications.order.shipped.content"),
     
     /**
      * 收益通知_邀请好友奖励
      */
-    REWARD_REFER_FRIENDS("REWARD_REFER_FRIENDS", "收益通知_邀请好友奖励"),
+    REWARD_REFER_FRIENDS("REWARD_REFER_FRIENDS", "收益通知_邀请好友奖励", "notifications.reward.referFriends.title", "notifications.reward.referFriends.content"),
     
     /**
      * 收益通知_拼团奖励
      */
-    REWARD_GROUP_BUY("REWARD_GROUP_BUY", "收益通知_拼团奖励"),
+    REWARD_GROUP_BUY("REWARD_GROUP_BUY", "收益通知_拼团奖励", "notifications.reward.groupBuy.title", "notifications.reward.groupBuy.content"),
     /**
      * 收益通知_开团奖励
      */
-    REWARD_OPEN_GROUP_BUY("REWARD_OPEN_GROUP_BUY", "收益通知_开团奖励"),
+    REWARD_OPEN_GROUP_BUY("REWARD_OPEN_GROUP_BUY", "收益通知_开团奖励", "notifications.reward.openGroupBuy.title", "notifications.reward.openGroupBuy.content"),
     
     /**
      * 收益通知_一级佣金奖励
      */
-    REWARD_FIRST_COMMISSION("REWARD_FIRST_COMMISSION", "一级佣金"),
+    REWARD_FIRST_COMMISSION("REWARD_FIRST_COMMISSION", "一级佣金", "notifications.reward.firstCommission.title", "notifications.reward.secondaryCommission.content"),
     
     /**
      * 收益通知_二级佣金奖励
      */
-    REWARD_SECONDARY_COMMISSION("REWARD_SECONDARY_COMMISSION", "二级佣金"),
+    REWARD_SECONDARY_COMMISSION("REWARD_SECONDARY_COMMISSION", "二级佣金", "notifications.reward.secondaryCommission.title", "notifications.reward.directReferral.content"),
     
     /**
      * 收益通知_邀请好友奖励
      */
-    REWARD_DIRECT_REFERRAL("REWARD_DIRECT_REFERRAL", "收益通知_邀请好友奖励"),
+    REWARD_DIRECT_REFERRAL("REWARD_DIRECT_REFERRAL", "收益通知_邀请好友奖励", "notifications.reward.directReferral.title", "notifications.reward.directReferral.content"),
     
     /**
      * 收益通知_签到奖励
      */
-    REWARD_CHECKIN("REWARD_CHECKIN", "收益通知_签到奖励"),
+    REWARD_CHECKIN("REWARD_CHECKIN", "收益通知_签到奖励", "notifications.reward.checkin.title", "notifications.reward.checkin.content"),
     
     /**
      * 充值/提现通知_充值成功
      */
-    MONEY_RECHARGE_SUCCESS("MONEY_RECHARGE_SUCCESS", "充值/提现通知_充值成功"),
+    MONEY_RECHARGE_SUCCESS("MONEY_RECHARGE_SUCCESS", "充值/提现通知_充值成功", "notifications.money.rechargeSuccess.title", "notifications.money.rechargeSuccess.content"),
     
     /**
      * 充值/提现通知_提现成功-收益
      */
-    MONEY_WITHDRAWAL_ACCOUNT_SUCCESS("MONEY_WITHDRAWAL_ACCOUNT_SUCCESS", "充值/提现通知_提现成功-收益"),
+    MONEY_WITHDRAWAL_ACCOUNT_SUCCESS("MONEY_WITHDRAWAL_ACCOUNT_SUCCESS", "充值/提现通知_提现成功-收益", "notifications.money.withdrawalAccountSuccess.title", "notifications.money.withdrawalAccountSuccess.content"),
     
     /**
      * 充值/提现通知_提现成功-钱包
      */
-    MONEY_WITHDRAWAL_WALLET_SUCCESS("MONEY_WITHDRAWAL_WALLET_SUCCESS", "充值/提现通知_提现成功-钱包"),
+    MONEY_WITHDRAWAL_WALLET_SUCCESS("MONEY_WITHDRAWAL_WALLET_SUCCESS", "充值/提现通知_提现成功-钱包", "notifications.money.withdrawalWalletSuccess.title", "notifications.money.withdrawalWalletSuccess.content"),
     
     /**
      * 充值/提现通知_提现失败
      */
-    MONEY_WITHDRAWAL_FAIL("MONEY_WITHDRAWAL_FAIL", "充值/提现通知_提现失败"),
+    MONEY_WITHDRAWAL_FAIL("MONEY_WITHDRAWAL_FAIL", "充值/提现通知_提现失败", "notifications.money.withdrawalFail.title", "notifications.money.withdrawalFail.content"),
     
     /**
      * 其他
      */
-    OTHER("OTHER", "其他"),
+    OTHER("OTHER", "其他", null, null),
     
     ;
     
@@ -111,4 +111,12 @@ public enum NoticeEnum {
     @Getter
     @Setter
     private String description;
+    
+    @Getter
+    @Setter
+    private String title;
+    
+    @Getter
+    @Setter
+    private String content;
 }

+ 18 - 0
mall-service/pom.xml

@@ -173,6 +173,24 @@
             <scope>test</scope>
         </dependency>
 
+        <dependency>
+            <groupId>io.github.engagelab-mt</groupId>
+            <artifactId>engagelab-sdk-java</artifactId>
+            <version>0.0.15</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>com.squareup.okhttp3</groupId>
+                    <artifactId>okhttp</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
+         <dependency>
+            <groupId>com.squareup.okhttp3</groupId>
+            <artifactId>okhttp</artifactId>
+            <version>4.12.0</version>
+        </dependency>
+
     </dependencies>
 
     <build>

+ 92 - 0
mall-service/src/main/java/com/txz/mall/configurer/jpush/JPushConf.java

@@ -0,0 +1,92 @@
+package com.txz.mall.configurer.jpush;
+
+import feign.okhttp.OkHttpClient;
+import io.github.engagelab.api.*;
+import io.github.engagelab.enums.DataCenterHost;
+import lombok.Data;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.stereotype.Component;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author: MTD®️
+ * @date: 2025/9/17
+ */
+@Data
+@Component
+@ConfigurationProperties(prefix = "jpush")
+public class JPushConf {
+    
+    private String appKey;
+    
+    private String masterSecret;
+    
+    @Bean
+    public PushApi pushApi() {
+        return new PushApi.Builder()
+                .setHost(DataCenterHost.HK.getUrl())
+                .setAppKey(this.appKey)
+                .setMasterSecret(this.masterSecret)
+                .build();
+    }
+    
+    @Bean
+    public DeviceApi deviceApi() {
+        return new DeviceApi.Builder()
+                .setHost(DataCenterHost.HK.getUrl())
+                .setAppKey(this.appKey)
+                .setMasterSecret(this.masterSecret)
+                .build();
+    }
+    
+    @Bean
+    public StatusApi statusApi() {
+        return new StatusApi.Builder()
+                .setHost(DataCenterHost.HK.getUrl())
+                .setAppKey(this.appKey)
+                .setMasterSecret(this.masterSecret)
+                .build();
+    }
+    
+    @Bean
+    public ScheduleApi scheduleApi() {
+        return new ScheduleApi.Builder()
+                .setHost(DataCenterHost.HK.getUrl())
+                .setAppKey(this.appKey)
+                .setMasterSecret(this.masterSecret)
+                .build();
+    }
+    
+    
+    @Bean("okHttpClient")
+    public OkHttpClient okHttpClient() {
+        okhttp3.OkHttpClient okHttpClient = new okhttp3.OkHttpClient().newBuilder()
+                // .proxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress("proxy_host", proxy_port)))
+                .connectTimeout(5, TimeUnit.SECONDS)
+                .build();
+        return new OkHttpClient(okHttpClient);
+    }
+    
+    @Bean
+    public GroupPushApi groupPushApi(@Qualifier("okHttpClient") OkHttpClient okHttpClient) {
+        return new GroupPushApi.Builder()
+                .setHost(DataCenterHost.HK.getUrl())
+                .setClient(okHttpClient)
+                .setAppKey(this.appKey)
+                .setMasterSecret(this.masterSecret)
+                .build();
+    }
+    
+    @Bean
+    public PushPlanApi pushPlanApi() {
+        return new PushPlanApi.Builder()
+                .setHost(DataCenterHost.HK.getUrl())
+                .setAppKey(this.appKey)
+                .setMasterSecret(this.masterSecret)
+                .build();
+    }
+    
+}

+ 72 - 0
mall-service/src/main/java/com/txz/mall/controller/appcontroller/JPushController.java

@@ -0,0 +1,72 @@
+package com.txz.mall.controller.appcontroller;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.txz.mall.core.AuthService;
+import com.txz.mall.core.Result;
+import com.txz.mall.model.UserJPush;
+import com.txz.mall.service.UserJPushService;
+import com.txz.mall.web.ro.JPushBindRO;
+import lombok.RequiredArgsConstructor;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.Date;
+
+/**
+ * [App]端极光
+ *
+ * @author: MTD®️
+ * @date: 2025/9/17
+ */
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("app/jpush")
+public class JPushController {
+    
+    private final UserJPushService userJPushService;
+    
+    private final AuthService authService;
+    
+    /**
+     * 客户端绑定用户
+     */
+    @PostMapping("bindUser")
+    private Result bindUser(@RequestBody @Validated JPushBindRO ro, HttpServletRequest request) {
+        Long userId = authService.getTokenUserId(request);
+        UserJPush device;
+        // 多端推送场景注掉这儿
+        if (ObjectUtil.isNotEmpty(userId)) {
+            userJPushService.update(Wrappers.<UserJPush>lambdaUpdate()
+                    .eq(UserJPush::getUserId, userId)
+                    .set(UserJPush::getUserId, null)
+            );
+        }
+        device = userJPushService.getOne(Wrappers.<UserJPush>lambdaQuery()
+                .eq(UserJPush::getJpushDeviceId, ro.getJpushDeviceId())
+        );
+        // 新设备
+        if (ObjectUtil.isEmpty(device)) {
+            device = new UserJPush();
+            device.setUserId(userId);
+            device.setJpushDeviceId(ro.getJpushDeviceId());
+            device.setUserLanguage(ro.getUserLanguage());
+            device.setPlatform(ro.getPlatform());
+            device.setLastBindTime(new Date());
+            userJPushService.save(device);
+        } else {
+            // 修改设备绑定信息
+            userJPushService.update(Wrappers.<UserJPush>lambdaUpdate()
+                    .eq(UserJPush::getJpushDeviceId, ro.getJpushDeviceId())
+                    .set(UserJPush::getUserId, userId)
+                    .set(UserJPush::getUserLanguage, ro.getUserLanguage())
+            );
+        }
+        return Result.success();
+    }
+    
+}

+ 13 - 0
mall-service/src/main/java/com/txz/mall/dao/UserJPushMapper.java

@@ -0,0 +1,13 @@
+package com.txz.mall.dao;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.txz.mall.model.UserJPush;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * @author: MTD®️
+ * @date: 2025/9/17
+ */
+@Mapper
+public interface UserJPushMapper extends BaseMapper<UserJPush> {
+}

+ 51 - 0
mall-service/src/main/java/com/txz/mall/model/UserJPush.java

@@ -0,0 +1,51 @@
+package com.txz.mall.model;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.extension.activerecord.Model;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.Date;
+
+/**
+ * @author: MTD®️
+ * @date: 2025/9/17
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@TableName("m_user_jpush")
+public class UserJPush extends Model<UserJPush> {
+    
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    
+    /**
+     * 用户id
+     */
+    private Long userId;
+    
+    /**
+     * 极光设备id
+     */
+    private String jpushDeviceId;
+    
+    /**
+     * 用户语言
+     */
+    private String userLanguage;
+    
+    /**
+     * 平台 android ios
+     */
+    private String platform;
+    
+    /**
+     * 最后绑定时间
+     */
+    private Date lastBindTime;
+    
+}

+ 12 - 0
mall-service/src/main/java/com/txz/mall/service/UserJPushService.java

@@ -0,0 +1,12 @@
+package com.txz.mall.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.txz.mall.model.UserJPush;
+
+/**
+ * @author: MTD®️
+ * @date: 2025/9/17
+ */
+
+public interface UserJPushService extends IService<UserJPush> {
+}

+ 145 - 0
mall-service/src/main/java/com/txz/mall/service/impl/NoticeServiceImpl.java

@@ -2,18 +2,30 @@ package com.txz.mall.service.impl;
 
 import cn.hutool.core.bean.BeanUtil;
 import cn.hutool.core.collection.CollectionUtil;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.txz.mall.dao.NoticeMapper;
 import com.txz.mall.enums.NoticeEnum;
 import com.txz.mall.model.Notice;
+import com.txz.mall.model.UserJPush;
 import com.txz.mall.service.NoticeService;
+import com.txz.mall.service.UserJPushService;
+import com.txz.mall.util.NoticeI18nUtil;
 import dto.NoticeDTO;
+import io.github.engagelab.bean.push.PushParam;
+import io.github.engagelab.bean.push.PushResult;
+import io.github.engagelab.bean.push.message.notification.NotificationMessage;
+import io.github.engagelab.bean.push.to.To;
+import io.github.engagelab.enums.Platform;
+import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
 
 /**
  * @author: MTD®️
@@ -21,8 +33,13 @@ import java.util.List;
  */
 @Slf4j
 @Service
+@AllArgsConstructor
 public class NoticeServiceImpl extends ServiceImpl<NoticeMapper, Notice> implements NoticeService {
     
+    private final io.github.engagelab.api.PushApi pushApi;
+    
+    private final UserJPushService userJPushService;
+    
     @Override
     public void addNotice(NoticeDTO notice, Long... uids) {
         if (CollectionUtil.isEmpty(Arrays.asList(uids))) {
@@ -30,6 +47,7 @@ public class NoticeServiceImpl extends ServiceImpl<NoticeMapper, Notice> impleme
         }
         List<Notice> saveBatch = new ArrayList<>();
         Notice info = BeanUtil.copyProperties(notice, Notice.class);
+        
         Arrays.stream(uids).forEach(uid -> {
             info.setUid(uid);
             info.setReadFlag(Boolean.FALSE);
@@ -37,6 +55,133 @@ public class NoticeServiceImpl extends ServiceImpl<NoticeMapper, Notice> impleme
             saveBatch.add(info);
         });
         this.saveBatch(saveBatch);
+        
+        
+        // 极光推送
+        List<UserJPush> pushList = userJPushService.list(Wrappers.<UserJPush>lambdaQuery()
+                .in(UserJPush::getUserId, Arrays.asList(uids))
+        );
+        
+        PushParam param = new PushParam();
+        PushParam.Body body = new PushParam.Body();
+        
+        Map<String, List<UserJPush>> languages = pushList.stream().collect(Collectors.groupingBy(UserJPush::getUserLanguage));
+        
+        // 单独处理孟加拉区域
+        List<UserJPush> bns = languages.get("bn");
+        if (CollectionUtil.isNotEmpty(bns)) {
+            // android 通知内容
+            NotificationMessage.Android android = new NotificationMessage.Android();
+            // ios 通知内容
+            NotificationMessage.IOS ios = new NotificationMessage.IOS();
+            switch (notice.getNoticeType()) {
+                default:
+                    android.setTitle(NoticeI18nUtil.get("bn", notice.getNoticeType().getTitle()));
+                    android.setAlert(String.format(NoticeI18nUtil.get("bn", notice.getNoticeType().getContent()), notice.getNoticeMessage()));
+                    ios.setAlert(NoticeI18nUtil.get("bn", notice.getNoticeType().getTitle()) + "," + String.format(NoticeI18nUtil.get("bn", notice.getNoticeType().getContent()), notice.getNoticeMessage()));
+                    break;
+                case OTHER:
+                    android.setTitle(notice.getNoticeTitle());
+                    android.setAlert(notice.getNoticeMessage());
+                    ios.setAlert(notice.getNoticeTitle() + "," + notice.getNoticeMessage());
+                    break;
+            }
+            NotificationMessage notificationMessage = new NotificationMessage();
+            notificationMessage.setAlert(NoticeI18nUtil.get("bn", notice.getNoticeType().getTitle()));
+            notificationMessage.setAndroid(android);
+            notificationMessage.setIos(ios);
+            body.setNotification(notificationMessage);
+            // 目标人群
+            To to = new To();
+            to.setRegistrationIdList(bns.stream().map(UserJPush::getJpushDeviceId).collect(Collectors.toList()));
+            // 指定目标
+            param.setTo(to);
+            
+            // 指定平台
+            body.setPlatform(Arrays.asList(Platform.android, Platform.ios));
+            
+            // 发送
+            param.setBody(body);
+            PushResult result = pushApi.push(param);
+            log.info("孟加拉极光推送结果:{}", result);
+        }
+        
+        List<UserJPush> ens = languages.get("en");
+        if (CollectionUtil.isNotEmpty(ens)) {
+            // android 通知内容
+            NotificationMessage.Android android = new NotificationMessage.Android();
+            // ios 通知内容
+            NotificationMessage.IOS ios = new NotificationMessage.IOS();
+            switch (notice.getNoticeType()) {
+                default:
+                    android.setTitle(NoticeI18nUtil.get("en", notice.getNoticeType().getTitle()));
+                    android.setAlert(String.format(NoticeI18nUtil.get("en", notice.getNoticeType().getContent()), notice.getNoticeMessage()));
+                    ios.setAlert(NoticeI18nUtil.get("en", notice.getNoticeType().getTitle()) + "," + String.format(NoticeI18nUtil.get("en", notice.getNoticeType().getContent()), notice.getNoticeMessage()));
+                    break;
+                case OTHER:
+                    android.setTitle(notice.getNoticeTitle());
+                    android.setAlert(notice.getNoticeMessage());
+                    ios.setAlert(notice.getNoticeTitle() + "," + notice.getNoticeMessage());
+                    break;
+            }
+            NotificationMessage notificationMessage = new NotificationMessage();
+            notificationMessage.setAlert(NoticeI18nUtil.get("en", notice.getNoticeType().getTitle()));
+            notificationMessage.setAndroid(android);
+            notificationMessage.setIos(ios);
+            body.setNotification(notificationMessage);
+            // 目标人群
+            To to = new To();
+            to.setRegistrationIdList(ens.stream().map(UserJPush::getJpushDeviceId).collect(Collectors.toList()));
+            // 指定目标
+            param.setTo(to);
+            
+            // 指定平台
+            body.setPlatform(Arrays.asList(Platform.android, Platform.ios));
+            
+            // 发送
+            param.setBody(body);
+            PushResult result = pushApi.push(param);
+            log.info("英文极光推送结果:{}", result);
+        }
+        
+        List<UserJPush> zhs = languages.get("zh");
+        if (CollectionUtil.isNotEmpty(zhs)) {
+            // android 通知内容
+            NotificationMessage.Android android = new NotificationMessage.Android();
+            // ios 通知内容
+            NotificationMessage.IOS ios = new NotificationMessage.IOS();
+            switch (notice.getNoticeType()) {
+                default:
+                    android.setTitle(NoticeI18nUtil.get("zh", notice.getNoticeType().getTitle()));
+                    android.setAlert(String.format(NoticeI18nUtil.get("zh", notice.getNoticeType().getContent()), notice.getNoticeMessage()));
+                    ios.setAlert(NoticeI18nUtil.get("zh", notice.getNoticeType().getTitle()) + "," + String.format(NoticeI18nUtil.get("zh", notice.getNoticeType().getContent()), notice.getNoticeMessage()));
+                    break;
+                case OTHER:
+                    android.setTitle(notice.getNoticeTitle());
+                    android.setAlert(notice.getNoticeMessage());
+                    ios.setAlert(notice.getNoticeTitle() + "," + notice.getNoticeMessage());
+                    break;
+            }
+            NotificationMessage notificationMessage = new NotificationMessage();
+            notificationMessage.setAlert(NoticeI18nUtil.get("zh", notice.getNoticeType().getTitle()));
+            notificationMessage.setAndroid(android);
+            notificationMessage.setIos(ios);
+            body.setNotification(notificationMessage);
+            // 目标人群
+            To to = new To();
+            to.setRegistrationIdList(zhs.stream().map(UserJPush::getJpushDeviceId).collect(Collectors.toList()));
+            // 指定目标
+            param.setTo(to);
+            
+            // 指定平台
+            body.setPlatform(Arrays.asList(Platform.android, Platform.ios));
+            
+            // 发送
+            param.setBody(body);
+            PushResult result = pushApi.push(param);
+            log.info("中文极光推送结果:{}", result);
+        }
+        
     }
     
     @Override

+ 17 - 0
mall-service/src/main/java/com/txz/mall/service/impl/UserJPushServiceImpl.java

@@ -0,0 +1,17 @@
+package com.txz.mall.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.txz.mall.dao.UserJPushMapper;
+import com.txz.mall.model.UserJPush;
+import com.txz.mall.service.UserJPushService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+/**
+ * @author: MTD®️
+ * @date: 2025/9/17
+ */
+@Slf4j
+@Service
+public class UserJPushServiceImpl extends ServiceImpl<UserJPushMapper, UserJPush> implements UserJPushService {
+}

+ 96 - 0
mall-service/src/main/java/com/txz/mall/util/NoticeI18nUtil.java

@@ -0,0 +1,96 @@
+package com.txz.mall.util;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PostConstruct;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 通知国际化工具类
+ * 用于读取和获取通知消息的多语言内容
+ */
+@Slf4j
+@Component
+public class NoticeI18nUtil {
+
+    private static final String BN_JSON_PATH = "i18n/notice/bn.json";
+    private static final String ZH_JSON_PATH = "i18n/notice/zh.json";
+    private static final String EN_JSON_PATH = "i18n/notice/en.json";
+
+    private Map<String, JSONObject> languageMap = new HashMap<>();
+
+    @PostConstruct
+    public void init() {
+        loadLanguageFile("bn", BN_JSON_PATH);
+        loadLanguageFile("zh", ZH_JSON_PATH);
+        loadLanguageFile("en", EN_JSON_PATH);
+    }
+
+    /**
+     * 加载语言文件
+     *
+     * @param language 语言标识
+     * @param path     文件路径
+     */
+    private void loadLanguageFile(String language, String path) {
+        try {
+            ClassPathResource resource = new ClassPathResource(path);
+            InputStream inputStream = resource.getInputStream();
+            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
+            StringBuilder stringBuilder = new StringBuilder();
+            String line;
+            while ((line = reader.readLine()) != null) {
+                stringBuilder.append(line);
+            }
+            JSONObject jsonObject = JSON.parseObject(stringBuilder.toString());
+            languageMap.put(language, jsonObject);
+            log.info("成功加载通知语言文件: {}", path);
+        } catch (IOException e) {
+            log.error("加载语言文件失败: {}", path, e);
+        }
+    }
+
+    /**
+     * 根据语言和键获取对应的消息内容
+     *
+     * @param language 语言标识 (bn/zh/en)
+     * @param key      消息键
+     * @return 对应的消息内容,如果找不到则返回键本身
+     */
+    public static String get(String language, String key) {
+        NoticeI18nUtil util = SpringContextUtil.getBean(NoticeI18nUtil.class);
+        if (util.languageMap.containsKey(language)) {
+            JSONObject langObject = util.languageMap.get(language);
+            if (langObject.containsKey(key)) {
+                return langObject.getString(key);
+            }
+        }
+        return key;
+    }
+
+    /**
+     * 根据语言和键获取对应的消息内容,支持格式化参数
+     *
+     * @param language 语言标识 (bn/zh/en)
+     * @param key      消息键
+     * @param args     格式化参数
+     * @return 对应的消息内容,如果找不到则返回键本身
+     */
+    public static String get(String language, String key, Object... args) {
+        String message = get(language, key);
+        if (args != null && args.length > 0) {
+            return String.format(message, args);
+        }
+        return message;
+    }
+}

+ 63 - 0
mall-service/src/main/java/com/txz/mall/util/SpringContextUtil.java

@@ -0,0 +1,63 @@
+package com.txz.mall.util;
+
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.stereotype.Component;
+
+/**
+ * Spring上下文工具类
+ * 用于在非Spring管理的类中获取Spring Bean
+ */
+@Component
+public class SpringContextUtil implements ApplicationContextAware {
+
+    private static ApplicationContext applicationContext;
+
+    @Override
+    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+        SpringContextUtil.applicationContext = applicationContext;
+    }
+
+    /**
+     * 获取applicationContext
+     *
+     * @return ApplicationContext
+     */
+    public static ApplicationContext getApplicationContext() {
+        return applicationContext;
+    }
+
+    /**
+     * 通过name获取 Bean
+     *
+     * @param name Bean名称
+     * @return Bean实例
+     */
+    public static Object getBean(String name) {
+        return getApplicationContext().getBean(name);
+    }
+
+    /**
+     * 通过class获取Bean
+     *
+     * @param clazz Bean类型
+     * @param <T>   Bean类型参数
+     * @return Bean实例
+     */
+    public static <T> T getBean(Class<T> clazz) {
+        return getApplicationContext().getBean(clazz);
+    }
+
+    /**
+     * 通过name和class返回指定的Bean
+     *
+     * @param name  Bean名称
+     * @param clazz Bean类型
+     * @param <T>   Bean类型参数
+     * @return Bean实例
+     */
+    public static <T> T getBean(String name, Class<T> clazz) {
+        return getApplicationContext().getBean(name, clazz);
+    }
+}

+ 31 - 0
mall-service/src/main/java/com/txz/mall/web/ro/JPushBindRO.java

@@ -0,0 +1,31 @@
+package com.txz.mall.web.ro;
+
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+
+/**
+ * @author: MTD®️
+ * @date: 2025/9/17
+ */
+@Data
+public class JPushBindRO {
+    
+    /**
+     * 设备ID
+     */
+    @NotBlank(message = "设备ID不能为空")
+    private String jpushDeviceId;
+    
+    /**
+     * 平台
+     */
+    @NotBlank(message = "平台")
+    private String platform;
+    
+    /**
+     * 语言
+     */
+    private String userLanguage;
+    
+}

+ 36 - 0
mall-service/src/main/resources/i18n/notice/bn.json

@@ -0,0 +1,36 @@
+{
+  "notifications.order.paymentSuccess.title": "গ্রুপ বাই পেমেন্ট সফল",
+  "notifications.order.paymentSuccess.content": "আপনার অংশগ্রহণ করা গ্রুপ অর্ডার [%s] সফলভাবে পেমেন্ট হয়েছে",
+  "notifications.order.groupBuyWin.title": "গ্রুপ বাই সফল",
+  "notifications.order.groupBuyWin.content": "আপনার অংশগ্রহণ করা গ্রুপ অর্ডার [%s] নির্বাচিত হয়েছে",
+  "notifications.order.groupBuyLose.title": "গ্রুপ বাই সফল",
+  "notifications.order.groupBuyLose.content": "আপনার অংশগ্রহণ করা গ্রুপ অর্ডার [%s] নির্বাচিত হয়নি",
+  "notifications.order.provideAddress.title": "অর্ডার শিপিং ঠিকানা প্রদান",
+  "notifications.order.provideAddress.content": "আপনার অংশগ্রহণ করা গ্রুপ অর্ডার [%s] অনুগ্রহ করে আপনার শিপিং ঠিকানা প্রদান করুন",
+  "notifications.order.groupBuyFail.title": "গ্রুপ বাই ব্যর্থ",
+  "notifications.order.groupBuyFail.content": "আপনার অংশগ্রহণ করা গ্রুপ অর্ডার [%s] ব্যর্থ হয়েছে",
+  "notifications.order.shipped.title": "অর্ডার সফলভাবে শিপ হয়েছে",
+  "notifications.order.shipped.content": "আপনার অংশগ্রহণ করা গ্রুপ অর্ডার [%s] সফলভাবে শিপ হয়েছে",
+  "notifications.reward.referFriends.title": "বন্ধু রেফার পুরস্কার",
+  "notifications.reward.referFriends.content": "আপনি বন্ধু রেফারের জন্য পুরস্কার পেয়েছেন",
+  "notifications.reward.groupBuy.title": "গ্রুপ বাই যোগ দিন পুরস্কার",
+  "notifications.reward.groupBuy.content": "আপনি একটি পুরস্কার পেয়েছেন, অর্ডার আইডি [%s]",
+  "notifications.reward.openGroupBuy.title": "গ্রুপ বাই খোলা পুরস্কার",
+  "notifications.reward.openGroupBuy.content": "আপনি একটি পুরস্কার পেয়েছেন, অর্ডার আইডি [%s]",
+  "notifications.reward.directReferral.title": "সরাসরি রেফারেল পুরস্কার",
+  "notifications.reward.directReferral.content": "আপনি সরাসরি রেফারেলের জন্য পুরস্কার পেয়েছেন",
+  "notifications.reward.checkin.title": "চেক-ইন পুরস্কার",
+  "notifications.reward.checkin.content": "আপনি চেক-ইনের জন্য পুরস্কার পেয়েছেন",
+  "notifications.reward.firstCommission.title": "সরাসরি রেফারেল পুরস্কার",
+  "notifications.reward.firstCommission.content": "আপনি সরাসরি রেফারেলের জন্য পুরস্কার পেয়েছেন",
+  "notifications.reward.secondaryCommission.title": "সরাসরি রেফারেল পুরস্কার",
+  "notifications.reward.secondaryCommission.content": "আপনি সরাসরি রেফারেলের জন্য পুরস্কার পেয়েছেন",
+  "notifications.money.rechargeSuccess.title": "রিচার্জ সফল",
+  "notifications.money.rechargeSuccess.content": "আপনার KLICKwallet সফলভাবে রিচার্জ হয়েছে",
+  "notifications.money.withdrawalAccountSuccess.title": "উত্তোলন সফল",
+  "notifications.money.withdrawalAccountSuccess.content": "আপনার রাজস্ব অ্যাকাউন্ট উত্তোলনের অনুরোধ প্রক্রিয়াকরণ করা হয়েছে",
+  "notifications.money.withdrawalWalletSuccess.title": "উত্তোলন সফল",
+  "notifications.money.withdrawalWalletSuccess.content": "আপনার KLICK ওয়ালেট উত্তোলনের অনুরোধ প্রক্রিয়াকরণ করা হয়েছে",
+  "notifications.money.withdrawalFail.title": "উত্তোলন ব্যর্থ",
+  "notifications.money.withdrawalFail.content": "আপনার উত্তোলনের অনুরোধ ব্যর্থ হয়েছে"
+}

+ 36 - 0
mall-service/src/main/resources/i18n/notice/en.json

@@ -0,0 +1,36 @@
+{
+  "notifications.order.paymentSuccess.title": "Group Buy Payment Successful",
+  "notifications.order.paymentSuccess.content": "The group order you participated in [%s] has been successfully paid",
+  "notifications.order.groupBuyWin.title": "Group Buy Successful",
+  "notifications.order.groupBuyWin.content": "The group order you participated in [%s] has been selected",
+  "notifications.order.groupBuyLose.title": "Group Buy Successful",
+  "notifications.order.groupBuyLose.content": "The group order you participated in [%s] was not selected",
+  "notifications.order.provideAddress.title": "Order provide shipping address",
+  "notifications.order.provideAddress.content": "The group order you participated in [%s] Please provide your shipping address",
+  "notifications.order.groupBuyFail.title": "Group Buy Failed",
+  "notifications.order.groupBuyFail.content": "The group order you participated in [%s] has failed",
+  "notifications.order.shipped.title": "Order shipped successfully",
+  "notifications.order.shipped.content": "The group order you participated in [%s] has been successfully shipped",
+  "notifications.reward.referFriends.title": "Refer Friends Reward",
+  "notifications.reward.referFriends.content": "You have received the reward for refer friends",
+  "notifications.reward.groupBuy.title": "Join Group Buy Reward",
+  "notifications.reward.groupBuy.content": "You have received a reward, order ID [%s]",
+  "notifications.reward.openGroupBuy.title": "Open Group Buy Reward",
+  "notifications.reward.openGroupBuy.content": "You have received a reward, order ID [%s]",
+  "notifications.reward.directReferral.title": "Direct Referral Reward",
+  "notifications.reward.directReferral.content": "You have received the reward for Direct Referral",
+  "notifications.reward.checkin.title": "Check-in Reward",
+  "notifications.reward.checkin.content": "You have received the reward for Check-in",
+  "notifications.reward.firstCommission.title": "Direct Referral Reward",
+  "notifications.reward.firstCommission.content": "You have received the reward for Direct Referral",
+  "notifications.reward.secondaryCommission.title": "Direct Referral Reward",
+  "notifications.reward.secondaryCommission.content": "You have received the reward for Direct Referral",
+  "notifications.money.rechargeSuccess.title": "Recharge Successful",
+  "notifications.money.rechargeSuccess.content": "Your KLICKwallet has been successfully recharged",
+  "notifications.money.withdrawalAccountSuccess.title": "Withdrawal Successful",
+  "notifications.money.withdrawalAccountSuccess.content": "Your Revenue Account withdrawal request has been processed",
+  "notifications.money.withdrawalWalletSuccess.title": "Withdrawal Successful",
+  "notifications.money.withdrawalWalletSuccess.content": "Your KLICK wallet withdrawal request has been processed",
+  "notifications.money.withdrawalFail.title": "Withdrawal Failed",
+  "notifications.money.withdrawalFail.content": "Your withdrawal request has been failed"
+}

+ 36 - 0
mall-service/src/main/resources/i18n/notice/zh.json

@@ -0,0 +1,36 @@
+{
+  "notifications.order.paymentSuccess.title": "团购支付成功",
+  "notifications.order.paymentSuccess.content": "您参与的团购订单[%s]已支付成功",
+  "notifications.order.groupBuyWin.title": "团购成功",
+  "notifications.order.groupBuyWin.content": "您参与的团购订单[%s]已中选",
+  "notifications.order.groupBuyLose.title": "团购成功",
+  "notifications.order.groupBuyLose.content": "您参与的团购订单[%s]未中选",
+  "notifications.order.provideAddress.title": "订单提供收货地址",
+  "notifications.order.provideAddress.content": "您参与的团购订单[%s]请提供收货地址",
+  "notifications.order.groupBuyFail.title": "团购失败",
+  "notifications.order.groupBuyFail.content": "您参与的团购订单[%s]已失败",
+  "notifications.order.shipped.title": "订单发货成功",
+  "notifications.order.shipped.content": "您参与的团购订单[%s]已成功发货",
+  "notifications.reward.referFriends.title": "推荐好友奖励",
+  "notifications.reward.referFriends.content": "您已获得推荐好友奖励",
+  "notifications.reward.groupBuy.title": "参团奖励",
+  "notifications.reward.groupBuy.content": "您已获得奖励,订单号[%s]",
+  "notifications.reward.openGroupBuy.title": "开团奖励",
+  "notifications.reward.openGroupBuy.content": "您已获得开团奖励,订单号[%s]",
+  "notifications.reward.directReferral.title": "直接推荐奖励",
+  "notifications.reward.directReferral.content": "您已获得直接推荐奖励",
+  "notifications.reward.checkin.title": "签到奖励",
+  "notifications.reward.checkin.content": "您已获得签到奖励",
+  "notifications.reward.firstCommission.title": "Direct Referral Reward",
+  "notifications.reward.firstCommission.content": "You have received the reward for Direct Referral",
+  "notifications.reward.secondaryCommission.title": "Direct Referral Reward",
+  "notifications.reward.secondaryCommission.content": "You have received the reward for Direct Referral",
+  "notifications.money.rechargeSuccess.title": "充值成功",
+  "notifications.money.rechargeSuccess.content": "您的KLICK钱包已成功充值",
+  "notifications.money.withdrawalAccountSuccess.title": "提现成功",
+  "notifications.money.withdrawalAccountSuccess.content": "您的收益账户提现请求已处理",
+  "notifications.money.withdrawalWalletSuccess.title": "提现成功",
+  "notifications.money.withdrawalWalletSuccess.content": "您的KLICK钱包提现请求已处理",
+  "notifications.money.withdrawalFail.title": "提现失败",
+  "notifications.money.withdrawalFail.content": "您的提现请求已失败"
+}