瀏覽代碼

初始导入

chenwen 1 年之前
父節點
當前提交
4820471a57
共有 37 個文件被更改,包括 1805 次插入0 次删除
  1. 39 0
      .classpath
  2. 3 0
      .gitignore
  3. 37 0
      .project
  4. 149 0
      pom.xml
  5. 16 0
      src/main/java/com/hb/proj/Application.java
  6. 13 0
      src/main/java/com/hb/proj/ServletInitializer.java
  7. 27 0
      src/main/java/com/hb/proj/allconfig/ApplicationContextProvider.java
  8. 46 0
      src/main/java/com/hb/proj/allconfig/RedisConfig.java
  9. 63 0
      src/main/java/com/hb/proj/allconfig/RequestValidateExceptionHandler.java
  10. 74 0
      src/main/java/com/hb/proj/allconfig/SpringMvcConfigurer.java
  11. 39 0
      src/main/java/com/hb/proj/allconfig/StringToDateConverter.java
  12. 65 0
      src/main/java/com/hb/proj/api/controller/APIController.java
  13. 71 0
      src/main/java/com/hb/proj/gather/protocol/ChannelGroupMgr.java
  14. 35 0
      src/main/java/com/hb/proj/gather/protocol/GatherRespParser.java
  15. 167 0
      src/main/java/com/hb/proj/gather/protocol/ZLOpdProtCMDEnum.java
  16. 121 0
      src/main/java/com/hb/proj/gather/protocol/ZLOpdProtHandler.java
  17. 24 0
      src/main/java/com/hb/proj/gather/server/MyChannelInitializer.java
  18. 30 0
      src/main/java/com/hb/proj/gather/server/NettyGatherRunner.java
  19. 65 0
      src/main/java/com/hb/proj/gather/server/NettyGatherServer.java
  20. 38 0
      src/main/java/com/hb/proj/gather/test/GatherDataDecoder.java
  21. 56 0
      src/main/java/com/hb/proj/gather/test/GatherProtocolHandler.java
  22. 124 0
      src/main/java/com/hb/proj/gather/utils/ByteUtils.java
  23. 71 0
      src/main/java/com/hb/proj/gather/utils/Crc16Utils.java
  24. 62 0
      src/main/java/com/hb/proj/utils/JacksonUtils.java
  25. 40 0
      src/main/java/com/hb/proj/utils/RequestParams.java
  26. 60 0
      src/main/java/com/hb/proj/utils/RespVO.java
  27. 52 0
      src/main/java/com/hb/proj/utils/RespVOBuilder.java
  28. 40 0
      src/main/java/logback-spring.xml
  29. 60 0
      src/main/resources/application-dev.properties
  30. 60 0
      src/main/resources/application-pro.properties
  31. 1 0
      src/main/resources/application.properties
  32. 15 0
      src/main/resources/smart-doc.json
  33. 0 0
      src/main/resources/static/i18n/messages.properties
  34. 0 0
      src/main/resources/static/i18n/messages_en_US.properties
  35. 1 0
      src/main/resources/static/i18n/messages_zh_CN.properties
  36. 34 0
      src/main/resources/transaction.xml
  37. 7 0
      src/main/webapp/WEB-INF/web.xml

+ 39 - 0
.classpath

@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" output="target/classes" path="src/main/java">
+		<attributes>
+			<attribute name="optional" value="true"/>
+			<attribute name="maven.pomderived" value="true"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
+		<attributes>
+			<attribute name="maven.pomderived" value="true"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="src" output="target/test-classes" path="src/test/java">
+		<attributes>
+			<attribute name="test" value="true"/>
+			<attribute name="optional" value="true"/>
+			<attribute name="maven.pomderived" value="true"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources">
+		<attributes>
+			<attribute name="test" value="true"/>
+			<attribute name="maven.pomderived" value="true"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17">
+		<attributes>
+			<attribute name="maven.pomderived" value="true"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
+		<attributes>
+			<attribute name="maven.pomderived" value="true"/>
+			<attribute name="org.eclipse.jst.component.dependency" value="/WEB-INF/lib"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="output" path="target/classes"/>
+</classpath>

+ 3 - 0
.gitignore

@@ -0,0 +1,3 @@
+/target/
+/.settings/
+/src/main/resources/static/doc/

+ 37 - 0
.project

@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>zl-opd-gather</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.wst.common.project.facet.core.builder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.wst.validation.validationbuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.m2e.core.maven2Builder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jem.workbench.JavaEMFNature</nature>
+		<nature>org.eclipse.wst.common.modulecore.ModuleCoreNature</nature>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+		<nature>org.eclipse.m2e.core.maven2Nature</nature>
+		<nature>org.eclipse.wst.common.project.facet.core.nature</nature>
+		<nature>org.eclipse.wst.jsdt.core.jsNature</nature>
+	</natures>
+</projectDescription>

+ 149 - 0
pom.xml

@@ -0,0 +1,149 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>com.hb</groupId>
+  <artifactId>zl-opd-gather</artifactId>
+  <version>0.0.1-SNAPSHOT</version>
+  <packaging>war</packaging>
+  
+  <properties>
+		<java.version>17</java.version>
+		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+        <spring-boot.version>3.0.0</spring-boot.version>
+	  	<lombok.version>1.16.12</lombok.version>
+	  	<netty-all.version>4.1.94.Final</netty-all.version>
+  </properties>
+  
+  <dependencies>
+	  
+	  <dependency>
+		  <groupId>cn.hutool</groupId>
+		  <artifactId>hutool-all</artifactId>
+		  <version>5.0.6</version>
+	  </dependency>
+	  <dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-web</artifactId>
+			<exclusions>
+				<exclusion>
+					<groupId>org.springframework.boot</groupId>
+					<artifactId>spring-boot-starter-tomcat</artifactId>
+				</exclusion>
+			</exclusions>
+		</dependency>
+
+		<dependency>
+			<groupId>org.springframework.boot</groupId>
+			<artifactId>spring-boot-starter-tomcat</artifactId>
+			<scope>provided</scope>
+		</dependency>
+		
+		<dependency>
+		    <groupId>mysql</groupId>
+		    <artifactId>mysql-connector-java</artifactId>
+		</dependency>
+		
+		<dependency>
+		    <groupId>org.springframework.boot</groupId>
+		    <artifactId>spring-boot-starter-validation</artifactId>
+		</dependency>
+		
+		 <dependency>
+		    <groupId>org.aspectj</groupId>
+		    <artifactId>aspectjweaver</artifactId>
+		</dependency>
+		
+		<dependency>
+		    <groupId>com.hb</groupId>
+		    <artifactId>xframework6-spring-boot3-starter</artifactId>
+		    <version>0.0.1-SNAPSHOT</version>
+		</dependency>
+		
+		<dependency>
+		    <groupId>com.github.ben-manes.caffeine</groupId>
+		    <artifactId>caffeine</artifactId>
+		</dependency>
+		
+		<dependency>
+			<groupId>org.projectlombok</groupId>
+			<artifactId>lombok</artifactId>
+		</dependency>
+		
+		<dependency>
+		    <groupId>com.belerweb</groupId>
+		    <artifactId>pinyin4j</artifactId>
+		    <version>2.5.1</version>
+		</dependency>
+		
+		<dependency>
+         	<groupId>org.springframework.boot</groupId>
+         	<artifactId>spring-boot-starter-data-redis</artifactId>
+    	</dependency>
+    	
+    	<dependency>
+            <groupId>io.netty</groupId>
+            <artifactId>netty-all</artifactId>
+            <version>${netty-all.version}</version>
+        </dependency>
+
+  </dependencies>
+	
+  <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-dependencies</artifactId>
+                <version>${spring-boot.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+        </dependencies>
+    </dependencyManagement>
+  
+  <build>
+		<plugins>
+			<!-- 明确配置编译插件可以指定一些参数,如jdk编译级别 。默认的可能编译级别较低。-->
+			<plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.8.1</version>
+                <configuration>
+                    <source>${java.version}</source>
+                    <target>${java.version}</target>
+                    <encoding>${project.build.sourceEncoding}</encoding>
+                </configuration>
+            </plugin>
+			<plugin>
+				<groupId>org.springframework.boot</groupId>
+				<artifactId>spring-boot-maven-plugin</artifactId>
+				<version>${spring-boot.version}</version>
+                <configuration>
+                    <mainClass>com.hb.proj.Application</mainClass>
+                </configuration>
+			</plugin>
+			
+			<!-- 明确配置打包插件可以指定一些参数。默认的插件版本可能级别较低,导致pom有错误提示。-->
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-war-plugin</artifactId>
+				<version>3.3.2</version>
+				<configuration>
+					<failOnMissingWebXml>false</failOnMissingWebXml>
+				</configuration>
+			</plugin>
+			
+			<!--api doc 生成插件-->
+			<plugin>
+                <groupId>com.github.shalousun</groupId>
+                <artifactId>smart-doc-maven-plugin</artifactId>
+                <version>2.4.9</version>
+                <configuration>
+                    <configFile>./src/main/resources/smart-doc.json</configFile>
+                    <projectName>智能油田生产决策平台api文档</projectName>
+                    <includes>com.hb.proj.*</includes>
+                </configuration>
+            </plugin>
+
+		</plugins>
+	</build>
+</project>

+ 16 - 0
src/main/java/com/hb/proj/Application.java

@@ -0,0 +1,16 @@
+package com.hb.proj;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.ImportResource;
+
+
+@ImportResource("classpath:transaction.xml")
+@SpringBootApplication
+public class Application {
+
+	public static void main(String[] args) {
+		SpringApplication.run(Application.class, args);
+	}
+
+}

+ 13 - 0
src/main/java/com/hb/proj/ServletInitializer.java

@@ -0,0 +1,13 @@
+package com.hb.proj;
+
+import org.springframework.boot.builder.SpringApplicationBuilder;
+import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
+
+public class ServletInitializer extends SpringBootServletInitializer {
+
+	@Override
+	protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
+		return application.sources(Application.class);
+	}
+
+}

