src/main/java/com/ruoyi/project/system/controller/SysUserClientController.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,44 @@ package com.ruoyi.project.system.controller; import com.ruoyi.framework.security.LoginUser; import com.ruoyi.framework.web.controller.BaseController; import com.ruoyi.framework.web.domain.AjaxResult; import com.ruoyi.project.system.domain.GetuiConfig; import com.ruoyi.project.system.domain.SysUserClient; import com.ruoyi.project.system.service.SysUserClientService; import com.ruoyi.common.utils.SecurityUtils; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.springframework.beans.factory.annotation.Autowired; 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; /** * ç¨æ·å®å设å¤ç®¡çæ§å¶å± * * @author deslrey * @version 1.0 * @since 2026/2/9 */ @Api(tags = "ç¨æ·è®¾å¤ç»å®") @RestController @RequestMapping("/system/client") public class SysUserClientController extends BaseController { @Autowired private SysUserClientService sysUserClientService; /** * æ·»å /æ´æ°ç¨æ·cid */ @PostMapping("/addOrUpdateClientId") @ApiOperation("æ·»å /æ´æ°ç¨æ·cid") public AjaxResult addOrUpdateClientId(@RequestBody SysUserClient sysUserClient) { Long userId = SecurityUtils.getUserId(); sysUserClient.setUserId(userId); boolean result = sysUserClientService.addOrUpdateClientId(sysUserClient); return result ? success() : error("设å¤ç»å®å¤±è´¥"); } } src/main/java/com/ruoyi/project/system/domain/GetuiConfig.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,46 @@ package com.ruoyi.project.system.domain; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; /** * <p> * ä¸ªæ¨ (Unipush v2) æ¶æ¯æ¨éé 置类 * </p> * * @author deslrey * @version 1.0 * @since 2026/2/9 */ @Data @Component @ConfigurationProperties(prefix = "ruoyi.getui") public class GetuiConfig { /** * AppID */ private String appId; /** * AppKey */ private String appKey; /** * MasterSecret */ private String masterSecret; /** * ä¸ªæ¨ RESTful API ååå°å */ private String domain; /** * 离线æ¨é Intent ç®æ ç»ä»¶å * æ ¼å¼: å å/å ¥å£Activityå */ private String intentComponent; } src/main/java/com/ruoyi/project/system/domain/SysUserClient.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,47 @@ package com.ruoyi.project.system.domain; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import com.fasterxml.jackson.annotation.JsonFormat; import lombok.Data; import lombok.NoArgsConstructor; import lombok.AllArgsConstructor; import java.io.Serializable; import java.util.Date; /** * <br> * ç¨æ·å®å设å¤å ³è对象 sys_user_client * </br> * * @author deslrey * @version 1.0 * @since 2026/2/9 */ @Data @NoArgsConstructor @AllArgsConstructor @TableName("sys_user_client") public class SysUserClient implements Serializable { private static final long serialVersionUID = 1L; /** * ç¨æ·ID */ @TableId(type = IdType.INPUT) private Long userId; /** * 个æ¨è®¾å¤æ è¯ (CID) */ private String cid; /** * æåæ´»è·æ¶é´ */ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private Date updateTime; } src/main/java/com/ruoyi/project/system/mapper/SysMenuMapper.java
@@ -122,4 +122,12 @@ * @return ç»æ */ public SysMenu checkMenuNameUnique(@Param("menuName") String menuName, @Param("parentId") Long parentId); /** * æ ¹æ®è·¯ç±å°åæ¥è¯¢èå * * @param lastSegment è·¯ç±å°å * @return èå */ SysMenu selectMenuByPath(String lastSegment); } src/main/java/com/ruoyi/project/system/mapper/SysUserClientMapper.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,18 @@ package com.ruoyi.project.system.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.ruoyi.project.system.domain.SysUserClient; /** * <br> * ç¨æ·å®å设å¤å ³èmapper * </br> * * @author deslrey * @version 1.0 * @since 2026/2/9 */ public interface SysUserClientMapper extends BaseMapper<SysUserClient> { } src/main/java/com/ruoyi/project/system/service/SysUserClientService.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,19 @@ package com.ruoyi.project.system.service; import com.baomidou.mybatisplus.extension.service.IService; import com.ruoyi.project.system.domain.SysUserClient; /** * <br> * ç¨æ·å®å设å¤å ³èæ¥å£ * </br> * * @author deslrey * @version 1.0 * @since 2026/2/9 */ public interface SysUserClientService extends IService<SysUserClient> { boolean addOrUpdateClientId(SysUserClient sysUserClient); } src/main/java/com/ruoyi/project/system/service/impl/SysNoticeServiceImpl.java
@@ -53,6 +53,9 @@ @Lazy private ISysNoticeService sysNoticeService; @Autowired private UnipushService unipushService; /** * æ¥è¯¢å ¬åä¿¡æ¯ * @@ -146,6 +149,11 @@ Long tenantId = SecurityUtils.getLoginUser().getTenantId(); List<SysNotice> sysNotices = consigneeId.stream().map(it -> convertSysNotice(title, message, it,tenantId, jumpPath, userId)).collect(Collectors.toList()); sysNoticeService.saveBatch(sysNotices); try { unipushService.sendClientMessage(sysNotices); } catch (Exception e) { log.error("APPæ¨ééç¥å¤±è´¥ï¼åå : {}", e); } } @Override @@ -197,6 +205,11 @@ .collect(Collectors.toList()); sysNoticeService.saveBatch(collect); try { unipushService.sendClientMessage(collect); } catch (Exception e) { log.error("APPæ¨ééç¥å¤±è´¥ï¼åå : {}", e); } } src/main/java/com/ruoyi/project/system/service/impl/SysUserClientServiceImpl.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,43 @@ package com.ruoyi.project.system.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.project.system.domain.SysUserClient; import com.ruoyi.project.system.mapper.SysUserClientMapper; import com.ruoyi.project.system.service.SysUserClientService; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.Date; /** * ç¨æ·å®å设å¤å ³èæ¥å£å®ç°ç±» * * @author deslrey * @version 1.0 * @since 2026/2/9 */ @Service public class SysUserClientServiceImpl extends ServiceImpl<SysUserClientMapper, SysUserClient> implements SysUserClientService { @Override @Transactional(rollbackFor = Exception.class) public boolean addOrUpdateClientId(SysUserClient sysUserClient) { if (sysUserClient == null || sysUserClient.getUserId() == null || StringUtils.isEmpty(sysUserClient.getCid())) { return false; } String cid = sysUserClient.getCid(); Long userId = sysUserClient.getUserId(); remove(new LambdaQueryWrapper<SysUserClient>().eq(SysUserClient::getCid, cid).ne(SysUserClient::getUserId, userId)); SysUserClient userClient = new SysUserClient(); userClient.setUserId(userId); userClient.setCid(cid); userClient.setUpdateTime(new Date()); return saveOrUpdate(userClient); } } src/main/java/com/ruoyi/project/system/service/impl/UnipushService.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,198 @@ package com.ruoyi.project.system.service.impl; import com.getui.push.v2.sdk.ApiHelper; import com.getui.push.v2.sdk.GtApiConfiguration; import com.getui.push.v2.sdk.api.PushApi; import com.getui.push.v2.sdk.common.ApiResult; import com.getui.push.v2.sdk.dto.req.Audience; import com.getui.push.v2.sdk.dto.req.message.PushChannel; import com.getui.push.v2.sdk.dto.req.message.PushDTO; import com.getui.push.v2.sdk.dto.req.message.PushMessage; import com.getui.push.v2.sdk.dto.req.message.android.AndroidDTO; import com.getui.push.v2.sdk.dto.req.message.android.ThirdNotification; import com.getui.push.v2.sdk.dto.req.message.android.Ups; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.project.system.domain.GetuiConfig; import com.ruoyi.project.system.domain.SysMenu; import com.ruoyi.project.system.domain.SysNotice; import com.ruoyi.project.system.domain.SysUserClient; import com.ruoyi.project.system.mapper.SysMenuMapper; import com.ruoyi.project.system.service.SysUserClientService; import lombok.extern.slf4j.Slf4j; import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import java.util.List; import java.util.Map; /** * APPæ¶æ¯æ¨éæå¡ * * @author deslrey * @version 1.4 * @since 2026/2/9 */ @Slf4j @Component public class UnipushService { @Autowired private SysMenuMapper sysMenuMapper; @Autowired private GetuiConfig getuiConfig; @Autowired private SysUserClientService userClientService; private PushApi pushApi; private static final String DEFAULT_APP_PAGE = "pages/index"; @PostConstruct public void init() { GtApiConfiguration config = new GtApiConfiguration(); config.setAppId(getuiConfig.getAppId()); config.setAppKey(getuiConfig.getAppKey()); config.setMasterSecret(getuiConfig.getMasterSecret()); config.setDomain(getuiConfig.getDomain()); ApiHelper apiHelper = ApiHelper.build(config); this.pushApi = apiHelper.creatApi(PushApi.class); } /** * æ¹éåééç¥å ¬åå°ç§»å¨ç«¯ */ @Async public void sendClientMessage(List<SysNotice> sysNoticeList) { if (sysNoticeList == null || sysNoticeList.isEmpty()) { return; } for (SysNotice sysNotice : sysNoticeList) { SysUserClient client = userClientService.getById(sysNotice.getConsigneeId()); if (client == null || StringUtils.isEmpty(client.getCid())) { log.warn("ç¨æ· {} æªç»å®ç§»å¨ç«¯ CID,è·³è¿æ¨é", sysNotice.getConsigneeId()); continue; } // 转æ¢è·¯å¾ String appPath = convertWebPathToAppPath(sysNotice.getJumpPath()); // æ¨é sendRoutingPush( client.getCid(), sysNotice.getNoticeTitle(), sysNotice.getRemark() != null ? sysNotice.getRemark() : sysNotice.getNoticeContent(), appPath ); } } /** * å° Web 端åå±å ¨è·¯å¾è½¬æ¢ä¸º App 端ç»ä»¶è·¯ç± */ private String convertWebPathToAppPath(String webPath) { if (StringUtils.isEmpty(webPath)) { return DEFAULT_APP_PAGE; } String pathOnly = webPath; String queryString = ""; if (webPath.contains("?")) { int index = webPath.indexOf("?"); pathOnly = webPath.substring(0, index); queryString = webPath.substring(index); } String[] pathSegments = pathOnly.split("/"); String lastSegment = ""; for (int i = pathSegments.length - 1; i >= 0; i--) { if (StringUtils.isNotEmpty(pathSegments[i])) { lastSegment = pathSegments[i]; break; } } if (StringUtils.isEmpty(lastSegment)) { return DEFAULT_APP_PAGE; } SysMenu menu = sysMenuMapper.selectMenuByPath(lastSegment); if (menu != null && StringUtils.isNotEmpty(menu.getAppComponent())) { String appComponent = menu.getAppComponent(); if (appComponent.startsWith("/")) { appComponent = appComponent.substring(1); } return appComponent + queryString; } return DEFAULT_APP_PAGE; } /** * åéåäººè·¯ç±æ¨é */ private void sendRoutingPush(String cid, String title, String content, String targetPath) { log.info("å夿¨éæ¶æ¯: CID={}, Title={}, TargetPath={}", cid, title, targetPath); PushDTO<Audience> pushDTO = new PushDTO<>(); pushDTO.setRequestId("REQ_" + System.currentTimeMillis()); // å¨çº¿éä¼ å 容 PushMessage pushMessage = new PushMessage(); String transmissionContent = String.format( "{\"title\":\"%s\",\"content\":\"%s\",\"payload\":\"%s\"}", title, content, targetPath ); pushMessage.setTransmission(transmissionContent); pushDTO.setPushMessage(pushMessage); // æ¥æ¶äºº Audience audience = new Audience(); audience.addCid(cid); pushDTO.setAudience(audience); // 离线æ¨ééé pushDTO.setPushChannel(getPushChannel(title, content, targetPath)); try { ApiResult<Map<String, Map<String, String>>> result = pushApi.pushToSingleByCid(pushDTO); if (result.isSuccess()) { log.info("Unipush æ¨éæå: CID={}", cid); } else { log.error("Unipush æ¨é失败: CID={}, Code={}, Msg={}", cid, result.getCode(), result.getMsg()); } } catch (Exception e) { log.error("Unipush æ¨éå¼å¸¸: ", e); } } @NotNull private PushChannel getPushChannel(String title, String content, String targetPath) { PushChannel pushChannel = new PushChannel(); AndroidDTO androidDTO = new AndroidDTO(); Ups ups = new Ups(); ThirdNotification thirdNotification = new ThirdNotification(); thirdNotification.setTitle(title); thirdNotification.setBody(content); thirdNotification.setClickType("intent"); String intent = "intent:#Intent;launchFlags=0x04000000;" + "component=" + getuiConfig.getIntentComponent() + ";" + "S.UP-OL-P9=true;" + "S.path=" + targetPath + ";" + "S.payload=" + targetPath + ";" + "end"; thirdNotification.setIntent(intent); ups.setNotification(thirdNotification); androidDTO.setUps(ups); pushChannel.setAndroid(androidDTO); return pushChannel; } } src/main/resources/application-dev.yml
@@ -15,6 +15,16 @@ captchaType: math # åå审æ¹ç¼å·åç¼(é ç½®æä»¶åç¼å½å) approvalNumberPrefix: DEV # ä¸ªæ¨ Unipush é ç½® getui: appId: PfjyAAE0FK64FaO1w2CMb1 appKey: zTMb831OEL6J4GK1uE3Ob4 masterSecret: K1GFtsv42v61tXGnF7SGE5 domain: https://restapi.getui.cn/v2/ # 离线æ¨é使ç¨çå å/ç»ä»¶å intentComponent: uni.app.UNI099A590/io.dcloud.PandoraEntry # å¼åç¯å¢é ç½® server: # æå¡å¨çHTTP端å£ï¼é»è®¤ä¸º8080 src/main/resources/application-new.yml
@@ -15,6 +15,16 @@ captchaType: math # åå审æ¹ç¼å·åç¼(é ç½®æä»¶åç¼å½å) approvalNumberPrefix: NEW # ä¸ªæ¨ Unipush é ç½® getui: appId: PfjyAAE0FK64FaO1w2CMb1 appKey: zTMb831OEL6J4GK1uE3Ob4 masterSecret: K1GFtsv42v61tXGnF7SGE5 domain: https://restapi.getui.cn/v2/ # 离线æ¨é使ç¨çå å/ç»ä»¶å intentComponent: uni.app.UNI099A590/io.dcloud.PandoraEntry # å¼åç¯å¢é ç½® server: # æå¡å¨çHTTP端å£ï¼é»è®¤ä¸º8080 src/main/resources/mapper/system/SysMenuMapper.xml
@@ -85,7 +85,7 @@ where u.user_id = #{userId} and m.menu_type in ('M', 'C') and m.status = 0 AND ro.status = 0 order by m.parent_id, m.order_num </select> <select id="selectMenuListByRoleId" resultType="Long"> select m.menu_id from sys_menu m @@ -133,7 +133,14 @@ <include refid="selectMenuVo"/> where menu_name=#{menuName} and parent_id = #{parentId} limit 1 </select> <select id="selectMenuByPath" resultType="com.ruoyi.project.system.domain.SysMenu" parameterType="java.lang.String"> SELECT menu_id, menu_name, parent_id, path, app_component, status FROM sys_menu WHERE path = #{path} AND status = '0' LIMIT 1 </select> <update id="updateMenu" parameterType="com.ruoyi.project.system.domain.SysMenu"> update sys_menu <set>