liyong
4 天以前 ffbe5016cb2426f16b13d58795c523931cb36f08
Merge remote-tracking branch 'origin/dev_New' into dev_New
已添加7个文件
已修改6个文件
550 ■■■■■ 文件已修改
pom.xml 83 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/controller/SysUserClientController.java 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/domain/GetuiConfig.java 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/domain/SysUserClient.java 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/mapper/SysMenuMapper.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/mapper/SysUserClientMapper.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/service/SysUserClientService.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/service/impl/SysNoticeServiceImpl.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/service/impl/SysUserClientServiceImpl.java 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/service/impl/UnipushService.java 198 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/application-dev.yml 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/application-new.yml 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/system/SysMenuMapper.xml 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pom.xml
@@ -16,7 +16,7 @@
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.15</version>
        <relativePath />
        <relativePath/>
    </parent>
    <properties>
@@ -43,6 +43,7 @@
        <spring-security.version>5.7.12</spring-security.version>
        <spring-framework.version>5.3.39</spring-framework.version>
        <mybatis-plus.version>3.5.3.1</mybatis-plus.version>
        <getui-sdk.version>1.0.7.0</getui-sdk.version>
    </properties>
    <dependencies>
@@ -269,7 +270,6 @@
        </dependency>
        <!-- minio -->
        <dependency>
            <groupId>io.minio</groupId>
@@ -301,51 +301,58 @@
            <artifactId>easyexcel</artifactId>
            <version>4.0.3</version>
        </dependency>
         <dependency>
        <dependency>
            <groupId>com.google.zxing</groupId>
            <artifactId>core</artifactId>
            <version>3.3.3</version>
        </dependency>
        <dependency>
            <groupId>com.getui.push</groupId>
            <artifactId>restful-sdk</artifactId>
            <version>${getui-sdk.version}</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>
    <build>
        <finalName>${project.artifactId}</finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <fork>true</fork> <!-- å¦‚果没有该配置,devtools不会生效 -->
                </configuration>
            </plugin>
    <build>
        <finalName>${project.artifactId}</finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <fork>true</fork> <!-- å¦‚果没有该配置,devtools不会生效 -->
                </configuration>
            </plugin>
        </plugins>
    </build>
    </build>
    <repositories>
        <repository>
            <id>public</id>
            <name>aliyun nexus</name>
            <url>https://maven.aliyun.com/repository/public</url>
            <releases>
                <enabled>true</enabled>
            </releases>
        </repository>
    </repositories>
    <repositories>
        <repository>
            <id>public</id>
            <name>aliyun nexus</name>
            <url>https://maven.aliyun.com/repository/public</url>
            <releases>
                <enabled>true</enabled>
            </releases>
        </repository>
    </repositories>
    <pluginRepositories>
        <pluginRepository>
            <id>public</id>
            <name>aliyun nexus</name>
            <url>https://maven.aliyun.com/repository/public</url>
            <releases>
                <enabled>true</enabled>
            </releases>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </pluginRepository>
    </pluginRepositories>
    <pluginRepositories>
        <pluginRepository>
            <id>public</id>
            <name>aliyun nexus</name>
            <url>https://maven.aliyun.com/repository/public</url>
            <releases>
                <enabled>true</enabled>
            </releases>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </pluginRepository>
    </pluginRepositories>
</project>
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>