+ 27 - 0
src/main/java/com/hb/proj/allconfig/ApplicationContextProvider.java

@@ -0,0 +1,27 @@
+package com.hb.proj.allconfig;
+
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+import org.springframework.stereotype.Component;
+
+@Component
+public class ApplicationContextProvider implements ApplicationContextAware  {
+
+	private  static ApplicationContext applicationContextObj=null;
+	@Override
+	public void setApplicationContext(ApplicationContext applicationContext)
+			throws BeansException {
+		
+		applicationContextObj=applicationContext;
+	}
+
+	public static <T> T  getBean(String beanName,Class<T> beanClass){
+		return applicationContextObj.getBean(beanName, beanClass);
+	}
+	
+	public static Object getBean(String beanName){
+		return applicationContextObj.getBean(beanName);
+	}
+
+}

+ 46 - 0
src/main/java/com/hb/proj/allconfig/RedisConfig.java

@@ -0,0 +1,46 @@
+package com.hb.proj.allconfig;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
+import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
+import org.springframework.data.redis.serializer.RedisSerializer;
+import org.springframework.data.redis.serializer.StringRedisSerializer;
+
+@Configuration
+public class RedisConfig {
+
+	@Bean
+	public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory){
+		RedisTemplate<String, String> template = new RedisTemplate<>();
+		template.setConnectionFactory(factory);
+		template.setKeySerializer(keySerializer());
+		template.setHashKeySerializer(keySerializer());
+		template.setValueSerializer(valueSerializer());
+		template.setHashValueSerializer(valueSerializer());
+		template.afterPropertiesSet();
+		return template;
+	}
+	
+	@Bean
+	public RedisConnectionFactory redisConnectionFactory() {
+	    RedisStandaloneConfiguration redisConfig = new RedisStandaloneConfiguration();
+	    redisConfig.setHostName("42.56.120.92");
+	    redisConfig.setPort(9608);
+	    redisConfig.setPassword("redis7.0");
+	    return new LettuceConnectionFactory(redisConfig);
+	}
+	
+	//使用Jackson序列化器,key使用字符串
+    private RedisSerializer<String> keySerializer() {
+        return new StringRedisSerializer();
+    }
+    
+	 //使用Jackson序列化器,value使用Object
+    private RedisSerializer<Object> valueSerializer() {
+        return new GenericJackson2JsonRedisSerializer();
+    }
+}

+ 63 - 0
src/main/java/com/hb/proj/allconfig/RequestValidateExceptionHandler.java

@@ -0,0 +1,63 @@
+package com.hb.proj.allconfig;
+
+import java.util.Set;
+
+import jakarta.validation.ConstraintViolation;
+import jakarta.validation.ConstraintViolationException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.validation.BindException;
+import org.springframework.validation.BindingResult;
+import org.springframework.validation.FieldError;
+import org.springframework.web.HttpRequestMethodNotSupportedException;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+import org.springframework.web.bind.annotation.RestControllerAdvice;
+
+import com.hb.proj.utils.RespVO;
+import com.hb.proj.utils.RespVOBuilder;
+
+@RestControllerAdvice
+public class RequestValidateExceptionHandler {
+	
+	private static final Logger logger=LoggerFactory.getLogger(RequestValidateExceptionHandler.class);
+
+	//处理请求参数格式错误 @RequestParam上validate失败后抛出的异常是javax.validation.ConstraintViolationException
+	@ExceptionHandler(ConstraintViolationException.class)
+	public RespVO<Object> constraintViolationExceptionHandler(ConstraintViolationException e) {
+		//e.printStackTrace();
+		Set<ConstraintViolation<?>> constraintViolations=e.getConstraintViolations();
+		StringBuilder error=new StringBuilder();
+		for(ConstraintViolation<?> itm : constraintViolations) {
+			//System.out.println(itm.getInvalidValue()+":"+itm.getPropertyPath()+":"+itm.getMessage());
+			error.append(itm.getMessage()+";");
+		}
+		//jdk8  写法:String message = e.getConstraintViolations().stream().map(ConstraintViolation::getMessage).collect(Collectors.joining());
+		return RespVOBuilder.error(error.toString());//
+	}
+	
+	@ExceptionHandler(BindException.class)
+	public RespVO<Object> validationExceptionHandler(BindException e) {
+		//e.printStackTrace();
+		BindingResult bindingResult = e.getBindingResult();
+		StringBuilder error=new StringBuilder();
+		for (FieldError fieldError : bindingResult.getFieldErrors()) {
+			error.append(fieldError.getDefaultMessage()+";");
+		}
+		return RespVOBuilder.error(error.toString());
+	}
+	
+	@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
+	public RespVO<Object> methodUnsupportExceptionHandler(HttpRequestMethodNotSupportedException e) {
+		logger.error("不支持该请求方式",e);
+		return RespVOBuilder.error(RespVOBuilder.API_CALL_ERROR,"不支持该请求方式");
+		
+	}
+	
+	@ExceptionHandler(Exception.class)
+	public RespVO<Object> otherExceptionHandler(Exception e) {
+		logger.error("服务出错",e);
+		return RespVOBuilder.error(RespVOBuilder.API_EXE_ERROR,"服务出错");
+		
+	}
+}

+ 74 - 0
src/main/java/com/hb/proj/allconfig/SpringMvcConfigurer.java

@@ -0,0 +1,74 @@
+package com.hb.proj.allconfig;
+
+import java.util.Arrays;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.web.servlet.FilterRegistrationBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.Ordered;
+import org.springframework.format.FormatterRegistry;
+import org.springframework.web.cors.CorsConfiguration;
+import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
+import org.springframework.web.filter.CorsFilter;
+import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+@Configuration
+public class SpringMvcConfigurer implements WebMvcConfigurer {
+
+	@Value("${api.filter.exclude}") 
+	private String excludePath;
+	
+	private static final long MAX_AGE=24*60*60;  //跨域预请求最大有效期(有效期内【只对同一请求?】不再预请求)
+	
+	/**
+	 * 静态资源的处理
+	 */
+	@Override
+	public void addResourceHandlers(ResourceHandlerRegistry registry) {
+		registry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");
+		WebMvcConfigurer.super.addResourceHandlers(registry);
+	}
+	
+	
+	
+
+	/**
+	 * 接口传参增加数据转换器(时间字符转为时间对象)
+	 */
+	@Override
+	public void addFormatters(FormatterRegistry registry) {
+		registry.addConverter(new StringToDateConverter());
+		WebMvcConfigurer.super.addFormatters(registry);
+	}
+	
+	/**
+	 * 跨域配置
+	 * @return
+	 */
+    @Bean
+    public FilterRegistrationBean<CorsFilter> corsFilter() {
+        // 跨域配置
+        CorsConfiguration configuration = new CorsConfiguration();
+        configuration.setAllowedOrigins(Arrays.asList("*"));
+        configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "HEAD", "DELETE", "OPTIONS"));
+        configuration.setAllowedHeaders(Arrays.asList("*"));
+        configuration.setMaxAge(MAX_AGE);
+        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
+        source.registerCorsConfiguration("/**", configuration);
+
+        // 有多个filter时此处可设置改CorsFilter的优先执行顺序,保证CorsFilter在其他过滤器之前执行(避免其他过滤器执行异常,导致CorsFilter没执行,从而导致跨域失效)
+        FilterRegistrationBean<CorsFilter> bean = new FilterRegistrationBean<>(new CorsFilter(source));
+        bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
+        
+        return bean;
+    }
+    
+    
+  
+    
+	
+
+
+}

