SpringBoot+modbus4j实现ModebusTCP通讯定时读取多个plc设备数并存储进redis中

场景

SpringBoot+modbus4j实现ModebusTCP通讯读取数据:

https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/135292378

基于上面对单个PLC设备进行ModbusTCP通讯获取数据的过程。

业务开发中,如果需要定时读取多个设备的数据,且需要将设备数据存储进redis中缓存,以供其他业务功能使用。

注:

博客:
https://blog.csdn.net/badao_liumang_qizhi

实现

新建SpringBoot项目并添加需要的pom依赖

复制代码
        <!--modbus4j 依赖-->
        <dependency>
            <groupId>com.infiniteautomation</groupId>
            <artifactId>modbus4j</artifactId>
            <version>3.0.3</version>
        </dependency>

其他业务依赖

复制代码
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- redis 缓存操作 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>${fastjson.version}</version>
            <!-- 排除SpringBoot的版本管理 -->
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-dependencies</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!--modbus4j 依赖-->
        <dependency>
            <groupId>com.infiniteautomation</groupId>
            <artifactId>modbus4j</artifactId>
            <version>3.0.3</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.6</version>
        </dependency>

配置文件中新增多个PLC设备相关信息,这里是三个,只关注必要连接相关信息,其他冗余信息忽略:

复制代码
#plc相关配置
modbus:
  task:
    rate: "10000"  # 定时任务执行周期
    timeout: 3000  # 读取超时(ms)
  # PLC寄存器配置
  devices:
    - ip: 127.0.0.1
      port: 502
      slaveId: 1
      name: "#1"
      registers:
        runSignal:
          address: "400153.0"  # V152.0运行信号
          type: "bool"
        pressure:
          address: "400201"    # VD200运行压力
          type: "float32"

      cameras:
        recognition:
          ip: "127.0.0.1"
          port: 8000
          username: "admin"
          password: "123456"
          channel: 1 
          userId: 1 

        capture:
          ip: "127.0.0.1"
          port: 8000
          username: "admin"
          password: "123456"
          channel: 2
          userId: 2 
    - ip: 127.0.0.2
      port: 502
      slaveId: 2
      name: "#2"
      registers:
        runSignal:
          address: "400154.0"  # V153.0运行信号
          type: "bool"
        pressure:
          address: "400202"    # VD201运行压力
          type: "float32"
      cameras:
        recognition:
          ip: "127.0.001"
          port: 8000
          username: "admin"
          password: "123456"
          channel: 1 
          userId: 3 
        capture:
          ip: "127.0.0.1"
          port: 8000
          username: "admin"
          password: "123456"
          channel: 2
          userId: 4 
    - ip: 127.0.0.3
      port: 502
      slaveId: 3
      name: "#3"
      registers:
        runSignal:
          address: "400154.0"  # V153.0运行信号
          type: "bool"
        pressure:
          address: "400202"    # VD201运行压力
          type: "float32"
      cameras:
        recognition:
          ip: "127.0.0.1"
          port: 8000
          username: "admin"
          password: "123456"
          channel: 1 
          userId: 5 
        capture:
          ip: "127.0.0.1"
          port: 8000
          username: "admin"
          password: "123456"
          channel: 2
          userId: 6 

增加对应的配制类:

复制代码
package com.badao.demo.config;

import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

@ConfigurationProperties(prefix = "modbus")
@Configuration
@Getter
@Setter
@Component
public class ModbusConfig {

    private transient Map<String, DeviceConfig> recognitionIpToDeviceMap;

    @PostConstruct
    public void initMapping() {
        // 初始化IP映射关系
        recognitionIpToDeviceMap = devices.stream()
                .filter(device -> device.getCameras()  != null)
                .filter(device -> device.getCameras().getRecognition()  != null)
                .collect(Collectors.toMap(
                        device -> device.getCameras().getRecognition().getIp(),
                        Function.identity()
                ));

        System.out.println("已初始化摄像头映射关系"+recognitionIpToDeviceMap);
    }

    public DeviceConfig findDeviceByRecognitionIp(String recognitionIp) {
        return recognitionIpToDeviceMap.get(recognitionIp);
    }

    private  TaskConfig task;
    private  List<DeviceConfig> devices; // 改为List接收