+ 39 - 0
src/main/java/com/hb/proj/allconfig/StringToDateConverter.java

@@ -0,0 +1,39 @@
+package com.hb.proj.allconfig;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.util.StringUtils;
+
+public class StringToDateConverter implements Converter<String, Date> {
+
+	private static final String[] dateFormats= {"yyyy-MM-dd HH:mm:ss","yyyy-MM-dd","yyyy/MM/dd HH:mm:ss","yyyy/MM/dd"};
+	
+	@Override
+	public Date convert(String source) {
+		if (!StringUtils.hasLength(source)) {
+	            return null;
+	    }
+		source = source.trim();
+		 try {
+	            SimpleDateFormat formatter;
+	            
+	            if (source.contains("-")) {
+	            	 formatter = new SimpleDateFormat(source.contains(":")?dateFormats[0]:dateFormats[1]);
+	                 return formatter.parse(source);
+	            } 
+	            else if (source.contains("/")) {
+	            	formatter = new SimpleDateFormat(source.contains(":")?dateFormats[2]:dateFormats[3]);
+	                return formatter.parse(source);
+	            }
+	            
+	            throw new RuntimeException(String.format("parser %s to Date fail,unknow format", source));
+	            
+	        } catch (Exception e) {
+	            throw new RuntimeException(String.format("parser %s to Date fail", source));
+	        }
+		
+	}
+
+}

+ 65 - 0
src/main/java/com/hb/proj/api/controller/APIController.java

@@ -0,0 +1,65 @@
+package com.hb.proj.api.controller;
+
+import java.time.LocalDateTime;
+
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import com.hb.proj.gather.protocol.ChannelGroupMgr;
+import com.hb.proj.gather.protocol.ZLOpdProtCMDEnum;
+import com.hb.proj.gather.utils.ByteUtils;
+import com.hb.proj.utils.RespVO;
+import com.hb.proj.utils.RespVOBuilder;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
+import io.netty.channel.Channel;
+import jakarta.validation.constraints.NotBlank;
+
+@RestController
+@RequestMapping("/api")
+@Validated
+public class APIController {
+
+	@RequestMapping("/sendCommond")
+	public RespVO<Object> sendCommond(@NotBlank(message="指令不能为空") String cmd){
+		Channel channel=ChannelGroupMgr.get("50188");
+		
+		if(channel==null) {
+			return RespVOBuilder.error("未找到客户端");
+		}
+		
+		/**
+		System.out.println("找到客户端"+channel.attr(ChannelGroupMgr.ATTR_KEY_SERIAL).get());
+		
+		//byte[] cmd={0x01, 0x03, 0x01, 0x2c, 0x00, 0x0a} ; 
+		
+		byte[] cmd={0x01, 0x03, 0x01, (byte)0xa4, 0x00, 0x04} ; 
+		
+		int crcInt=Crc16Utils.getCRC(cmd);
+		byte[] crcBytes=ByteUtils.int2Bytes(crcInt, 2);
+		
+		byte[] fullCmd=new byte[cmd.length+crcBytes.length];
+		System.arraycopy(cmd,0,fullCmd,0,cmd.length);
+		System.arraycopy(crcBytes,0,fullCmd,cmd.length,crcBytes.length);
+		**/
+		
+		byte[] cmdBytes=ZLOpdProtCMDEnum.valueOf(cmd).getCmd();
+		
+		
+		
+		ByteBufAllocator alloc=channel.alloc();  //类型为:PooledByteBufAllocator  netty默认内存分配器
+		
+		//System.out.println("相应消息分配器:"+alloc.getClass().toString());
+		
+		ByteBuf byteBuf=alloc.heapBuffer().writeBytes(cmdBytes);
+		channel.writeAndFlush(byteBuf);
+		
+		//分配的byteBuf 在发送完消息后由netty进行释放
+		
+		System.out.println("指令发送时间:"+LocalDateTime.now());
+		
+		return RespVOBuilder.ok(ByteUtils.toHexString(cmdBytes));
+	}
+}

+ 71 - 0
src/main/java/com/hb/proj/gather/protocol/ChannelGroupMgr.java

@@ -0,0 +1,71 @@
+package com.hb.proj.gather.protocol;
+
+import java.util.Iterator;
+
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import io.netty.channel.Channel;
+import io.netty.channel.group.ChannelGroup;
+import io.netty.channel.group.ChannelGroupFuture;
+import io.netty.channel.group.DefaultChannelGroup;
+import io.netty.util.AttributeKey;
+import io.netty.util.concurrent.GlobalEventExecutor;
+
+/**
+ * 对所有的channel统一管理
+ * @author cwen
+ *
+ */
+public class ChannelGroupMgr {
+	
+	private final static  Logger logger = LoggerFactory.getLogger(ChannelGroupMgr.class);
+
+	private static final ChannelGroup CHANNEL_GROUP = new DefaultChannelGroup("ChannelGroups", GlobalEventExecutor.INSTANCE);
+	
+	public  static final AttributeKey<String> ATTR_KEY_SERIAL=AttributeKey.valueOf("serial");  //通道自定义属性key,用于保存属性数据,便于后面通过该属性查找对应channel
+	
+	public static void add(Channel channel,String serial) {
+		logger.info("增加客户端通道:{}",serial);
+		channel.attr(ATTR_KEY_SERIAL).set(serial);
+        CHANNEL_GROUP.add(channel);
+    }
+	
+	public static void add(Channel channel) {
+        CHANNEL_GROUP.add(channel);
+    }
+	
+	public static boolean remove(Channel channel) {
+	    return CHANNEL_GROUP.remove(channel);
+	}
+	     
+	public static ChannelGroupFuture disconnect() {
+	    return CHANNEL_GROUP.disconnect();
+	}
+	
+	public static boolean contains(Channel channel) {
+		
+        return CHANNEL_GROUP.contains(channel);
+    }
+	
+	public static int size() {
+		return CHANNEL_GROUP.size();
+	}
+	
+	public static  Channel  get(String serial) {
+		if(StringUtils.isBlank(serial)) {
+			return null;
+		}
+		Iterator<Channel> iterator=CHANNEL_GROUP.iterator();
+		Channel channel=null;
+		while(iterator.hasNext()) {
+			channel=iterator.next();
+			if(serial.equals(channel.attr(ATTR_KEY_SERIAL).get())) {
+				return channel;
+			}
+		}
+		return null;
+	}
+	
+}

+ 35 - 0
src/main/java/com/hb/proj/gather/protocol/GatherRespParser.java

@@ -0,0 +1,35 @@
+package com.hb.proj.gather.protocol;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import io.netty.buffer.ByteBuf;
+
+public class GatherRespParser {
+
+	private final static  Logger logger = LoggerFactory.getLogger(GatherRespParser.class);
+			
+	/**
+	 * 解析消息中的数据部分
+	 * @param byteBuf
+	 * @param  startIndex 数据区开始索引
+	 * @param  dataLen  数据区长度
+	 */
+	public static List<Float> parseFloat(ByteBuf byteBuf ,int startIndex,int dataLen) {
+		byteBuf.readerIndex(startIndex);
+		List<Float> rtns=new ArrayList<Float>();
+		while(true) {
+			rtns.add(byteBuf.readFloat() ); //顺序读取,readIndex 自动后移
+			if(byteBuf.readerIndex()>=(startIndex+dataLen)) {
+				break;
+			}
+		}
+		logger.info("数据解析完:{}",rtns);
+		return rtns;
+		
+		
+	}
+}