    @Data
    public static class TaskConfig {
        private String rate;
        private int timeout;
    }

    @Data
    public static class DeviceConfig {
        private String ip;       // 设备IP地址
        private int port;        // 端口号
        private int slaveId;     // 从站ID
        private String name;     // 设备名称
        private Map<String, RegisterConfig> registers;
        // 摄像头绑定配置
        private CameraBinding cameras;


        @Data
        public static class CameraBinding {
            private CameraConfig recognition;
            private CameraConfig capture;
        }

        @Data
        public static class CameraConfig {
            private String ip;
            private int port;
            private String username;
            private String password;
            private int channel;
            private int userId ;
        }
    }

    @Data
    public static class RegisterConfig {
        private String address;  // 寄存器地址(格式:400153.0 或 400201)
        private String type;     // 数据类型(bool/float32/int16等)
    }
}

新建modbus工具类

复制代码
package com.badao.demo.utils;

import com.serotonin.modbus4j.BatchRead;
import com.serotonin.modbus4j.BatchResults;
import com.serotonin.modbus4j.ModbusFactory;
import com.serotonin.modbus4j.ModbusMaster;
import com.serotonin.modbus4j.exception.ErrorResponseException;
import com.serotonin.modbus4j.exception.ModbusInitException;
import com.serotonin.modbus4j.exception.ModbusTransportException;
import com.serotonin.modbus4j.ip.IpParameters;
import com.serotonin.modbus4j.locator.BaseLocator;

public class Modbus4jUtils {

    /**
     * 工厂。
     */
    static ModbusFactory modbusFactory;

    static {
        if (modbusFactory == null) {
            modbusFactory = new ModbusFactory();
        }
    }

    /**
     * 获取master
     *
     * @return
     * @throws ModbusInitException
     */
    public static ModbusMaster getMaster(String ip,int port) throws ModbusInitException {
        IpParameters params = new IpParameters();
        params.setHost(ip);
        params.setPort(port);
        // modbusFactory.createRtuMaster(wapper); //RTU 协议
        // modbusFactory.createUdpMaster(params);//UDP 协议
        // modbusFactory.createAsciiMaster(wrapper);//ASCII 协议
        //true启用长连接
        ModbusMaster master = modbusFactory.createTcpMaster(params, true);// TCP 协议
        master.setTimeout(2000);   // 超时设置
        master.setRetries(1);      // 失败后重试1次
        master.init();
        return master;
    }

    /**
     * 读取[01 Coil Status 0x]类型 开关数据
     *
     * @param slaveId
     *            slaveId
     * @param offset
     *            位置
     * @return 读取值
     * @throws ModbusTransportException
     *             异常
     * @throws ErrorResponseException
     *             异常
     */
    public static Boolean readCoilStatus(ModbusMaster master,int slaveId, int offset)
            throws ModbusTransportException, ErrorResponseException {
        // 01 Coil Status
        BaseLocator<Boolean> loc = BaseLocator.coilStatus(slaveId, offset);
        Boolean value = master.getValue(loc);
        return value;
    }

    /**
     * 读取[02 Input Status 1x]类型 开关数据
     *
     * @param slaveId
     * @param offset
     * @return
     * @throws ModbusTransportException
     * @throws ErrorResponseException
     */
    public static Boolean readInputStatus(ModbusMaster master,int slaveId, int offset)
            throws ModbusTransportException, ErrorResponseException {
        // 02 Input Status
        BaseLocator<Boolean> loc = BaseLocator.inputStatus(slaveId, offset);
        Boolean value = master.getValue(loc);
        return value;
    }

    /**
     * 读取[03 Holding Register类型 2x]模拟量数据
     *
     * @param slaveId
     *            slave Id
     * @param offset
     *            位置
     * @param dataType
     *            数据类型,来自com.serotonin.modbus4j.code.DataType
     * @return
     * @throws ModbusTransportException
     *             异常
     * @throws ErrorResponseException
     *             异常
     */
    public static Number readHoldingRegister(ModbusMaster master,int slaveId, int offset, int dataType)
            throws ModbusTransportException, ErrorResponseException {
        // 03 Holding Register类型数据读取
        BaseLocator<Number> loc = BaseLocator.holdingRegister(slaveId, offset, dataType);
        Number value = master.getValue(loc);
        return value;
    }