+ 167 - 0
src/main/java/com/hb/proj/gather/protocol/ZLOpdProtCMDEnum.java

@@ -0,0 +1,167 @@
+package com.hb.proj.gather.protocol;
+
+public enum ZLOpdProtCMDEnum {
+	
+	//20[应返回数据区字节数] 一次读取到油压、套压、回压、井口温度、载荷 01 03 01 2c 00 0a    校验位两字节         应返回字节数     每个数据项字节数  参数编码表
+	PRESS_TEMP_LOAD(
+			new byte[] {0x01, 0x03, 0x01, 0x2c, 0x00, 0x0a,       0x05, (byte)0xf8},   
+			0x14,  
+			4, 
+			new String[]{"oil_press","casing_press","back_press","well_head_temp","load"}),
+	
+	
+	
+	//48 电机电流A、B、C,电压A、B、C,有功功耗,无功功耗,有功功率,无功功率,反向功率,功率因数 01 03 01 5f 00 18
+	CURR_VOL_LOS_PW(
+			new byte[] {0x01, 0x03, 0x01, 0x5f, 0x00, 0x18,    0x74, (byte)0x2e},  
+			0x30,  
+			4,
+			new String[]{"current_a","current_b","current_c","voltage_a","voltage_b","voltage_c","useful_power_loss","unuseful_power_loss","useful_power","unuseful_power","reverse_power","power_factor"}),
+	
+	//8  冲次、冲程 01 03 01 a4 00 04
+	FREQ_STROKE(
+			new byte[] {0x01, 0x03, 0x01, (byte)0xa4, 0x00, 0x04,        0x04, 0x16},  
+			0x08,  
+			4,
+			new String[]{"freq","stroke"}),
+	
+	//2 功图实际点数 01 03 03 d7 00 01
+	DIAGRAM_POINT_COUNT(
+			new byte[] {0x01, 0x03, 0x03, (byte)0xd7, 0x00, 0x01,        0x34, 0x76},  
+			0x02,  
+			2,
+			new String[]{"diagram_point"}),
+	
+	
+	
+	
+	//200-100 功图位移第1部分 01 03 03 e8 00 64
+	DIAGRAM_DISP_1(
+			new byte[] {0x01, 0x03, 0x03, (byte)0xe8, 0x00, 0x64,        (byte)0xc4, 0x51},  
+			0xc8,  
+			2,
+			new String[]{"disp_1"}),
+	
+	//200-100 功图位移第2部分 01 03 04 4c 00 64
+	DIAGRAM_DISP_2(
+			new byte[] {0x01, 0x03, 0x04, 0x4c, 0x00, 0x64,        (byte)0x84, (byte)0xc6},  
+			0xc8,  
+			2,
+			new String[]{"disp_2"}),
+	
+	//100-50 功图位移第3部分 01 03 04 b0 00 32
+	DIAGRAM_DISP_3(
+			new byte[] {0x01, 0x03, 0x04, (byte)0xb0, 0x00, 0x32,        (byte)0xc4, (byte)0xc8},  
+			0x64,  
+			2,
+			new String[]{"disp_3"}),
+	
+	
+	
+	
+	//200-100 功图载荷第1部分 01 03 04 e2 00 64
+	DIAGRAM_LOAD_1(
+			new byte[] {0x01, 0x03, 0x04, (byte)0xe2, 0x00, 0x64,        (byte)0xe5, 0x27},  
+			0xc8,  
+			2,
+			new String[]{"chartload_1"}),
+		
+	//200-100 功图载荷第2部分 01 03 05 46 00 64
+	DIAGRAM_LOAD_2(
+			new byte[] {0x01, 0x03, 0x05, 0x46, 0x00, 0x64,        (byte)0xa5, 0x38},  
+			0xc8,  
+			2,
+			new String[]{"chartload_2"}),
+		
+	//100-50 功图载荷第3部分 01 03 05 aa 00 32
+	DIAGRAM_LOAD_3(
+			new byte[] {0x01, 0x03, 0x05, (byte)0xaa, 0x00, 0x32,        (byte)0xe4, (byte)0xf3},  
+			0x64,  
+			2,
+			new String[]{"chartload_3"}),
+	
+	
+	
+	
+	
+	
+	//200-100 电流图电流第1部分 01 03 05 dc 00 64
+	DIAGRAM_CURR_1(
+			new byte[] {0x01, 0x03, 0x05, (byte)0xdc, 0x00, 0x64,        (byte)0x85, 0x17},  
+			0xc8,  
+			2,
+			new String[]{"chartcurr_1"}),
+			
+	//200-100 电流图电流第2部分 01 03 06 40 00 64
+	DIAGRAM_CURR_2(
+			new byte[] {0x01, 0x03, 0x06, 0x40, 0x00, 0x64,        0x45, 0x7d},  
+			0xc8,  
+			2,
+			new String[]{"chartcurr_2"}),
+			
+	//100-50 电流图电流第3部分 01 03 06 a4 00 32 
+	DIAGRAM_CURR_3(
+			new byte[] {0x01, 0x03, 0x06, (byte)0xa4, 0x00, 0x32,        (byte)0x85, (byte)0x74},  
+			0x64,  
+			2,
+			new String[]{"chartcurr_3"}),
+	
+	
+	
+	
+	
+	//200-100 功率图功率第1部分 01 03 06 d6 00 64
+	DIAGRAM_POWER_1(
+			new byte[] {0x01, 0x03, 0x06, (byte)0xd6, 0x00, 0x64,        (byte)0xa5, 0x51},  
+			0xc8,  
+			2,
+			new String[]{"chartpower_1"}),
+				
+	//200-100 功率图功率第2部分 01 03 07 3a 00 64
+	DIAGRAM_POWER_2(
+			new byte[] {0x01, 0x03, 0x07, 0x3a, 0x00, 0x64,        0x65, 0x58},  
+			0xc8,  
+			2,
+			new String[]{"chartpower_2"}),
+				
+	//100-50 功率图功率第3部分 01 03 07 e9 00 32
+	DIAGRAM_POWER_3(
+			new byte[] {0x01, 0x03, 0x07, (byte)0xe9, 0x00, 0x32,       0x14, (byte)0x9f},  
+			0x64,  
+			2,
+			new String[]{"chartpower_3"})
+	;
+	
+	private byte[]  cmd;  //读取指令 最后字节CRC16
+	
+	private int totalBytCount; //返回的字节数(以此区分不同指令的返回数据)
+	
+	private int itemBytCount; //每个数据项字节数  目前为4字节或2字节一个数据
+	
+	private String[]  paramCodes; //返回数据项对应参数编码
+	
+	private ZLOpdProtCMDEnum(byte[]  cmd,int totalBytCount,int itemBytCount,String[] paramCodes) {
+		this.cmd=cmd;
+		this.totalBytCount=totalBytCount;
+		this.itemBytCount=itemBytCount;
+		this.paramCodes=paramCodes;
+	}
+
+	public byte[] getCmd() {
+		return cmd;
+	}
+
+	public int getTotalBytCount() {
+		return totalBytCount;
+	}
+
+	public int getItemBytCount() {
+		return itemBytCount;
+	}
+
+	public String[] getParamCodes() {
+		return paramCodes;
+	}
+
+	
+}

+ 121 - 0
src/main/java/com/hb/proj/gather/protocol/ZLOpdProtHandler.java