    /**
     * 读取[04 Input Registers 3x]类型 模拟量数据
     *
     * @param slaveId
     *            slaveId
     * @param offset
     *            位置
     * @param dataType
     *            数据类型,来自com.serotonin.modbus4j.code.DataType
     * @return 返回结果
     * @throws ModbusTransportException
     *             异常
     * @throws ErrorResponseException
     *             异常
     */
    public static Number readInputRegisters(ModbusMaster master,int slaveId, int offset, int dataType)
            throws ModbusTransportException, ErrorResponseException {
        // 04 Input Registers类型数据读取
        BaseLocator<Number> loc = BaseLocator.inputRegister(slaveId, offset, dataType);
        Number value = master.getValue(loc);
        return value;
    }

    /**
     * 批量读取使用方法
     *
     * @throws ModbusTransportException
     * @throws ErrorResponseException
     * @throws ModbusInitException
     * @return
     */
    public static BatchResults<Integer> batchRead(ModbusMaster master,BatchRead<Integer> batchRead) throws ModbusTransportException, ErrorResponseException, ModbusInitException {
        // 是否连续请求
        batchRead.setContiguousRequests(false);
        BatchResults<Integer> results = master.send(batchRead);
        return results;
    }
}

新建定时任务读取数据

复制代码
package com.badao.demo.task;

import com.badao.demo.config.ModbusConfig;
import com.badao.demo.constants.Constants;
import com.badao.demo.entity.PlcData;
import com.badao.demo.utils.Modbus4jUtils;
import com.badao.demo.utils.RedisCache;
import com.serotonin.modbus4j.ModbusMaster;
import com.serotonin.modbus4j.code.DataType;
import com.serotonin.modbus4j.exception.ErrorResponseException;
import com.serotonin.modbus4j.exception.ModbusInitException;
import com.serotonin.modbus4j.exception.ModbusTransportException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;

import javax.annotation.PreDestroy;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

@Configuration
@EnableScheduling
@Slf4j
@DependsOn("modbusConfig")  // 明确依赖配置类
public class GetModbusTCPDataTask {

    @Autowired
    private ModbusConfig modbusConfig;

    @Autowired
    private RedisCache redisCache;

    // 新增类成员变量(连接池)
    private final Map<String, ModbusMaster> masterPool = new ConcurrentHashMap<>();

    @Scheduled(fixedRateString = "2000")
    public void  getData() {
        modbusConfig.getDevices().parallelStream().forEach(device  -> {
            String deviceKey = device.getIp()  + ":" + device.getPort();
            ModbusMaster master = masterPool.computeIfAbsent(deviceKey,  k -> {
                try {
                    return createNewMaster(device);
                } catch (ModbusInitException e) {
                    throw new RuntimeException(e);
                }
            });
            try {
                if (!checkMasterValid(master)) {
                    masterPool.remove(deviceKey);
                    master = createNewMaster(device); // 重建连接
                }
                Number runSignal = Modbus4jUtils.readHoldingRegister(master, 1, 0, DataType.TWO_BYTE_INT_UNSIGNED);
                Number waterPress = Modbus4jUtils.readHoldingRegister(master, 1, 2, DataType.FOUR_BYTE_FLOAT);
                PlcData plcData = PlcData.builder()
                        .ip(device.getIp())
                        .runSignal(runSignal.intValue())
                        .waterPress(waterPress)
                        .build();
                redisCache.setCacheObject(Constants.PLC_KEY+device.getIp(),plcData,5, TimeUnit.SECONDS);

            } catch (ModbusTransportException e) {
                System.out.println(" 通信失败: 网络或端口不可达 - {}"+e.getMessage());
                // 发生通信错误时移除无效连接
                masterPool.remove(deviceKey);
                master.destroy();
            } catch (ErrorResponseException e) {
                //System.out.println(" 设备返回错误: 从站ID或寄存器地址无效 - {}"+e.getErrorResponse().getExceptionMessage());
                //System.out.println(e.getMessage());
            } catch (ModbusInitException e) {
                throw new RuntimeException(e);
            }
        });
    }



    private ModbusMaster createNewMaster(ModbusConfig.DeviceConfig device) throws ModbusInitException {
        try {
            return Modbus4jUtils.getMaster(device.getIp(),  device.getPort());
        } catch (ModbusInitException e) {
            throw new RuntimeException("Modbus初始化失败(ip): "+device.getIp() , e);
        }
    }
    //健康检查方法
    private boolean checkMasterValid(ModbusMaster master) {
        try {
            master.init();  // 尝试初始化验证连接状态
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    //在应用关闭时清理连接
    @PreDestroy
    public void cleanup() {
        masterPool.values().forEach(ModbusMaster::destroy);
    }
}

上面所用到的实体类

复制代码
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class PlcData {

    private String  ip;
    private Number runSignal;
    private Number waterPress;

}

所用到的redis工具类

复制代码
package com.badao.demo.utils;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * spring redis 工具类
 *
 **/
@SuppressWarnings(value = { "unchecked", "rawtypes" })
@Component
public class RedisCache
{
    @Autowired
    public RedisTemplate redisTemplate;

    /**
     * 缓存基本的对象,Integer、String、实体类等
     *
     * @param key 缓存的键值
     * @param value 缓存的值
     * @return 缓存的对象
     */
    public <T> ValueOperations<String, T> setCacheObject(String key, T value)
    {
        ValueOperations<String, T> operation = redisTemplate.opsForValue();
        operation.set(key, value);
        return operation;
    }

    /**
     * 缓存基本的对象,Integer、String、实体类等
     *
     * @param key 缓存的键值
     * @param value 缓存的值
     * @param timeout 时间
     * @param timeUnit 时间颗粒度
     * @return 缓存的对象
     */
    public <T> ValueOperations<String, T> setCacheObject(String key, T value, Integer timeout, TimeUnit timeUnit)
    {
        ValueOperations<String, T> operation = redisTemplate.opsForValue();
        operation.set(key, value, timeout, timeUnit);
        return operation;
    }

    /**
     * 获得缓存的基本对象。
     *
     * @param key 缓存键值
     * @return 缓存键值对应的数据
     */
    public <T> T getCacheObject(String key)
    {
        ValueOperations<String, T> operation = redisTemplate.opsForValue();
        return operation.get(key);
    }

    /**
     * 删除单个对象
     *
     * @param key
     */
    public void deleteObject(String key)
    {
        redisTemplate.delete(key);
    }

    /**
     * 删除集合对象
     *
     * @param collection
     */
    public void deleteObject(Collection collection)
    {
        redisTemplate.delete(collection);
    }

    /**
     * 缓存List数据
     *
     * @param key 缓存的键值
     * @param dataList 待缓存的List数据
     * @return 缓存的对象
     */
    public <T> ListOperations<String, T> setCacheList(String key, List<T> dataList)
    {
        ListOperations listOperation = redisTemplate.opsForList();
        if (null != dataList)
        {
            int size = dataList.size();
            for (int i = 0; i < size; i++)
            {
                listOperation.leftPush(key, dataList.get(i));
            }
        }
        return listOperation;
    }

    /**
     * 获得缓存的list对象
     *
     * @param key 缓存的键值
     * @return 缓存键值对应的数据
     */
    public <T> List<T> getCacheList(String key)
    {
        List<T> dataList = new ArrayList<T>();
        ListOperations<String, T> listOperation = redisTemplate.opsForList();
        Long size = listOperation.size(key);

        for (int i = 0; i < size; i++)
        {
            dataList.add(listOperation.index(key, i));
        }
        return dataList;
    }

    /**
     * 缓存Set
     *
     * @param key 缓存键值
     * @param dataSet 缓存的数据
     * @return 缓存数据的对象
     */
    public <T> BoundSetOperations<String, T> setCacheSet(String key, Set<T> dataSet)
    {
        BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
        Iterator<T> it = dataSet.iterator();
        while (it.hasNext())
        {
            setOperation.add(it.next());
        }
        return setOperation;
    }

    /**
     * 获得缓存的set
     *
     * @param key
     * @return
     */
    public <T> Set<T> getCacheSet(String key)
    {
        Set<T> dataSet = new HashSet<T>();
        BoundSetOperations<String, T> operation = redisTemplate.boundSetOps(key);
        dataSet = operation.members();
        return dataSet;
    }

    /**
     * 缓存Map
     *
     * @param key
     * @param dataMap
     * @return
     */
    public <T> HashOperations<String, String, T> setCacheMap(String key, Map<String, T> dataMap)
    {
        HashOperations hashOperations = redisTemplate.opsForHash();
        if (null != dataMap)
        {
            for (Map.Entry<String, T> entry : dataMap.entrySet())
            {
                hashOperations.put(key, entry.getKey(), entry.getValue());
            }
        }
        return hashOperations;
    }

    /**
     * 获得缓存的Map
     *
     * @param key
     * @return
     */
    public <T> Map<String, T> getCacheMap(String key)
    {
        Map<String, T> map = redisTemplate.opsForHash().entries(key);
        return map;
    }

    /**
     * 获得缓存的基本对象列表
     *
     * @param pattern 字符串前缀
     * @return 对象列表
     */
    public Collection<String> keys(String pattern)
    {
        return redisTemplate.keys(pattern);
    }
}

yml中redis相关配制

复制代码
# 数据源
spring:
  profiles:
    active: default  # 强制激活默认配置
  application:
    name: demo
  # redis 配置
  redis:
    # 地址
    #本地测试用
    host: 127.0.0.1
    port: 6379
    password: 123456
    # 连接超时时间
    timeout: 10s
    lettuce:
      pool:
        # 连接池中的最小空闲连接
        min-idle: 0
        # 连接池中的最大空闲连接
        max-idle: 8
        # 连接池的最大数据库连接数
        max-active: 8
        # #连接池最大阻塞等待时间(使用负值表示没有限制)
        max-wait: -1ms

所用的redis配制类

复制代码
package com.badao.demo.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * redis配置
 *
 */
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport
{
    @Bean
    @SuppressWarnings(value = { "unchecked", "rawtypes" })
    @Primary
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory)
    {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);

        FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);