@@ -0,0 +1,121 @@
+package com.hb.proj.gather.protocol;
+
+import java.util.List;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.hb.proj.gather.utils.ByteUtils;
+import com.hb.proj.gather.utils.Crc16Utils;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufUtil;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+
+/**
+ * 按协议解析数据    zl-opd mudbus 参考 A11标准
+ * @author cwen
+ *
+ */
+public class ZLOpdProtHandler extends ChannelInboundHandlerAdapter {
+
+	private final static  Logger logger = LoggerFactory.getLogger(ZLOpdProtHandler.class);
+	
+	
+	//整个处理链路中只执行一次,顺序靠前的执行
+	@Override
+	public void channelActive(ChannelHandlerContext ctx) throws Exception {
+		logger.info("有设备连接上:{}",ctx.channel().remoteAddress());
+	}
+
+	
+	/**
+	 * 协议标准:0103[数据区字节数 1字节][数据区 若干字节][CRC16校验 2字节]
+	 * 01:dtu上位地址  03:表示读取  
+	 * 
+	 * 心跳2字节,间隔约30s
+	 */
+	@Override
+	public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
+		//msg:如果设置了decoder编码器,则msg为编码后的类型,可强制转换
+		ByteBuf byteBuf=(ByteBuf)msg;
+		
+		if(!byteBuf.isReadable()) {
+			logger.info("没有数据可接收");
+			return;
+		}
+		
+		String hexmsg=ByteBufUtil.hexDump(byteBuf);
+		
+		logger.debug("接收到数据:{}",hexmsg);
+		
+		//byte[] temp=ByteBufUtil.getBytes(byteBuf, 0, 2);
+		
+		//两字节都没有既不是心跳也不是采集数据,忽略
+		int byteCount=byteBuf.readableBytes();
+		if(byteCount<2) {
+			return ;
+		}
+		
+		//byte[] allBytes=new byte[byteBuf.readableBytes()];
+		//byteBuf.readBytes(allBytes);
+		
+		
+		//开头两字节且不以0103开头,就认为是心跳数据-作为设备号,该方法并不可靠,有可能把采集的残包数据当作心跳
+		if(byteCount==2&&(!hexmsg.startsWith("0103"))) { 
+			if(!ChannelGroupMgr.contains(ctx.channel())) {
+				ChannelGroupMgr.add(ctx.channel(),ByteUtils.toIntStr(ByteBufUtil.getBytes(byteBuf,0,byteCount)));
+				return;
+			}
+			
+		}
+		else if(byteCount>2&&hexmsg.startsWith("0103")){ 
+			
+			int headBtyCount=3,crc16BtyCount=2; //头部字节数,校验位字节数
+			
+			int datalen=byteBuf.getByte(2)&0xff; //数据区字节数   byteBuf.get方法不改变readIndex,writeIndex,readXX方法会
+			logger.info("数据字节长度:{}",datalen);
+			
+			if(byteCount<(datalen+headBtyCount+crc16BtyCount)) {  // 读取的字节数量不够---拆包了,目前处理:舍弃
+				return;
+			}
+			
+			//读取头部+数据区
+			/*
+			byte[] headAndDatas=new byte[datalen+headBtyCount];
+			byteBuf.readerIndex(0);  //重置读索引至起始
+			byteBuf.readBytes(headAndDatas);
+			*/
+			byte[] headAndDatas=ByteBufUtil.getBytes(byteBuf,0,headBtyCount+datalen);
+			
+			//读取校验位
+			/*
+			byte[] crc16=new byte[crc16BtyCount];
+			byteBuf.readerIndex(datalen+headBtyCount); 
+			byteBuf.readBytes(crc16);*/
+			
+			byte[] crc16=ByteBufUtil.getBytes(byteBuf,headBtyCount+datalen,2);
+			
+			int calCrc16=Crc16Utils.getCRC(headAndDatas);
+			
+			logger.info("接收CRC:{}:{},计算CRC:{}",ByteUtils.toHexString(crc16),ByteUtils.byte2ToIntHL(crc16),calCrc16);
+			
+			if(ByteUtils.byte2ToIntHL(crc16)==calCrc16) {  //crc校验通过
+				
+				List<Float> smpdatas=GatherRespParser.parseFloat(byteBuf,headBtyCount,datalen);
+				
+			}
+		}
+		
+		/**
+		 * 继续流转到下个handler 直到最后默认的tail handler 由它来释放
+		 * 关键点:数据能流转到最后、数据类型为byteBuf 中途没有被改变
+		 * 
+		 * 也可创建继承SimpleChanneInboundHandler的自定义handler,实现channelRead0方法。
+		 * SimpleChannelInboundHandler负责释放
+		 */
+		ctx.fireChannelRead(msg); 
+	}
+
+}

+ 24 - 0
src/main/java/com/hb/proj/gather/server/MyChannelInitializer.java

@@ -0,0 +1,24 @@
+package com.hb.proj.gather.server;
+
+import com.hb.proj.gather.protocol.ZLOpdProtHandler;
+
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.socket.SocketChannel;
+
+public class MyChannelInitializer extends ChannelInitializer<SocketChannel> {
+
+	/*
+	 * channel是流式API,可以添加ChannelHandler
+	 * Pipeline被创建的时候,会默认创建两个handler,head和tail
+	 */
+	
+	@Override
+	protected void initChannel(SocketChannel channel) throws Exception {
+		channel.pipeline()
+				.addLast(new ZLOpdProtHandler());
+				//发送的数据时行文字编码
+                //.addLast("encoder", new StringEncoder(StandardCharsets.UTF_8))
+			    //.addLast(new GatherProtocolHandler());
+	}
+
+}

+ 30 - 0
src/main/java/com/hb/proj/gather/server/NettyGatherRunner.java

@@ -0,0 +1,30 @@
+package com.hb.proj.gather.server;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.ApplicationArguments;
+import org.springframework.boot.ApplicationRunner;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Component;
+
+@Component
+@Order(1)
+public class NettyGatherRunner implements ApplicationRunner {
+
+	private final static Logger logger = LoggerFactory.getLogger(NettyGatherRunner.class);
+	
+	@Autowired
+	private NettyGatherServer  nettyGatherServer;
+	
+	@Override
+	public void run(ApplicationArguments args) throws Exception {
+		
+		new Thread(()-> {
+			nettyGatherServer.start(9610);
+		}).start();
+		
+		logger.info("开始启动采集程序");
+	}
+
+}

+ 65 - 0
src/main/java/com/hb/proj/gather/server/NettyGatherServer.java

@@ -0,0 +1,65 @@
+package com.hb.proj.gather.server;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import jakarta.annotation.PreDestroy;
+
+@Component
+public class NettyGatherServer {
+
+	private final static  Logger logger = LoggerFactory.getLogger(NettyGatherServer.class);
+	
+	private Channel  serverChannel;
+	
+	public void start(int port) {
+		
+		 EventLoopGroup bossGroup  = new NioEventLoopGroup(1); //nio连接处理池 默认线程数和可用cpu相关
+	     EventLoopGroup workerGroup = new NioEventLoopGroup(); //IO处理池
+	     
+	     try {
+	    	 ServerBootstrap bootstrap = new ServerBootstrap();
+		     
+		     bootstrap.group(bossGroup, workerGroup)
+		     		  .channel(NioServerSocketChannel.class)
+		     		  .option(ChannelOption.SO_BACKLOG, 128)  //ServerSocket的配置参数(可选):最大128个连接的排队
+		     		  .childHandler(new MyChannelInitializer());
+		     
+		     ChannelFuture  future=bootstrap.bind(port).sync();
+		     if(future.isDone()) {
+		    	 logger.info("采集服务已经在{}端口启动...",port);
+		     }
+		     serverChannel=future.channel();
+		     
+		     future.channel().closeFuture().sync();  //开启子线程监听channel是否关闭了,并阻塞当前线程,避免直接执行到finally块,关闭服务了。也可以用Thread.join 达到同样目的
+		     logger.info("采集服务通道已关闭,继续执行...");
+	     }
+	     catch (Exception e) {
+	        e.printStackTrace();
+	        logger.error(e.getMessage());
+	     }
+	     finally {
+	    	 logger.info("采集服务停止前,关闭处理池...");
+	    	 bossGroup.shutdownGracefully();  //防止线程泄漏
+	    	 workerGroup.shutdownGracefully();
+	    	 logger.info("采集服务即将停止");
+	     }
+	     		  
+	}
+	
+	@PreDestroy
+    public void stop(){
+        if (serverChannel!=null && serverChannel.isOpen()){
+        	logger.info("即将关闭服务 channel.close");
+            serverChannel.close();
+        }
+    }
+}

+ 38 - 0
src/main/java/com/hb/proj/gather/test/GatherDataDecoder.java