        ObjectMapper mapper = new ObjectMapper();
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
        serializer.setObjectMapper(mapper);

        template.setValueSerializer(serializer);
        // 使用StringRedisSerializer来序列化和反序列化redis的key值
        template.setKeySerializer(new StringRedisSerializer());
        template.afterPropertiesSet();
        return template;
    }
}

Redis使用FastJson序列化类

复制代码
package com.badao.demo.config;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.TypeFactory;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import org.springframework.util.Assert;

import java.nio.charset.Charset;

public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
{
    @SuppressWarnings("unused")
    private ObjectMapper objectMapper = new ObjectMapper();

    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");

    private Class<T> clazz;

    static
    {
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
    }

    public FastJson2JsonRedisSerializer(Class<T> clazz)
    {
        super();
        this.clazz = clazz;
    }

    @Override
    public byte[] serialize(T t) throws SerializationException
    {
        if (t == null)
        {
            return new byte[0];
        }
        return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
    }

    @Override
    public T deserialize(byte[] bytes) throws SerializationException
    {
        if (bytes == null || bytes.length <= 0)
        {
            return null;
        }
        String str = new String(bytes, DEFAULT_CHARSET);

        return JSON.parseObject(str, clazz);
    }

    public void setObjectMapper(ObjectMapper objectMapper)
    {
        Assert.notNull(objectMapper, "'objectMapper' must not be null");
        this.objectMapper = objectMapper;
    }

    protected JavaType getJavaType(Class<?> clazz)
    {
        return TypeFactory.defaultInstance().constructType(clazz);
    }
}

运行效果:

相关推荐
花花鱼9 小时前
Spring Security 与 Spring MVC
java·spring·mvc
陌殇殇10 小时前
001 Spring AI Alibaba框架整合百炼大模型平台 — 快速入门
人工智能·spring boot·ai
言慢行善10 小时前
sqlserver模糊查询问题
java·数据库·sqlserver
专吃海绵宝宝菠萝屋的派大星10 小时前
使用Dify对接自己开发的mcp
java·服务器·前端
大数据新鸟11 小时前
操作系统之虚拟内存
java·服务器·网络
Tong Z11 小时前
常见的限流算法和实现原理
java·开发语言
凭君语未可11 小时前
Java 中的实现类是什么
java·开发语言
He少年11 小时前
【基础知识、Skill、Rules和MCP案例介绍】
java·前端·python
克里斯蒂亚诺更新11 小时前
myeclipse的pojie
java·ide·myeclipse