@@ -0,0 +1,38 @@
+package com.hb.proj.gather.test;
+
+import java.util.List;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.hb.proj.gather.utils.ByteUtils;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.ByteToMessageDecoder;
+
+/**
+ * 对接收的远程消息先进行解码  ByteBuf->Byte[]
+ * @author cwen
+ *
+ */
+public class GatherDataDecoder extends ByteToMessageDecoder {
+	
+	private final static  Logger logger = LoggerFactory.getLogger(GatherDataDecoder.class);
+
+	@Override
+	protected void decode(ChannelHandlerContext ctx, ByteBuf byteBuf, List<Object> out) throws Exception {
+		byte[] bytes=new byte[byteBuf.readableBytes()];
+		byteBuf.readBytes(bytes);
+		logger.debug("接收到数据:{}",ByteUtils.toHexString(bytes));
+		out.add(bytes);
+
+	}
+
+	//整个处理链路中只执行一次,顺序靠前的执行
+	@Override
+	public void channelActive(ChannelHandlerContext ctx) throws Exception {
+		logger.info("有设备连接上:{}",ctx.channel().remoteAddress());
+	}
+
+}

+ 56 - 0
src/main/java/com/hb/proj/gather/test/GatherProtocolHandler.java

@@ -0,0 +1,56 @@
+package com.hb.proj.gather.test;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.hb.proj.gather.protocol.ChannelGroupMgr;
+import com.hb.proj.gather.protocol.GatherRespParser;
+import com.hb.proj.gather.utils.ByteUtils;
+import com.hb.proj.gather.utils.Crc16Utils;
+
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+
+public class GatherProtocolHandler extends ChannelInboundHandlerAdapter {
+
+	private final static  Logger logger = LoggerFactory.getLogger(GatherProtocolHandler.class);
+	
+
+	@Override
+	public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
+		//msg:如果设置了decoder编码器,则msg为编码后的类型,可强制转换
+		byte[] recmsg=(byte[])msg;
+		String hexmsg=ByteUtils.toHexString(recmsg);
+		
+		//两字节且不以0103开头,就认为是心跳数据-作为设备号,该方法并不可靠,有可能把采集的残包数据当作心跳
+		if(recmsg.length==2&&(!hexmsg.startsWith("0103"))) { 
+			if(!ChannelGroupMgr.contains(ctx.channel())) {
+				ChannelGroupMgr.add(ctx.channel(),ByteUtils.toIntStr(recmsg));
+			}
+			
+		}
+		else if(hexmsg.startsWith("0103")){  //采集指令返回数据
+			int datalen=recmsg[2]&0xff; //数据区字节数
+			logger.info("数据字节长度:{}",datalen);
+			byte[] hdBodyMsg=Arrays.copyOfRange(recmsg,0,datalen+3);   //除CRC外的消息主体
+			byte[] crc16=Arrays.copyOfRange(recmsg,datalen+3,datalen+3+2);  //2字节校验位
+			//System.arraycopy(recmsg,0,hdBodyMsg,0,hdBodyMsg.length); 
+			//System.arraycopy(recmsg,hdBodyMsg.length,crc16,0,crc16.length);
+			
+			int calCrc16=Crc16Utils.getCRC(hdBodyMsg);
+			
+			logger.info("接收CRC:{}:{},计算CRC:{}",ByteUtils.toHexString(crc16),ByteUtils.byte2ToIntHL(crc16),calCrc16);
+			
+			if(ByteUtils.byte2ToIntHL(crc16)==calCrc16) {  //crc校验通过
+				//List<Integer> smpdatas=GatherRespParser.parseFloat(Arrays.copyOfRange(hdBodyMsg,3,datalen+3), 4);
+				//logger.info("解析后的采集数据{}",smpdatas);
+			}
+		}
+		
+		ctx.fireChannelRead(msg);
+	}
+
+}

+ 124 - 0
src/main/java/com/hb/proj/gather/utils/ByteUtils.java

@@ -0,0 +1,124 @@
+package com.hb.proj.gather.utils;
+
+import java.math.BigInteger;
+import java.nio.ByteBuffer;
+
+public class ByteUtils {
+
+	public static String toHexString(byte[] data) {
+
+		 StringBuilder sb = new StringBuilder();
+
+		 for (byte b : data) {
+
+			 sb.append(String.format("%02x", b));
+
+		 }
+		 return sb.toString();
+
+	}
+	
+	//高位在前
+	public static String toIntStr(byte[] bytes){  
+        return new BigInteger(1, bytes).toString(10);// 这里的1代表正数  
+    } 
+	
+	public static String binary(byte[] bytes, int radix){  
+        return new BigInteger(1, bytes).toString(radix);// 这里的1代表正数  
+    }
+	
+    /** 
+    * 多字节转化为 10进制 高位在前低位在后
+    * 理论上几次操作后需要与 int 的最大最小值比较大小,不能超出范围:Integer.MAX_VALUE、Integer.MIN_VALUE  
+    * @Param: data
+    */ 
+    public static int toIntHL(byte[] data){
+        int deci = 0;
+        for(int i = 0; i < data.length; i++){
+            deci = (deci<< 8) | (data[i] & 0xff) ;
+        }
+        return deci;
+    }
+    
+    
+    public static int toIntLH4(byte[] data){
+    	return (data[3] & 0xff) << 24 | (data[2] & 0xff)<<16 | (data[1] & 0xff)<<8 | (data[0] & 0xff);
+    }
+    
+    //低位在前高位在后
+    public static int toIntLH(byte[] data){
+        int deci = 0;
+        for(int i = data.length-1; i >= 0; i--){
+            deci = (deci << 8) | (data[i] & 0xff) ;
+        }
+        return deci;
+    }
+    
+    //低位在前高位在后
+    public static int byte2ToIntLH(byte[] data){
+    	return (data[0] & 0xff)|(data[1] & 0xff)<<8;
+    }
+    
+    //高位在前低位在后
+    public static int byte2ToIntHL(byte[] data){
+    	return (data[0] & 0xff) <<8 |(data[1] & 0xff);
+    }
+    
+    /**
+     * int整型转换成字节数组byte[],注意该算法是从高位(左端)往低位(右端)取二进制,
+	 * len为字节个数,int整型占4个字节为32位。
+     * @param n
+     * @param len
+     * @return
+     * @throws IllegalArgumentException
+     */
+    public static byte[] int2Bytes(int n, int len) throws IllegalArgumentException
+	  {
+	    if (len <= 0) {
+	      throw new IllegalArgumentException("Illegal of length");
+	    }
+	    byte[] b = new byte[len];
+	    for (int i = len; i > 0; i--) {
+	      b[(i - 1)] = ((byte)(n >> 8 * (len - i) & 0xFF));
+	    }
+	    return b;
+	 }
+
+    /**
+     * 开辟直接缓冲区,对比堆字节缓冲区 少1次内存复制(系统内存-jvm内存)
+     * 对java来说直接缓冲区创建、销毁更耗性能
+     * @param bytes
+     * @return
+     */
+    public static float readFloat(byte[] bytes) {
+    	ByteBuffer buf=ByteBuffer.allocateDirect(bytes.length); 
+    	buf.put(bytes);
+    	buf.rewind();
+    	return buf.getFloat();
+    }
+    
+    public static void main(String[] args) {
+    	byte[] bytes= {0x40, (byte)0x9B, (byte)0x85, 0x1F};
+    	
+    	byte[] bytes2= {0x40,  0x3B,  0x00,  0x00};
+    	
+    	byte[] bytes3= {0x40, (byte)0xa9, (byte)0xa3, (byte)0xd7};
+    	
+    	byte[] bytes4= {0x40,  0x28,  0x00,  0x00};
+    	
+    	byte[] bytes5= {0x40, (byte)0xA9, (byte)0xE5, 0x60};
+    	//System.out.println(ByteUtils.toIntStr(bytes));
+    	//System.out.println(ByteUtils.toIntHL(bytes));
+    	//System.out.println(ByteUtils.toIntLH(bytes));
+    	//System.out.println(ByteUtils.toIntLH(bytes2));
+    	//System.out.println(ByteUtils.toIntLH(bytes3));
+    	//System.out.println(ByteUtils.toIntLH(bytes4));
+    	
+    	//System.out.println(ByteUtils.toIntLH(bytes2));
+    	System.out.println(ByteUtils.toIntLH4(bytes5));
+    	System.out.println(ByteUtils.toIntLH(bytes5));
+    	System.out.println(ByteUtils.toIntHL(bytes5));
+    	//System.out.println(ByteUtils.toHexString(bytes2));
+    }
+
+}

+ 71 - 0
src/main/java/com/hb/proj/gather/utils/Crc16Utils.java

@@ -0,0 +1,71 @@
+package com.hb.proj.gather.utils;
+
+public class Crc16Utils {
+
+	/**
+     * 一个字节包含位的数量 8
+     */
+    private static final int BITS_OF_BYTE = 8;
+
+    /**
+     * 多项式
+     */
+    private static final int POLYNOMIAL = 0xA001;
+
+    /**
+     * 初始值
+     */
+    private static final int INITIAL_VALUE = 0xFFFF;
+
+    /**
+     * CRC16 编码
+     * @param bytes 编码内容
+     * @return 编码结果
+     */
+    public static String crc16(int[] bytes) {
+        int res = INITIAL_VALUE;
+        for (int data : bytes) {
+            res = res ^ data;
+            for (int i = 0; i < BITS_OF_BYTE; i++) {
+                res = (res & 0x0001) == 1 ? (res >> 1) ^ POLYNOMIAL : res >> 1;
+            }
+        }
+        return Integer.toHexString(revert(res));
+    }
+
+    /**
+     * 翻转16位的高八位和低八位字节  低位在前  LH
+     * @param src 翻转数字
+     * @return 翻转结果
+     */
+    private static int revert(int src) {
+        int lowByte = (src & 0xFF00) >> 8;
+        int highByte = (src & 0x00FF) << 8;
+        return lowByte | highByte;
+    }
+
+   
+    
+    
+    public static int getCRC(byte[] bytes) {
+    	int CRC = 0x0000ffff;
+        int POLYNOMIAL = 0x0000a001;
+        
+        for(byte b : bytes) {
+        	CRC = CRC ^ (b & 0x000000ff) ;
+        	for(int j = 0; j < 8; j++) {
+        		CRC = (CRC & 0x0001) == 1 ? (CRC >> 1) ^ POLYNOMIAL : CRC >> 1;
+        	}
+        }
+        return revert(CRC);
+    }
+
+
+    public static void main(String[] args) {
+       // int[] data = new int[]{0x01, 0x03, 0x03, 0xd7, 0x00, 0x01};
+       // System.out.println(Crc16Utils.crc16(data));
+        
+        byte[] data2 = {0x01, 0x03, 0x03, (byte)0xd7, 0x00, 0x01};
+        System.out.println(Crc16Utils.getCRC(data2));
+    }
+}

+ 62 - 0
src/main/java/com/hb/proj/utils/JacksonUtils.java

@@ -0,0 +1,62 @@
+package com.hb.proj.utils;
+
+import java.util.List;
+import java.util.Map;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+public class JacksonUtils {
+
+	//private static final Logger logger = LoggerFactory.getLogger(JacksonUtils.class);
+	
+	private final static ObjectMapper mapper = new ObjectMapper();
+	
+	public static String getJSON(Object obj){
+		try {
+			return mapper.writeValueAsString(obj);
+		}
+		catch(Exception e) {
+    		throw new RuntimeException("jackson序列化对象时出错");
+    	}
+	}
+
+    public static <T> List<T>  getList(String json,Class<T> cls){
+    	try {
+    		return mapper.readValue(json, new TypeReference<List<T>>() {});
+    	}
+    	catch(Exception e) {
+    		throw new RuntimeException("jackson参数转换出错");
+    	}
+    	
+    }
+    
+    public static List<Map<String,Object>>  getMaps(String json){
+    	try {
+    		return mapper.readValue(json, new TypeReference<List<Map<String,Object>>>() {});
+    	}
+    	catch(Exception e) {
+    		throw new RuntimeException("jackson参数转换出错");
+    	}
+    	
+    }
+    
+    @SuppressWarnings("unchecked")
+	public static Map<String,Object>  getMap(String json){
+    	try {
+    		return mapper.readValue(json, Map.class);
+    	}
+    	catch(Exception e) {
+    		throw new RuntimeException("jackson参数转换出错");
+    	}
+    }
+    
+    public static <T> T  get(String json,Class<T> cls) {
+    	try {
+    		return mapper.readValue(json, cls);
+    	}
+    	catch(Exception e) {
+    		throw new RuntimeException("jackson参数转换出错");
+    	}
+    }
+}

+ 40 - 0
src/main/java/com/hb/proj/utils/RequestParams.java

@@ -0,0 +1,40 @@
+package com.hb.proj.utils;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class RequestParams {
+
+	
+	private Map<String, String> args;
+	
+	public RequestParams(){
+		this.args=new HashMap<String,String>();
+	}
+	
+	public RequestParams(Map<String,String> args){
+		this.args=args;
+	}
+
+	public RequestParams(int initialCapacity){
+		this.args=new HashMap<String,String>(initialCapacity);
+	}
+	
+	public String get(String key){
+		return args.get(key);
+	}
+	
+	public void put(String key,String value){
+		args.put(key, value);
+	}
+	
+	public Map<String,String> get(){
+		return args;
+	}
+	
+	public Map<String,Object> getObjectMap(){
+		Map<String,Object> addArgs=new HashMap<String,Object>();
+		addArgs.putAll(args);
+		return addArgs;
+	}
+}

+ 60 - 0
src/main/java/com/hb/proj/utils/RespVO.java

@@ -0,0 +1,60 @@
+package com.hb.proj.utils;
+
+public class RespVO<T>{
+	
+	/**
+	 * 接口调用返回码,0:成功;非0:失败  4xx  接口调用参数错误,5xx接口服务内部错误
+	 */
+	private int code; 
+
+	/**
+	 * 接口返回数据
+	 */
+	private T  data;
+	
+	
+	/**
+	 * 消息
+	 */
+	private String msg;  
+	
+	public RespVO() {
+		this.code=0;
+	}
+	
+	public RespVO(int code,T  data,String  msg) {
+		this.code=code;
+		this.data=data;
+		this.msg=msg;
+	}
+
+	
+
+	public T getData() {
+		return data;
+	}
+
+	public void setData(T data) {
+		this.data = data;
+	}
+
+	
+
+	public int getCode() {
+		return code;
+	}
+
+	public void setCode(int code) {
+		this.code = code;
+	}
+
+	public String getMsg() {
+		return msg;
+	}
+
+	public void setMsg(String msg) {
+		this.msg = msg;
+	}
+	
+	
+}

+ 52 - 0
src/main/java/com/hb/proj/utils/RespVOBuilder.java

@@ -0,0 +1,52 @@
+package com.hb.proj.utils;
+
+public class RespVOBuilder {
+	
+	public static final int API_CALL_ERROR=400;  //api调用错误,逻辑错误
+	
+	public static final int API_EXE_ERROR=500;  //api执行错误,运行时错误
+	
+	public static final int NO_AUTH_ERROR=403;  //api调用权限不足
+	
+	public static final int UN_IDENTIFY_ERROR=401;  //未认证用户
+
+	public static <T> RespVO<T>  ok() {  
+		return new RespVO<T>(0,null,getI18n("操作成功"));
+	}
+	
+	public static <T>  RespVO<T>  ok(T data) {
+		return new RespVO<T>(0,data,getI18n("操作成功"));
+	}
+	
+	public static <T> RespVO<T>  error(String error) {
+		return new RespVO<T>(API_CALL_ERROR,null,getI18n(error));
+	}
+	
+	public static <T> RespVO<T>  noAuth(String error) {
+		return new RespVO<T>(NO_AUTH_ERROR,null,getI18n(error));
+	}
+	
+	public static <T> RespVO<T>  unIdentify() {
+		return new RespVO<T>(UN_IDENTIFY_ERROR,null,getI18n("未认证或认证已失效"));
+	}
+	
+	public static <T> RespVO<T>  error(int code,String error) {
+		return new RespVO<T>(code,null,getI18n(error));
+	}
+	
+	public static <T> RespVO<T>  error(int code,String error,T data) {
+		return new RespVO<T>(code,data,getI18n(error));
+	}
+	
+	
+	
+	private static String getI18n(String str) {
+		/*
+		if(StringUtils.isBlank(str)) {
+			return null;
+		}
+		return LocalConfig.get(MD5Encrypt.md5(str.trim()),str);
+		*/
+		return str;
+	}
+}

+ 40 - 0
src/main/java/logback-spring.xml

@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<configuration>
+    <appender name="consoleAppender" class="ch.qos.logback.core.ConsoleAppender">
+        <layout class="ch.qos.logback.classic.PatternLayout">
+            <Pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg %n
+            </Pattern>
+        </layout>
+        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
+            <level>DEBUG</level>
+        </filter>
+    </appender>
+    
+    
+ 
+    
+ 
+    <springProfile name="dev">
+        <root level="WARN"  additivity="false">
+            <appender-ref ref="consoleAppender"/>
+       </root>
+        <logger name="com.hb.proj" level="DEBUG" additivity="false">
+             <appender-ref ref="consoleAppender"/>
+        </logger>
+       
+    </springProfile>
+    
+    
+    <springProfile name="pro">
+        <root level="WARN"  additivity="false">
+            <appender-ref ref="consoleAppender"/>
+       </root>
+        <logger name="com.hb.proj" level="DEBUG" additivity="false">
+             <appender-ref ref="consoleAppender"/>
+        </logger>
+       
+    </springProfile>
+ 
+    
+ 
+</configuration>

+ 60 - 0
src/main/resources/application-dev.properties

@@ -0,0 +1,60 @@
+# 应用名称
+spring.application.name=智能油田
+
+# 应用服务 WEB 访问端口
+server.port=8080
+
+server.servlet.context-path=/zl
+server.tomcat.uri-encoding=UTF-8
+server.servlet.encoding.charset=UTF-8
+server.servlet.encoding.enabled=true
+server.servlet.encoding.force=true
+
+#语言国际化配置
+spring.messages.active=false
+spring.messages.encoding=UTF-8
+spring.messages.basename: static/i18n/messages
+
+#全局时间输出格式化(对map中的date无效),优先级低于实体属性上的格式化注解
+spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
+spring.jackson.time-zone=GMT+8
+
+#日志配置
+#业务日志配置
+spring.syslog.aspect.active=true
+
+#数据库连接池配置
+spring.datasource.url=jdbc:mysql://127.0.0.1:3306/zl_opd?useOldAliasMetadataBehavior=true
+spring.datasource.username=root
+spring.datasource.password=123456
+spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
+spring.datasource.type=com.hb.xframework.dao.util.HikariDataSourceWrap
+spring.datasource.dialect=MySQL
+
+hikari.connection-timeout: 10000
+hikari.validation-timeout: 3000
+hikari.idle-timeout: 60000
+hikari.login-timeout: 5
+hikari.max-lifetime: 60000
+hikari.maximum-pool-size: 10
+hikari.minimum-idle: 5
+hikari.read-only: false
+
+#redis 连接配置
+spring.redis.database=0
+spring.redis.host=42.56.120.92
+spring.redis.port=9608
+spring.redis.password=redis7.0
+
+#缓存配置  登录信息过期时间,单位分钟
+cache.token.expire=30
+
+#token 在请求header中的name,默认为token
+token.header.name=token
+
+#api调用权限控制filter配置
+api.filter.exclude=/login
+
+#系统管理员
+sys.admin.account=admin
+sys.admin.pwd=2f459bf76f471ff9753caffd1be02bac

+ 60 - 0
src/main/resources/application-pro.properties

@@ -0,0 +1,60 @@
+# 应用名称
+spring.application.name=智能油田
+
+# 应用服务 WEB 访问端口
+server.port=8080
+
+server.servlet.context-path=/zl
+server.tomcat.uri-encoding=UTF-8
+server.servlet.encoding.charset=UTF-8
+server.servlet.encoding.enabled=true
+server.servlet.encoding.force=true
+
+#语言国际化配置
+spring.messages.active=false
+spring.messages.encoding=UTF-8
+spring.messages.basename: static/i18n/messages
+
+#全局时间输出格式化(对map中的date无效),优先级低于实体属性上的格式化注解
+spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
+spring.jackson.time-zone=GMT+8
+
+#日志配置
+#业务日志配置
+spring.syslog.aspect.active=true
+
+#数据库连接池配置
+spring.datasource.url=jdbc:mysql://42.56.120.92:9601/zl_opd?useOldAliasMetadataBehavior=true
+spring.datasource.username=root
+spring.datasource.password=zlmysql
+spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
+spring.datasource.type=com.hb.xframework.dao.util.HikariDataSourceWrap
+spring.datasource.dialect=MySQL
+
+hikari.connection-timeout: 10000
+hikari.validation-timeout: 3000
+hikari.idle-timeout: 60000
+hikari.login-timeout: 5
+hikari.max-lifetime: 60000
+hikari.maximum-pool-size: 100
+hikari.minimum-idle: 10
+hikari.read-only: false
+
+#redis 连接配置
+spring.redis.database=0
+spring.redis.host=42.56.120.92
+spring.redis.port=9608
+spring.redis.password=redis7.0
+
+#缓存配置  登录信息过期时间,单位分钟
+cache.token.expire=30
+
+#token 在请求header中的name,默认为token
+token.header.name=token
+
+#api调用权限控制filter配置
+api.filter.exclude=/login
+
+#系统管理员
+sys.admin.account=admin
+sys.admin.pwd=2f459bf76f471ff9753caffd1be02bac

+ 1 - 0
src/main/resources/application.properties

@@ -0,0 +1 @@
+spring.profiles.active=dev

+ 15 - 0
src/main/resources/smart-doc.json

@@ -0,0 +1,15 @@
+{
+	"serverUrl":"http://127.0.0.1:8080/zl",
+	
+	"outPath": "src/main/resources/static/doc",
+	"allInOne":true,
+	"createDebugPage":true,
+	"revisionLogs": [{ 
+      "version": "1.0", 
+      "revisionTime": "2023-02-01 10:30",
+      "status": "update", 
+      "author": "文", 
+      "remarks": "desc" 
+      }
+    ]
+}

+ 0 - 0
src/main/resources/static/i18n/messages.properties


+ 0 - 0
src/main/resources/static/i18n/messages_en_US.properties


+ 1 - 0
src/main/resources/static/i18n/messages_zh_CN.properties

@@ -0,0 +1 @@
+5c76474c13d9f8c5cd912ec5ca3f214a=lost param222

+ 34 - 0
src/main/resources/transaction.xml

@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+     xmlns:aop="http://www.springframework.org/schema/aop"    
+     xmlns:tx="http://www.springframework.org/schema/tx"
+     xmlns:context="http://www.springframework.org/schema/context"
+	 xsi:schemaLocation="http://www.springframework.org/schema/beans
+		http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
+		http://www.springframework.org/schema/tx     
+        http://www.springframework.org/schema/tx/spring-tx-3.0.xsd    
+		http://www.springframework.org/schema/aop     
+        http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
+		http://www.springframework.org/schema/context
+		http://www.springframework.org/schema/context/spring-context-3.0.xsd" >
+		
+	<!-- spring boot 会根据持久化引入的starter自动构建事务管理器,引入了jdbc-starter 事务管理器名称:transactionManager,不需要在本文件重复定义 -->	 
+   <tx:advice id="txAdvice" transaction-manager="transactionManager">  
+       <tx:attributes>  
+           <tx:method name="add*" 		propagation="REQUIRED" rollback-for="Exception"/>  
+           <tx:method name="create*" 	propagation="REQUIRED" rollback-for="Exception"/>  
+           <tx:method name="insert*" 	propagation="REQUIRED" rollback-for="Exception"/>  
+           <tx:method name="del*" 		propagation="REQUIRED" rollback-for="Exception"/> 
+           <tx:method name="save*" 		propagation="REQUIRED" rollback-for="Exception"/>  
+           <tx:method name="update*" 	propagation="REQUIRED" rollback-for="Exception"/>  <!-- rollback-for="Exception" -->
+           <tx:method name="*" 			read-only="true"/>
+      </tx:attributes>  
+    </tx:advice>  
+    
+      <!-- 声明式事务管理 -->  
+    <aop:config>  
+       <aop:advisor pointcut="(execution(* com.hb.proj.*.service.*.*(..)))" advice-ref="txAdvice" /> 
+       
+    </aop:config> 
+</beans>

+ 7 - 0
src/main/webapp/WEB-INF/web.xml

@@ -0,0 +1,7 @@
+<!DOCTYPE web-app PUBLIC
+ "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
+ "http://java.sun.com/dtd/web-app_2_3.dtd" >
+
+<web-app>
+  <display-name>Archetype Created Web Application</display-name>
+</web-app>