lifuquan 2 rokov pred
commit
ccdf9b85c9
58 zmenil súbory, kde vykonal 1530 pridanie a 0 odobranie
  1. 8 0
      .gitignore
  2. 25 0
      doc/河北垃圾短信监控系统提供的文档/文档中存在的疑问20220725.md
  3. BIN
      doc/河北垃圾短信监控系统提供的文档/河北联通垃圾短信一键解黑接口规范-2022072502.docx
  4. BIN
      doc/河北垃圾短信监控系统提供的文档/河北联通垃圾短信一键解黑接口规范.docx
  5. BIN
      doc/河北垃圾短信监控系统提供的文档/河北联通垃圾短信黑名单加解黑同步接口规范.doc
  6. 44 0
      doc/短信黑名单接口说明文档.md
  7. 9 0
      readme.md
  8. 6 0
      sms_blk_api/.gitignore
  9. 3 0
      sms_blk_api/bin/run.sh
  10. BIN
      sms_blk_api/bin/smsBlacklistRemoveApi-exec.jar
  11. 6 0
      sms_blk_api/bin/stop.sh
  12. 3 0
      sms_blk_api/bin/test/application.properties
  13. 3 0
      sms_blk_api/bin/test/run_test.sh
  14. BIN
      sms_blk_api/bin/test/smsBlacklistRemoveApi-exec_test.jar
  15. 6 0
      sms_blk_api/bin/test/stop_test.sh
  16. 37 0
      sms_blk_api/pom.xml
  17. 67 0
      sms_blk_api/src/main/java/com/nokia/common/http/R.java
  18. 59 0
      sms_blk_api/src/main/java/com/nokia/common/io/ByteArrayUtil.java
  19. 12 0
      sms_blk_api/src/main/java/com/nokia/sms/SmsBlkApiApplication.java
  20. 24 0
      sms_blk_api/src/main/java/com/nokia/sms/config/BeanConfig.java
  21. 21 0
      sms_blk_api/src/main/java/com/nokia/sms/config/BlkConfig.java
  22. 46 0
      sms_blk_api/src/main/java/com/nokia/sms/controller/BlkController.java
  23. 40 0
      sms_blk_api/src/main/java/com/nokia/sms/entity/BindRespStatus.java
  24. 31 0
      sms_blk_api/src/main/java/com/nokia/sms/entity/CommandId.java
  25. 27 0
      sms_blk_api/src/main/java/com/nokia/sms/entity/DelBlkBody.java
  26. 26 0
      sms_blk_api/src/main/java/com/nokia/sms/entity/DelBlkRespStatus.java
  27. 8 0
      sms_blk_api/src/main/java/com/nokia/sms/exception/ConnectFailedException.java
  28. 5 0
      sms_blk_api/src/main/java/com/nokia/sms/exception/ConnectionOutOfSyncException.java
  29. 7 0
      sms_blk_api/src/main/java/com/nokia/sms/exception/ParseException.java
  30. 5 0
      sms_blk_api/src/main/java/com/nokia/sms/exception/PhoneNumberCanNotBeNullException.java
  31. 130 0
      sms_blk_api/src/main/java/com/nokia/sms/message/ClientMessageUtil.java
  32. 61 0
      sms_blk_api/src/main/java/com/nokia/sms/message/MessageHeader.java
  33. 212 0
      sms_blk_api/src/main/java/com/nokia/sms/service/SocketClientService.java
  34. 9 0
      sms_blk_api/src/main/java/com/nokia/sms/vo/DelBlkResp.java
  35. 11 0
      sms_blk_api/src/main/java/com/nokia/sms/vo/RequestParams.java
  36. 3 0
      sms_blk_api/src/main/resources/application.properties
  37. 8 0
      sms_blk_api/src/main/resources/smsblk.properties
  38. 6 0
      sms_blk_server/.gitignore
  39. 3 0
      sms_blk_server/bin/run.sh
  40. BIN
      sms_blk_server/bin/smsBlacklistRemoveApi-exec.jar
  41. 6 0
      sms_blk_server/bin/stop.sh
  42. 3 0
      sms_blk_server/bin/test/application.properties
  43. 3 0
      sms_blk_server/bin/test/run_test.sh
  44. BIN
      sms_blk_server/bin/test/smsBlacklistRemoveApi-exec_test.jar
  45. 6 0
      sms_blk_server/bin/test/stop_test.sh
  46. 33 0
      sms_blk_server/pom.xml
  47. 59 0
      sms_blk_server/src/main/java/com/nokia/common/io/ByteArrayUtil.java
  48. 97 0
      sms_blk_server/src/main/java/com/nokia/sms/BlkDelServer.java
  49. 40 0
      sms_blk_server/src/main/java/com/nokia/sms/entity/BindRespStatus.java
  50. 31 0
      sms_blk_server/src/main/java/com/nokia/sms/entity/CommandId.java
  51. 27 0
      sms_blk_server/src/main/java/com/nokia/sms/entity/DelBlkBody.java
  52. 26 0
      sms_blk_server/src/main/java/com/nokia/sms/entity/DelBlkRespStatus.java
  53. 8 0
      sms_blk_server/src/main/java/com/nokia/sms/exception/ConnectFailedException.java
  54. 5 0
      sms_blk_server/src/main/java/com/nokia/sms/exception/ConnectionOutOfSyncException.java
  55. 7 0
      sms_blk_server/src/main/java/com/nokia/sms/exception/ParseException.java
  56. 5 0
      sms_blk_server/src/main/java/com/nokia/sms/exception/PhoneNumberCanNotBeNullException.java
  57. 64 0
      sms_blk_server/src/main/java/com/nokia/sms/message/MessageHeader.java
  58. 139 0
      sms_blk_server/src/main/java/com/nokia/sms/message/ServerMessageUtil.java

+ 8 - 0
.gitignore

@@ -0,0 +1,8 @@
+# maven项目编译目标路径
+target/
+# vscode项目配置文件路径
+.vscode
+# office 临时文件
+~$*
+# 屏蔽的文件
+ignored/

+ 25 - 0
doc/河北垃圾短信监控系统提供的文档/文档中存在的疑问20220725.md

@@ -0,0 +1,25 @@
+# 接口文档中的疑问
+
+1. `C-Octet String 以NULL结束的字符串` 和 `Octet String 无NULL结束` 这里的 C-Octet String 需要如何处理
+
+    解答: 末尾添加\0(0x00)
+
+    补充提问: `AuthCode` 值为共享的认证密码(由垃圾短信监控系统与短信投诉处理系统分别共享)和当前日期经过SHA-1散列后的输出, 这个字段也要求是`20`字节的`C-Octet String`,但是sha1以后就是20字节了,0x00添加在哪儿?
+
+    答案: 补全的意思是从0位开始写入,如果位数不足,在末尾填入0x00
+
+2. `SMIT_ACTIVE_TEST` 时间间隔是多少
+
+    建议15秒
+
+3. 解除黑名单请求定义中 `blk_Num` 如何区分 `点对点和网间用户` 和 `端口用户`
+
+    不需要带86
+
+4. 解除黑名单请求定义中 `blk_type` 如何区分 `主叫黑名单` 和 `被叫黑名单`  河北垃圾短信监控系统向投诉平台同步黑名单号码接口规范 这个规范里提供的字段不包括`主叫黑名单` 和 `被叫黑名单`, 主叫黑名单 是不是不能发短信,被叫黑名单是不是不能收短信?
+
+    仅针对主叫黑名单
+
+5. 解除黑名单请求定义中 `province_code` 我们的权限是否仅包含河北省(14), 这里的省份是号码归属省份还是仅仅是这个号码在河北省的黑名单中? 举例,一个山西的号码被添加了河北省黑名单,是不是就是这个山西用户在河北省内漫游时无法收发短信?
+
+    与黑名单省份相关,与号码归属地无关,所以漫游用户也可以用。

BIN
doc/河北垃圾短信监控系统提供的文档/河北联通垃圾短信一键解黑接口规范-2022072502.docx


BIN
doc/河北垃圾短信监控系统提供的文档/河北联通垃圾短信一键解黑接口规范.docx


BIN
doc/河北垃圾短信监控系统提供的文档/河北联通垃圾短信黑名单加解黑同步接口规范.doc


+ 44 - 0
doc/短信黑名单接口说明文档.md

@@ -0,0 +1,44 @@
+# 短信黑名单接口说明文档
+
+## 接口1: 黑名单查询接口
+
+```http
+POST HTTP://127.0.0.1:12086/sms/blacklist/api/query/
+Content-Type: application/json
+
+{
+    "phoneNumber": "13231899751",
+    "fromSystem": "test"
+}
+```
+
+## 接口2: 黑名单解除接口
+
+```http
+POST HTTP://127.0.0.1:12110/sms/blacklist/api/remove/
+Content-Type: application/json
+
+{
+    "phoneNumber": "13231899751",
+    "fromSystem": "test",
+    "operatorId": "test01"
+}
+```
+
+```json
+HTTP/1.1 200 
+Content-Type: application/json
+Transfer-Encoding: chunked
+Date: Mon, 01 Aug 2022 02:18:48 GMT
+Connection: close
+
+{
+  "success": true,
+  "code": 200,
+  "message": "成功",
+  "data": {
+    "delBlkSuccess": true,
+    "delBlkMessage": "成功解除黑名单"
+  }
+}
+```

+ 9 - 0
readme.md

@@ -0,0 +1,9 @@
+# 短信黑名单接口
+
+[短信黑名单接口说明文档](doc/%E7%9F%AD%E4%BF%A1%E9%BB%91%E5%90%8D%E5%8D%95%E6%8E%A5%E5%8F%A3%E8%AF%B4%E6%98%8E%E6%96%87%E6%A1%A3.md)
+
+## v1.0 20220725
+
+正式环境部署端口 12110
+
+测试环境部署端口 12120

+ 6 - 0
sms_blk_api/.gitignore

@@ -0,0 +1,6 @@
+# maven项目编译目标路径
+target/
+# vscode项目配置文件路径
+.vscode
+# office 临时文件
+~$*

+ 3 - 0
sms_blk_api/bin/run.sh

@@ -0,0 +1,3 @@
+#!/bin/bash
+
+nohup java -jar /data1/s130/sms_blacklist/smsBlacklistRemoveApi-exec.jar > /data1/s130/sms_blacklist/output.out 2>&1 &

BIN
sms_blk_api/bin/smsBlacklistRemoveApi-exec.jar


+ 6 - 0
sms_blk_api/bin/stop.sh

@@ -0,0 +1,6 @@
+#!/bin/bash
+for i in $(ps -ef|grep smsBlacklistRemoveApi-exec.jar |grep -v grep|awk '{print $2}')
+do 
+kill -9 $i;
+done
+

+ 3 - 0
sms_blk_api/bin/test/application.properties

@@ -0,0 +1,3 @@
+server.port=12120
+
+logging.level.com.nokia=debug

+ 3 - 0
sms_blk_api/bin/test/run_test.sh

@@ -0,0 +1,3 @@
+#!/bin/bash
+
+nohup java -jar /data1/s130/sms_blacklist/test/smsBlacklistRemoveApi-exec_test.jar > /data1/s130/sms_blacklist/test/output.out 2>&1 &

BIN
sms_blk_api/bin/test/smsBlacklistRemoveApi-exec_test.jar


+ 6 - 0
sms_blk_api/bin/test/stop_test.sh

@@ -0,0 +1,6 @@
+#!/bin/bash
+for i in $(ps -ef|grep smsBlacklistRemoveApi-exec_test.jar |grep -v grep|awk '{print $2}')
+do 
+kill -9 $i;
+done
+

+ 37 - 0
sms_blk_api/pom.xml

@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>com.nokia</groupId>
+        <artifactId>hb_springboot_parent</artifactId>
+        <version>1.0</version>
+        <relativePath />
+    </parent>
+
+    <groupId>com.nokia</groupId>
+    <artifactId>smsBlkApi</artifactId>
+    <version>1.0</version>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        
+    </build>
+
+</project>

+ 67 - 0
sms_blk_api/src/main/java/com/nokia/common/http/R.java

@@ -0,0 +1,67 @@
+package com.nokia.common.http;
+
+import lombok.Data;
+
+/**
+ * 返回值的统一包装
+ */
+@Data
+public class R {
+    private Boolean success;
+    private Integer code;
+    private String message;
+
+    private Object data = null;
+
+    /**
+     * 私有化构造方法,不允许在外部实例化
+     */
+    private R() {
+    }
+
+    /**
+     * 成功的静态方法
+     *
+     * @return R实例
+     */
+    public static R ok() {
+        R r = new R();
+        r.setSuccess(true);
+        r.setCode(200);
+        r.setMessage("成功");
+        return r;
+    }
+
+    /**
+     * 失败的静态方法
+     *
+     * @return R实例
+     */
+    public static R error() {
+        R r = new R();
+        r.setSuccess(false);
+        r.setCode(500);
+        r.setMessage("失败");
+        return r;
+    }
+
+    public R success(Boolean success) {
+        this.setSuccess(success);
+        return this;
+    }
+
+    public R code(Integer code) {
+        this.setCode(code);
+        return this;
+    }
+
+    public R data(Object object) {
+        this.setData(object);
+        return this;
+    }
+
+    public R message(String message) {
+        this.setMessage(message);
+        return this;
+    }
+}

+ 59 - 0
sms_blk_api/src/main/java/com/nokia/common/io/ByteArrayUtil.java

@@ -0,0 +1,59 @@
+package com.nokia.common.io;
+
+import java.nio.ByteBuffer;
+
+/**
+ * 整数和字节数组相互转化的工具类
+ */
+public class ByteArrayUtil {
+
+    /**
+     * 从buffer的从offset开始的4个字节中读取一个Integer
+     */
+    public static int parseInteger(byte[] buffer, int offset) {
+        // 识别
+        byte[] byteArr = new byte[4];
+        int size = buffer.length - offset;
+        if (size >= 4) {
+            System.arraycopy(buffer, offset, byteArr, 0, 4);
+        } else {
+            System.arraycopy(buffer, offset, byteArr, 4 - size, size);
+        }
+
+        return ByteBuffer.wrap(byteArr).getInt();
+    }
+
+    /**
+     * 从buffer的从offset开始的4个字节中读取一个无符号整数,以long的类型返回
+     */
+    public static long parseUnsignedInteger(byte[] buffer, int offset) {
+        // 识别
+        byte[] byteArr = new byte[8];
+        int size = buffer.length - offset;
+        if (size >= 4) {
+            System.arraycopy(buffer, offset, byteArr, 4, 4);
+        } else {
+            System.arraycopy(buffer, offset, byteArr, 8 - size, size);
+        }
+
+        return ByteBuffer.wrap(byteArr).getLong();
+    }
+
+    /**
+     * 把int转化成4字节数组
+     */
+    public static byte[] toByteArray(int value) {
+        ByteBuffer buffer = ByteBuffer.allocate(4);
+        buffer.putInt(value);
+        return buffer.array();
+    }
+
+    /*
+     * 把long转成8字节数组
+     */
+    public static byte[] toByteArray(long value) {
+        ByteBuffer buffer = ByteBuffer.allocate(8);
+        buffer.putLong(value);
+        return buffer.array();
+    }
+}

+ 12 - 0
sms_blk_api/src/main/java/com/nokia/sms/SmsBlkApiApplication.java

@@ -0,0 +1,12 @@
+package com.nokia.sms;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class SmsBlkApiApplication {
+
+    public static void main(String[] args) {
+        SpringApplication.run(SmsBlkApiApplication.class, args);
+    }
+}

+ 24 - 0
sms_blk_api/src/main/java/com/nokia/sms/config/BeanConfig.java

@@ -0,0 +1,24 @@
+package com.nokia.sms.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
+import org.springframework.stereotype.Component;
+
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@Component
+public class BeanConfig {
+
+    @Bean("socketScheduler")
+    public ThreadPoolTaskScheduler socketScheduler() {
+        // 使用最常用的ThreadPoolTaskScheduler
+        ThreadPoolTaskScheduler socketScheduler = new ThreadPoolTaskScheduler();
+        // 线程数
+        socketScheduler.setPoolSize(1);
+        socketScheduler.setRemoveOnCancelPolicy(true);
+        socketScheduler.setThreadNamePrefix("socketScheduler-");
+        log.info("已完成 用于socket调度的 ThreadPoolTaskScheduler 配置, poolSize = {}", 1);
+        return socketScheduler;
+    }
+}

+ 21 - 0
sms_blk_api/src/main/java/com/nokia/sms/config/BlkConfig.java

@@ -0,0 +1,21 @@
+package com.nokia.sms.config;
+
+import java.io.IOException;
+import java.util.Properties;
+
+public class BlkConfig {
+
+    private static final Properties PROPERTIES = new Properties();
+
+    static {
+        try {
+            PROPERTIES.load(ClassLoader.getSystemResourceAsStream("smsblk.properties"));
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public static String getConfig(String name) {
+        return PROPERTIES.getProperty(name);
+    }
+}

+ 46 - 0
sms_blk_api/src/main/java/com/nokia/sms/controller/BlkController.java

@@ -0,0 +1,46 @@
+package com.nokia.sms.controller;
+
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import com.nokia.common.http.R;
+import com.nokia.sms.service.SocketClientService;
+import com.nokia.sms.vo.DelBlkResp;
+import com.nokia.sms.vo.RequestParams;
+
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * 黑名单查询及解除接口类
+ */
+@Slf4j
+@RestController
+@RequestMapping("/sms/blacklist/api/")
+public class BlkController {
+    private final SocketClientService socketClientService;
+
+    public BlkController(SocketClientService socketClientService) {
+        this.socketClientService = socketClientService;
+    }
+
+    /**
+     * 黑名单解除接口
+     */
+    @PostMapping("remove")
+    public R remove(@RequestBody RequestParams params) {
+        // 尝试解除黑名单
+        log.debug("入参 -- {} ", params);
+        DelBlkResp delBlkResp = new DelBlkResp();
+        if (socketClientService.delBlk(params.getPhoneNumber())) {
+            delBlkResp.setDelBlkSuccess(true);
+            delBlkResp.setDelBlkMessage("成功解除黑名单");
+        } else {
+            delBlkResp.setDelBlkSuccess(false);
+            delBlkResp.setDelBlkMessage("解除黑名单失败");
+        }
+        log.debug("返回 -- {}", delBlkResp);
+        return R.ok().data(delBlkResp);
+    }
+}

+ 40 - 0
sms_blk_api/src/main/java/com/nokia/sms/entity/BindRespStatus.java

@@ -0,0 +1,40 @@
+package com.nokia.sms.entity;
+
+/*
+     * Status 1 Unsigned Integer 否 状态
+     * 见错误码表:
+     * 0:成功
+     * 12:消息结构错
+     * 13:非法源地址
+     * 14:认证错
+     * 21~其它错误
+     */
+public enum BindRespStatus {
+    SUCCESS((byte) 0, "成功"),
+    STRUCTURE_ERROR((byte) 12, "消息结构错"),
+    ILLEGAL_SOURCE_IP((byte) 13, "非法源地址"),
+    AUTH_ERROR((byte) 14, "认证错"),
+    OTHER_ERROR((byte) 21, "其他错误");
+
+    public final byte value;
+    public final String status;
+
+    BindRespStatus(byte value, String status) {
+        this.value = value;
+        this.status = status;
+    }
+
+    public static BindRespStatus parse(byte value) {
+        for (BindRespStatus status : values()) {
+            if (value == status.value) {
+                return status;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public String toString() {
+        return name() + ":" + status;
+    }
+}

+ 31 - 0
sms_blk_api/src/main/java/com/nokia/sms/entity/CommandId.java

@@ -0,0 +1,31 @@
+package com.nokia.sms.entity;
+
+/**
+ * Command_Id定义
+ * SMIT_BIND 0x00000001 绑定请求
+ * SMIT_BIND_RESP 0x80000001 绑定应答
+ * SMIT_ACTIVE_TEST 0x0000000f 链路检测
+ * SMIT_ACTIVE_TEST_RESP 0x8000000f 链路检测应答
+ * SMIT_DELBLK 0x00000003 解除黑名单请求
+ * SMIT_DELBLK_RESP 0x80000003 解除黑名单应答
+ */
+public enum CommandId {
+    SMIT_BIND(1L), SMIT_BIND_RESP(2147483649L),
+    SMIT_ACTIVE_TEST(15L), SMIT_ACTIVE_TEST_RESP(2147483663L),
+    SMIT_DELBLK(3L), SMIT_DELBLK_RESP(2147483651L);
+
+    public final long value;
+
+    CommandId(long value) {
+        this.value = value;
+    }
+
+    public static CommandId parse(long value) {
+        for (CommandId commandId : values()) {
+            if (value == commandId.value) {
+                return commandId;
+            }
+        }
+        return null;
+    }
+}

+ 27 - 0
sms_blk_api/src/main/java/com/nokia/sms/entity/DelBlkBody.java

@@ -0,0 +1,27 @@
+package com.nokia.sms.entity;
+
+import lombok.Data;
+
+/*
+ * 解除黑名单请求的body
+ */
+@Data
+public class DelBlkBody {
+    // blk_Num 21 C-Octet String 否 不带86
+    private String blkNum;
+    // block_type 1 Unsigned Integer 否 1:黑名单号码 2:黑名单号段
+    private byte block_type = 1;
+    // blk_type 1 Unsigned Integer 否 1:主叫黑名单 固定值
+    private byte blk_type = 1;
+    // province_code 1 Unsigned Integer 否 黑名单的省份代码,参见附录B
+    // 河北省对应code为14,这里是黑名单省份,与号码归属无关
+    private byte province_code = 14;
+
+    public DelBlkBody(String phoneNumber) {
+        blkNum = phoneNumber;
+    }
+
+    public DelBlkBody() {
+        
+    }
+}

+ 26 - 0
sms_blk_api/src/main/java/com/nokia/sms/entity/DelBlkRespStatus.java

@@ -0,0 +1,26 @@
+package com.nokia.sms.entity;
+
+/*
+ * Status	1	Unsigned Integer	否	状态 0:成功 1:失败
+ */
+public enum DelBlkRespStatus {
+    SUCCESS((byte) 0, "成功"),
+    FAILED((byte) 1, "失败");
+
+    public final byte value;
+    public final String status;
+
+    DelBlkRespStatus(byte value, String status) {
+        this.value = value;
+        this.status = status;
+    }
+
+    public static DelBlkRespStatus parse(byte value) {
+        for (DelBlkRespStatus status : values()) {
+            if (value == status.value) {
+                return status;
+            }
+        }
+        return null;
+    }
+}

+ 8 - 0
sms_blk_api/src/main/java/com/nokia/sms/exception/ConnectFailedException.java

@@ -0,0 +1,8 @@
+package com.nokia.sms.exception;
+
+public class ConnectFailedException extends Exception {
+    
+    public ConnectFailedException(String message) {
+        super(message);
+    }
+}

+ 5 - 0
sms_blk_api/src/main/java/com/nokia/sms/exception/ConnectionOutOfSyncException.java

@@ -0,0 +1,5 @@
+package com.nokia.sms.exception;
+
+public class ConnectionOutOfSyncException extends Exception {
+
+}

+ 7 - 0
sms_blk_api/src/main/java/com/nokia/sms/exception/ParseException.java

@@ -0,0 +1,7 @@
+package com.nokia.sms.exception;
+
+public class ParseException extends Exception {
+    public ParseException(String message) {
+        super(message);
+    }
+}

+ 5 - 0
sms_blk_api/src/main/java/com/nokia/sms/exception/PhoneNumberCanNotBeNullException.java

@@ -0,0 +1,5 @@
+package com.nokia.sms.exception;
+
+public class PhoneNumberCanNotBeNullException extends Exception {
+    
+}

+ 130 - 0
sms_blk_api/src/main/java/com/nokia/sms/message/ClientMessageUtil.java

@@ -0,0 +1,130 @@
+package com.nokia.sms.message;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import com.nokia.sms.entity.BindRespStatus;
+import com.nokia.sms.entity.CommandId;
+import com.nokia.sms.entity.DelBlkBody;
+import com.nokia.sms.entity.DelBlkRespStatus;
+import com.nokia.sms.exception.ParseException;
+import com.nokia.sms.exception.PhoneNumberCanNotBeNullException;
+
+public class ClientMessageUtil {
+
+    private static final DateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");
+
+    /*
+     * SMIT_BIND 0x00000001 绑定请求
+     */
+    public static byte[] getSmitBindMessage(String systemId, String password) throws NoSuchAlgorithmException {
+        // 初始化header
+        MessageHeader header = new MessageHeader();
+        // header 12 + body 16+20 所以是48字节
+        header.setMessageLength(48);
+        header.setCommandId(CommandId.SMIT_BIND);
+        // 所有的初始连接需要从0开始
+        header.setSequenceNumber(0L);
+        // 初始化
+        byte[] message = new byte[header.getMessageLength()];
+
+        // 填充header
+        System.arraycopy(header.toByteArray(), 0, message, 0, 12);
+
+        // 填充systemId
+        byte[] systemIdByteArray = systemId.getBytes();
+        System.arraycopy(systemIdByteArray, 0, message, 12, systemIdByteArray.length);
+
+        // 填充AuthCode
+        String authCodeString = password + dateFormat.format(new Date());
+        MessageDigest digest = MessageDigest.getInstance("SHA-1");
+        // sha1以后为20字节
+        System.arraycopy(digest.digest(authCodeString.getBytes()), 0, message, 28, 20);
+        return message;
+    }
+
+    /*
+     * SMIT_DELBLK 0x00000003 解除黑名单请求
+     */
+    public static byte[] getSmitDelBlkMessage(DelBlkBody delBlkBody, long sequenceNumber)
+            throws PhoneNumberCanNotBeNullException {
+        // 初始化header
+        MessageHeader header = new MessageHeader();
+        // header 12 + body 21 + 1 + 1 + 1 所以是36字节
+        header.setMessageLength(36);
+        header.setCommandId(CommandId.SMIT_DELBLK);
+        // 从客户端获取下一个序号
+        header.setSequenceNumber(sequenceNumber);
+        // 初始化
+        byte[] message = new byte[header.getMessageLength()];
+        // 填充header
+        System.arraycopy(header.toByteArray(), 0, message, 0, 12);
+
+        // 填充 blk_Num 21字节
+        if (delBlkBody.getBlkNum() == null) {
+            throw new PhoneNumberCanNotBeNullException();
+        }
+        byte[] blkNumByteArray = delBlkBody.getBlkNum().getBytes();
+        System.arraycopy(blkNumByteArray, 0, message, 12, blkNumByteArray.length);
+        // 填充 block_type
+        message[33] = delBlkBody.getBlock_type();
+        message[34] = delBlkBody.getBlk_type();
+        message[35] = delBlkBody.getProvince_code();
+        return message;
+    }
+
+    /*
+     * SMIT_ACTIVE_TEST 0x0000000f 链路检测
+     */
+    public static byte[] getSmitActiveTestMessage(long sequenceNumber) {
+        // 初始化header
+        MessageHeader header = new MessageHeader();
+        // header 12 + body 0 所以是12字节
+        header.setMessageLength(12);
+        header.setCommandId(CommandId.SMIT_ACTIVE_TEST);
+        // 从客户端获取下一个序号
+        header.setSequenceNumber(sequenceNumber);
+        // 初始化
+        byte[] message = new byte[header.getMessageLength()];
+        // 填充header
+        System.arraycopy(header.toByteArray(), 0, message, 0, 12);
+        return message;
+    }
+
+    /*
+     * SMIT_BIND_RESP 0x80000001 绑定应答
+     */
+    public static BindRespStatus parseSmitBindRespMessage(byte[] message) throws ParseException {
+        MessageHeader header = MessageHeader.parse(message);
+        if (header.getCommandId().equals(CommandId.SMIT_BIND_RESP)) {
+            return BindRespStatus.parse(message[12]);
+        }
+        throw new ParseException("期待 SMIT_BIND_RESP 消息,收到了 " + header.getCommandId() + " 消息");
+    }
+
+    /*
+     * SMIT_DELBLK_RESP 0x80000003 解除黑名单应答
+     */
+    public static DelBlkRespStatus parseSmitDelBlkRespMessage(byte[] message) throws ParseException {
+        MessageHeader header = MessageHeader.parse(message);
+        if (header.getCommandId().equals(CommandId.SMIT_DELBLK_RESP)) {
+            return DelBlkRespStatus.parse(message[12]);
+        }
+        throw new ParseException("期待 SMIT_DELBLK_RESP 消息,收到了 " + header.getCommandId() + " 消息");
+    }
+
+    /*
+     * SMIT_ACTIVE_TEST_RESP 0x8000000f 链路检测应答
+     */
+    public static long parseSmitActiveTestRespMessage(byte[] message) throws ParseException {
+        MessageHeader header = MessageHeader.parse(message);
+        // 判断消息类型
+        if (CommandId.SMIT_ACTIVE_TEST_RESP.equals(header.getCommandId())) {
+            return header.getSequenceNumber();
+        }
+        throw new ParseException("期待 SMIT_ACTIVE_TEST_RESP 消息,收到了 " + header.getCommandId() + " 消息");
+    }
+}

+ 61 - 0
sms_blk_api/src/main/java/com/nokia/sms/message/MessageHeader.java

@@ -0,0 +1,61 @@
+package com.nokia.sms.message;
+
+import java.util.Arrays;
+
+import com.nokia.common.io.ByteArrayUtil;
+import com.nokia.sms.entity.CommandId;
+import com.nokia.sms.exception.ParseException;
+
+import lombok.Data;
+
+/**
+ * Message Length 4 Unsigned Integer 消息长度:消息包的总长度(字节)
+ * Command ID 4 Unsigned Integer 消息类型:表明此条消息的类型,采用固定值
+ * Sequence Number 4 Unsigned Integer
+ * 此字段表示消息的序列号,它由请求发起端产生,是消息和它的应答之间的对应标志。数值在0到0XFFFFFFFF间必须保证严格单调的递增,当达到0XFFFFFFFF时,从0开始下一循环。
+ */
+@Data
+public class MessageHeader {
+    // 协议为无符号int 但是实际情况都不超过int
+    private int messageLength;
+    /**
+     * Command_Id定义
+     * SMIT_BIND 0x00000001 绑定请求
+     * SMIT_BIND_RESP 0x80000001 绑定应答
+     * SMIT_ACTIVE_TEST 0x0000000f 链路检测
+     * SMIT_ACTIVE_TEST_RESP 0x8000000f 链路检测应答
+     * SMIT_DELBLK 0x00000003 解除黑名单请求
+     * SMIT_DELBLK_RESP 0x80000003 解除黑名单应答
+     */
+    private CommandId commandId;
+    private long sequenceNumber;
+
+    public byte[] toByteArray() {
+        byte[] header = new byte[12];
+        // messageLength
+        System.arraycopy(ByteArrayUtil.toByteArray(messageLength), 0, header, 0, 4);
+        // commandId
+        System.arraycopy(ByteArrayUtil.toByteArray(commandId.value), 4, header, 4, 4);
+        // sequenceNumber
+        System.arraycopy(ByteArrayUtil.toByteArray(sequenceNumber), 4, header, 8, 4);
+        return header;
+    }
+
+    public static MessageHeader parse(byte[] message) throws ParseException {
+        // 获取前12个字节作为header
+        byte[] header = Arrays.copyOf(message, 12);
+        MessageHeader messageHeader = new MessageHeader();
+        // messageLength
+        messageHeader.setMessageLength(ByteArrayUtil.parseInteger(header, 0));
+        // commandId
+        long commandIdValue = ByteArrayUtil.parseUnsignedInteger(header, 4);
+        CommandId commandId = CommandId.parse(commandIdValue);
+        if (null == commandId) {
+            throw new ParseException("无法识别 Command_Id : " + commandIdValue);
+        }
+        messageHeader.setCommandId(commandId);
+        // sequenceNumber
+        messageHeader.setSequenceNumber(ByteArrayUtil.parseUnsignedInteger(header, 8));
+        return messageHeader;
+    }
+}

+ 212 - 0
sms_blk_api/src/main/java/com/nokia/sms/service/SocketClientService.java

@@ -0,0 +1,212 @@
+package com.nokia.sms.service;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.concurrent.Future;
+
+import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
+import org.springframework.stereotype.Service;
+
+import com.nokia.sms.config.BlkConfig;
+import com.nokia.sms.entity.BindRespStatus;
+import com.nokia.sms.entity.DelBlkBody;
+import com.nokia.sms.entity.DelBlkRespStatus;
+import com.nokia.sms.exception.ConnectFailedException;
+import com.nokia.sms.exception.ParseException;
+import com.nokia.sms.message.ClientMessageUtil;
+
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+@Service
+public class SocketClientService {
+
+    private final ThreadPoolTaskScheduler socketScheduler;
+
+    volatile Socket socket;
+    volatile InputStream inputStream;
+    volatile OutputStream outputStream;
+    volatile boolean connected;
+    // 0 - 4294967295 0xFFFFFFFF
+    // 在进程间共享
+    volatile long sequenceNumber;
+    private static final long MAX_SEQUENCE_NUMBER = 10L;
+    volatile Future<?> heartbeatFuture;
+
+    volatile String systemId = BlkConfig.getConfig("blkServer.systemId");
+    volatile String password = BlkConfig.getConfig("blkServer.password");
+    volatile String host = BlkConfig.getConfig("blkServer.ipAddress");
+    volatile int port = Integer.parseInt(BlkConfig.getConfig("blkServer.port"));
+    volatile int connectTimeout = Integer.parseInt(BlkConfig.getConfig("blkServer.connectTimeout"));
+    volatile long heartbeatDelay = Long.parseLong(BlkConfig.getConfig("blkServer.heartbeatDelay"));
+
+    public SocketClientService(ThreadPoolTaskScheduler socketScheduler) throws ConnectFailedException {
+        this.socketScheduler = socketScheduler;
+        connect();
+    }
+
+    /*
+     * 完成删除黑名单业务
+     */
+    public boolean delBlk(String phoneNumber) {
+        log.debug("准备删除号码 {} 的短信黑名单记录...", phoneNumber);
+        Future<Boolean> future = socketScheduler.submit(() -> {
+            if (!connected) {
+                connect();
+            }
+            DelBlkBody delBlkBody = new DelBlkBody(phoneNumber);
+            // 发送消息
+            long sequenceNumberSend = getNextSequence();
+            byte[] smitDelBlk = ClientMessageUtil.getSmitDelBlkMessage(delBlkBody, sequenceNumberSend);
+            outputStream.write(smitDelBlk);
+            outputStream.flush();
+            log.debug("已发送解除黑名单消息 SMIT_DELBLK, 序列号: {}", sequenceNumberSend);
+            // 接收回复消息
+            byte[] resp = new byte[13];
+            inputStream.read(resp);
+            log.debug("收到服务器回复... {}", Arrays.toString(resp));
+            DelBlkRespStatus status = ClientMessageUtil.parseSmitDelBlkRespMessage(resp);
+            boolean b = (status == DelBlkRespStatus.SUCCESS);
+            if (b) {
+                // 删除成功, 序列号增加
+                sequenceNumber = getNextSequence();
+            }
+            return b;
+        });
+        try {
+            return future.get();
+        } catch (Exception e) {
+            log.error("删除黑名单出错...");
+            e.printStackTrace();
+            // 关闭连接,释放资源
+            close();
+            return false;
+        }
+    }
+
+    /*
+     * 连接到服务器
+     */
+    public void connect() throws ConnectFailedException {
+        try {
+            // 启动socket连接
+            socket = new Socket();
+            InetAddress address = InetAddress.getByName(host);
+            socket.connect(new InetSocketAddress(address, port), connectTimeout);
+            // 绑定输入输出
+            inputStream = socket.getInputStream();
+            outputStream = socket.getOutputStream();
+            // 向客户端发送绑定请求
+            byte[] smitBind = ClientMessageUtil.getSmitBindMessage(systemId, password);
+            outputStream.write(smitBind);
+            outputStream.flush();
+            log.debug("已发送绑定请求消息 SMIT_BIND");
+            // 接收回复消息
+            byte[] resp = new byte[128];
+            int count = inputStream.read(resp);
+            // 解析消息
+            BindRespStatus status = ClientMessageUtil.parseSmitBindRespMessage(resp);
+            log.debug("收到服务器回复...长度 {} 字节, 状态: {}({})", count, status, status.status);
+            if (BindRespStatus.SUCCESS.equals(status)) {
+                connected = true;
+                log.info("已成功与服务端建立连接,开始心跳检测");
+                // 开启心跳检测
+                heartbeatFuture = socketScheduler.scheduleAtFixedRate(() -> {
+                    heartbeat();
+                }, new Date(System.currentTimeMillis() + heartbeatDelay), heartbeatDelay);
+                return;
+            } else {
+                log.error("服务器连接失败,错误码: {}", status);
+                // 连接失败释放资源
+                close();
+                throw new ConnectFailedException("连接失败,状态码 : " + status);
+            }
+        } catch (IOException | NoSuchAlgorithmException | ParseException e) {
+            log.error("服务器连接失败");
+            // 连接失败释放资源
+            close();
+            throw new ConnectFailedException(
+                    String.format("连接服务器失败, 原因: %s - %s", e.getClass().getName(), e.getMessage()));
+        }
+    }
+
+    /*
+     * 心跳检测
+     */
+    public void heartbeat() {
+        try {
+            // 发送心跳消息
+            long sequenceNumberSend = getNextSequence();
+            byte[] smitActiveTest = ClientMessageUtil.getSmitActiveTestMessage(sequenceNumberSend);
+            outputStream.write(smitActiveTest);
+            outputStream.flush();
+            log.debug("已发送心跳消息, 序列号: {}", sequenceNumberSend);
+            // 读取心跳消息的返回
+            byte[] buffer = new byte[48];
+            inputStream.read(buffer);
+            long sequenceNumberReceive = ClientMessageUtil.parseSmitActiveTestRespMessage(buffer);
+            log.debug("接收到服务器返回的心跳消息, 序列号: {}", sequenceNumberReceive);
+            if (sequenceNumberReceive == sequenceNumberSend) {
+                sequenceNumber = getNextSequence();
+            } else {
+                log.error("心跳乱序,将关闭连接...发送 {} 接收 {} ", sequenceNumberSend, sequenceNumberReceive);
+                close();
+            }
+        } catch (IOException | ParseException e) {
+            log.error("心跳出错,将关闭连接... {}({})", e.getClass().getName(), e.getMessage());
+            close();
+        }
+    }
+
+    /*
+     * 遇到问题关闭连接
+     */
+    public void close() {
+        // 停止心跳检测
+        heartbeatFuture.cancel(true);
+        // 复位sequenceNumber
+        sequenceNumber = 0;
+        // 复位连接指示
+        connected = false;
+        // 释放资源
+        if (inputStream != null) {
+            try {
+                inputStream.close();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+            inputStream = null;
+        }
+        if (outputStream != null) {
+            try {
+                outputStream.close();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+            outputStream = null;
+        }
+        if (socket != null) {
+            try {
+                socket.close();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+            socket = null;
+        }
+    }
+
+    private long getNextSequence() {
+        if (sequenceNumber < MAX_SEQUENCE_NUMBER) {
+            return sequenceNumber + 1L;
+        } else {
+            return 0L;
+        }
+    }
+}

+ 9 - 0
sms_blk_api/src/main/java/com/nokia/sms/vo/DelBlkResp.java

@@ -0,0 +1,9 @@
+package com.nokia.sms.vo;
+
+import lombok.Data;
+
+@Data
+public class DelBlkResp {
+    private boolean delBlkSuccess;
+    private String delBlkMessage;
+}

+ 11 - 0
sms_blk_api/src/main/java/com/nokia/sms/vo/RequestParams.java

@@ -0,0 +1,11 @@
+package com.nokia.sms.vo;
+
+import lombok.Data;
+
+@Data
+public class RequestParams {
+
+    private String phoneNumber;
+    private String fromSystem;
+    private String operatorId;
+}

+ 3 - 0
sms_blk_api/src/main/resources/application.properties

@@ -0,0 +1,3 @@
+server.port=12110
+
+logging.level.com.nokia=debug

+ 8 - 0
sms_blk_api/src/main/resources/smsblk.properties

@@ -0,0 +1,8 @@
+blkServer.ipAddress=localhost
+blkServer.port=50000
+blkServer.systemId=smsTousu
+blkServer.password=123456
+blkServer.heartbeatDelay=5000
+
+# 超时设置
+blkServer.connectTimeout=3000

+ 6 - 0
sms_blk_server/.gitignore

@@ -0,0 +1,6 @@
+# maven项目编译目标路径
+target/
+# vscode项目配置文件路径
+.vscode
+# office 临时文件
+~$*

+ 3 - 0
sms_blk_server/bin/run.sh

@@ -0,0 +1,3 @@
+#!/bin/bash
+
+nohup java -jar /data1/s130/sms_blacklist/smsBlacklistRemoveApi-exec.jar > /data1/s130/sms_blacklist/output.out 2>&1 &

BIN
sms_blk_server/bin/smsBlacklistRemoveApi-exec.jar


+ 6 - 0
sms_blk_server/bin/stop.sh

@@ -0,0 +1,6 @@
+#!/bin/bash
+for i in $(ps -ef|grep smsBlacklistRemoveApi-exec.jar |grep -v grep|awk '{print $2}')
+do 
+kill -9 $i;
+done
+

+ 3 - 0
sms_blk_server/bin/test/application.properties

@@ -0,0 +1,3 @@
+server.port=12120
+
+logging.level.com.nokia=debug

+ 3 - 0
sms_blk_server/bin/test/run_test.sh

@@ -0,0 +1,3 @@
+#!/bin/bash
+
+nohup java -jar /data1/s130/sms_blacklist/test/smsBlacklistRemoveApi-exec_test.jar > /data1/s130/sms_blacklist/test/output.out 2>&1 &

BIN
sms_blk_server/bin/test/smsBlacklistRemoveApi-exec_test.jar


+ 6 - 0
sms_blk_server/bin/test/stop_test.sh

@@ -0,0 +1,6 @@
+#!/bin/bash
+for i in $(ps -ef|grep smsBlacklistRemoveApi-exec_test.jar |grep -v grep|awk '{print $2}')
+do 
+kill -9 $i;
+done
+

+ 33 - 0
sms_blk_server/pom.xml

@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>com.nokia</groupId>
+        <artifactId>hb_springboot_parent</artifactId>
+        <version>1.0</version>
+        <relativePath />
+    </parent>
+
+    <groupId>com.nokia</groupId>
+    <artifactId>smsBlkServer</artifactId>
+    <version>1.0</version>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+
+</project>

+ 59 - 0
sms_blk_server/src/main/java/com/nokia/common/io/ByteArrayUtil.java

@@ -0,0 +1,59 @@
+package com.nokia.common.io;
+
+import java.nio.ByteBuffer;
+
+/**
+ * 整数和字节数组相互转化的工具类
+ */
+public class ByteArrayUtil {
+
+    /**
+     * 从buffer的从offset开始的4个字节中读取一个Integer
+     */
+    public static int parseInteger(byte[] buffer, int offset) {
+        // 识别
+        byte[] byteArr = new byte[4];
+        int size = buffer.length - offset;
+        if (size >= 4) {
+            System.arraycopy(buffer, offset, byteArr, 0, 4);
+        } else {
+            System.arraycopy(buffer, offset, byteArr, 4 - size, size);
+        }
+
+        return ByteBuffer.wrap(byteArr).getInt();
+    }
+
+    /**
+     * 从buffer的从offset开始的4个字节中读取一个无符号整数,以long的类型返回
+     */
+    public static long parseUnsignedInteger(byte[] buffer, int offset) {
+        // 识别
+        byte[] byteArr = new byte[8];
+        int size = buffer.length - offset;
+        if (size >= 4) {
+            System.arraycopy(buffer, offset, byteArr, 4, 4);
+        } else {
+            System.arraycopy(buffer, offset, byteArr, 8 - size, size);
+        }
+
+        return ByteBuffer.wrap(byteArr).getLong();
+    }
+
+    /**
+     * 把int转化成4字节数组
+     */
+    public static byte[] toByteArray(int value) {
+        ByteBuffer buffer = ByteBuffer.allocate(4);
+        buffer.putInt(value);
+        return buffer.array();
+    }
+
+    /*
+     * 把long转成8字节数组
+     */
+    public static byte[] toByteArray(long value) {
+        ByteBuffer buffer = ByteBuffer.allocate(8);
+        buffer.putLong(value);
+        return buffer.array();
+    }
+}

+ 97 - 0
sms_blk_server/src/main/java/com/nokia/sms/BlkDelServer.java

@@ -0,0 +1,97 @@
+package com.nokia.sms;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.ServerSocket;
+import java.net.Socket;
+
+import com.nokia.sms.message.ServerMessageUtil;
+
+import lombok.extern.slf4j.Slf4j;
+
+/*
+ * 模拟服务器
+ */
+@Slf4j
+public class BlkDelServer {
+    // 服务端口号
+    private static int port = 50000;
+    private ServerSocket server = null;
+    private Socket socket = null;
+    private InputStream inputStream = null;
+    private OutputStream outputStream = null;
+    private byte[] buffer = new byte[48];
+    private byte[] message;
+    private int readCount;
+
+    public static void main(String[] args) {
+        BlkDelServer server = new BlkDelServer();
+        server.startServer();
+    }
+
+    private void startServer() {
+        try {
+            // 启动socket服务
+            server = new ServerSocket(port);
+            log.debug("socket服务端已在端口 {} 启动", port);
+            // 接收socket连接
+            Socket socket = server.accept();
+            outputStream = socket.getOutputStream();
+            inputStream = socket.getInputStream();
+            while (true) {
+                // 先读取客户端的输入
+                readCount = inputStream.read(buffer);
+                log.debug("收到 {} 字节 数据", readCount);
+                // 识别消息 并建立 应返回的消息
+                message = ServerMessageUtil.parse(buffer);
+                // 向客户端输出
+                outputStream.write(message);
+                outputStream.flush();
+            }
+        } catch (IOException e) {
+            log.error("服务端运行出错......");
+            e.printStackTrace();
+        } finally {
+            close();
+        }
+    }
+
+    private void close() {
+        if (inputStream != null) {
+            try {
+                inputStream.close();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+        if (outputStream != null) {
+            try {
+                outputStream.close();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+        if (server != null) {
+            try {
+                server.close();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+        if (socket != null) {
+            try {
+                socket.close();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+        if (server != null) {
+            try {
+                server.close();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+}

+ 40 - 0
sms_blk_server/src/main/java/com/nokia/sms/entity/BindRespStatus.java

@@ -0,0 +1,40 @@
+package com.nokia.sms.entity;
+
+/*
+     * Status 1 Unsigned Integer 否 状态
+     * 见错误码表:
+     * 0:成功
+     * 12:消息结构错
+     * 13:非法源地址
+     * 14:认证错
+     * 21~其它错误
+     */
+public enum BindRespStatus {
+    SUCCESS((byte) 0, "成功"),
+    STRUCTURE_ERROR((byte) 12, "消息结构错"),
+    ILLEGAL_SOURCE_IP((byte) 13, "非法源地址"),
+    AUTH_ERROR((byte) 14, "认证错"),
+    OTHER_ERROR((byte) 21, "其他错误");
+
+    public final byte value;
+    public final String status;
+
+    BindRespStatus(byte value, String status) {
+        this.value = value;
+        this.status = status;
+    }
+
+    public static BindRespStatus parse(byte value) {
+        for (BindRespStatus status : values()) {
+            if (value == status.value) {
+                return status;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public String toString() {
+        return name() + ":" + status;
+    }
+}

+ 31 - 0
sms_blk_server/src/main/java/com/nokia/sms/entity/CommandId.java

@@ -0,0 +1,31 @@
+package com.nokia.sms.entity;
+
+/**
+ * Command_Id定义
+ * SMIT_BIND 0x00000001 绑定请求
+ * SMIT_BIND_RESP 0x80000001 绑定应答
+ * SMIT_ACTIVE_TEST 0x0000000f 链路检测
+ * SMIT_ACTIVE_TEST_RESP 0x8000000f 链路检测应答
+ * SMIT_DELBLK 0x00000003 解除黑名单请求
+ * SMIT_DELBLK_RESP 0x80000003 解除黑名单应答
+ */
+public enum CommandId {
+    SMIT_BIND(1L), SMIT_BIND_RESP(2147483649L),
+    SMIT_ACTIVE_TEST(15L), SMIT_ACTIVE_TEST_RESP(2147483663L),
+    SMIT_DELBLK(3L), SMIT_DELBLK_RESP(2147483651L);
+
+    public final long value;
+
+    CommandId(long value) {
+        this.value = value;
+    }
+
+    public static CommandId parse(long value) {
+        for (CommandId commandId : values()) {
+            if (value == commandId.value) {
+                return commandId;
+            }
+        }
+        return null;
+    }
+}

+ 27 - 0
sms_blk_server/src/main/java/com/nokia/sms/entity/DelBlkBody.java

@@ -0,0 +1,27 @@
+package com.nokia.sms.entity;
+
+import lombok.Data;
+
+/*
+ * 解除黑名单请求的body
+ */
+@Data
+public class DelBlkBody {
+    // blk_Num 21 C-Octet String 否 不带86
+    private String blkNum;
+    // block_type 1 Unsigned Integer 否 1:黑名单号码 2:黑名单号段
+    private byte block_type = 1;
+    // blk_type 1 Unsigned Integer 否 1:主叫黑名单 固定值
+    private byte blk_type = 1;
+    // province_code 1 Unsigned Integer 否 黑名单的省份代码,参见附录B
+    // 河北省对应code为14,这里是黑名单省份,与号码归属无关
+    private byte province_code = 14;
+
+    public DelBlkBody(String phoneNumber) {
+        blkNum = phoneNumber;
+    }
+
+    public DelBlkBody() {
+        
+    }
+}

+ 26 - 0
sms_blk_server/src/main/java/com/nokia/sms/entity/DelBlkRespStatus.java

@@ -0,0 +1,26 @@
+package com.nokia.sms.entity;
+
+/*
+ * Status	1	Unsigned Integer	否	状态 0:成功 1:失败
+ */
+public enum DelBlkRespStatus {
+    SUCCESS((byte) 0, "成功"),
+    FAILED((byte) 1, "失败");
+
+    public final byte value;
+    public final String status;
+
+    DelBlkRespStatus(byte value, String status) {
+        this.value = value;
+        this.status = status;
+    }
+
+    public static DelBlkRespStatus parse(byte value) {
+        for (DelBlkRespStatus status : values()) {
+            if (value == status.value) {
+                return status;
+            }
+        }
+        return null;
+    }
+}

+ 8 - 0
sms_blk_server/src/main/java/com/nokia/sms/exception/ConnectFailedException.java

@@ -0,0 +1,8 @@
+package com.nokia.sms.exception;
+
+public class ConnectFailedException extends Throwable {
+    
+    public ConnectFailedException(String message) {
+        super(message);
+    }
+}

+ 5 - 0
sms_blk_server/src/main/java/com/nokia/sms/exception/ConnectionOutOfSyncException.java

@@ -0,0 +1,5 @@
+package com.nokia.sms.exception;
+
+public class ConnectionOutOfSyncException extends Throwable {
+
+}

+ 7 - 0
sms_blk_server/src/main/java/com/nokia/sms/exception/ParseException.java

@@ -0,0 +1,7 @@
+package com.nokia.sms.exception;
+
+public class ParseException extends Throwable {
+    public ParseException(String message) {
+        super(message);
+    }
+}

+ 5 - 0
sms_blk_server/src/main/java/com/nokia/sms/exception/PhoneNumberCanNotBeNullException.java

@@ -0,0 +1,5 @@
+package com.nokia.sms.exception;
+
+public class PhoneNumberCanNotBeNullException extends Throwable {
+    
+}

+ 64 - 0
sms_blk_server/src/main/java/com/nokia/sms/message/MessageHeader.java

@@ -0,0 +1,64 @@
+package com.nokia.sms.message;
+
+import java.util.Arrays;
+
+import com.nokia.common.io.ByteArrayUtil;
+import com.nokia.sms.entity.CommandId;
+import com.nokia.sms.exception.ParseException;
+
+import lombok.Data;
+
+/**
+ * Message Length 4 Unsigned Integer 消息长度:消息包的总长度(字节)
+ * Command ID 4 Unsigned Integer 消息类型:表明此条消息的类型,采用固定值
+ * Sequence Number 4 Unsigned Integer
+ * 此字段表示消息的序列号,它由请求发起端产生,是消息和它的应答之间的对应标志。数值在0到0XFFFFFFFF间必须保证严格单调的递增,当达到0XFFFFFFFF时,从0开始下一循环。
+ */
+@Data
+public class MessageHeader {
+    // 协议为无符号int 但是实际情况都不超过int
+    private int messageLength;
+    /**
+     * Command_Id定义
+     * SMIT_BIND 0x00000001 绑定请求
+     * SMIT_BIND_RESP 0x80000001 绑定应答
+     * SMIT_ACTIVE_TEST 0x0000000f 链路检测
+     * SMIT_ACTIVE_TEST_RESP 0x8000000f 链路检测应答
+     * SMIT_DELBLK 0x00000003 解除黑名单请求
+     * SMIT_DELBLK_RESP 0x80000003 解除黑名单应答
+     */
+    private CommandId commandId;
+    private long sequenceNumber;
+
+    public byte[] toByteArray() {
+        byte[] header = new byte[12];
+        // messageLength
+        System.arraycopy(ByteArrayUtil.toByteArray(messageLength), 0, header, 0, 4);
+        // commandId
+        System.arraycopy(ByteArrayUtil.toByteArray(commandId.value), 4, header, 4, 4);
+        // sequenceNumber
+        System.arraycopy(ByteArrayUtil.toByteArray(sequenceNumber), 4, header, 8, 4);
+        return header;
+    }
+
+    public static MessageHeader parse(byte[] message) throws ParseException {
+        if (message.length < 12) {
+            throw new ParseException("message header 必须包含12个字节。");
+        }
+        // 获取前12个字节作为header
+        byte[] header = Arrays.copyOf(message, 12);
+        MessageHeader messageHeader = new MessageHeader();
+        // messageLength
+        messageHeader.setMessageLength(ByteArrayUtil.parseInteger(header, 0));
+        // commandId
+        long commandIdValue = ByteArrayUtil.parseUnsignedInteger(header, 4);
+        CommandId commandId = CommandId.parse(commandIdValue);
+        if (null == commandId) {
+            throw new ParseException("无法识别 Command_Id : " + commandIdValue);
+        }
+        messageHeader.setCommandId(commandId);
+        // sequenceNumber
+        messageHeader.setSequenceNumber(ByteArrayUtil.parseUnsignedInteger(header, 8));
+        return messageHeader;
+    }
+}

+ 139 - 0
sms_blk_server/src/main/java/com/nokia/sms/message/ServerMessageUtil.java

@@ -0,0 +1,139 @@
+package com.nokia.sms.message;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Date;
+
+import com.nokia.sms.entity.BindRespStatus;
+import com.nokia.sms.entity.CommandId;
+import com.nokia.sms.exception.ParseException;
+
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class ServerMessageUtil {
+    private static final String systemId = "smsTousu";
+    private static final String password = "123456";
+    private static final DateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");
+
+    /*
+     * SMIT_BIND_RESP 0x80000001 绑定应答
+     */
+    public static byte[] getSmitBindRespMessage(BindRespStatus status) {
+        // 初始化header
+        MessageHeader header = new MessageHeader();
+        // header 12 + body 1 所以是13字节
+        header.setMessageLength(13);
+        header.setCommandId(CommandId.SMIT_BIND_RESP);
+        // 所有的初始连接需要从0开始
+        header.setSequenceNumber(0L);
+        // 初始化
+        byte[] message = new byte[header.getMessageLength()];
+        // 填充header
+        System.arraycopy(header.toByteArray(), 0, message, 0, 12);
+
+        // 填充status
+        message[12] = status.value;
+        return message;
+    }
+
+    /*
+     * SMIT_ACTIVE_TEST_RESP 0x8000000f 链路检测应答
+     */
+    public static byte[] getSmitActiveTestRespMessage(long sequenceNumber) {
+        // 初始化header
+        MessageHeader header = new MessageHeader();
+        // header 12 + body 0 所以是12字节
+        header.setMessageLength(12);
+        header.setCommandId(CommandId.SMIT_ACTIVE_TEST_RESP);
+        // 从客户端获取下一个序号
+        header.setSequenceNumber(sequenceNumber);
+        // 初始化
+        byte[] message = new byte[header.getMessageLength()];
+        // 填充header
+        System.arraycopy(header.toByteArray(), 0, message, 0, 12);
+        return message;
+    }
+
+    /*
+     * SMIT_DELBLK_RESP 0x80000003 解除黑名单应答
+     */
+    public static byte[] getSmitDelBlkRespMessage(long sequenceNumber) {
+        // 初始化header
+        MessageHeader header = new MessageHeader();
+        // header 12 + body 0 所以是12字节
+        header.setMessageLength(13);
+        header.setCommandId(CommandId.SMIT_DELBLK_RESP);
+        // 从客户端获取下一个序号
+        header.setSequenceNumber(sequenceNumber);
+        // 初始化
+        byte[] message = new byte[header.getMessageLength()];
+        // 填充header
+        System.arraycopy(header.toByteArray(), 0, message, 0, 12);
+        // 填充header
+        message[12] = 0;
+        return message;
+    }
+
+    /*
+     * 服务端解析
+     */
+    public static byte[] parse(byte[] message) {
+        try {
+            MessageHeader header = MessageHeader.parse(message);
+            log.debug("消息被服务端解析为 {} 消息, 序列号 {}", header.getCommandId(), header.getSequenceNumber());
+            // 判断消息类型
+            switch (header.getCommandId()) {
+                case SMIT_ACTIVE_TEST:
+                    // 链路检测消息返回 sequenceNumber
+                    return getSmitActiveTestRespMessage(header.getSequenceNumber());
+                case SMIT_DELBLK:
+                    // 解除黑名单消息 返回成功
+                    return getSmitDelBlkRespMessage(header.getSequenceNumber());
+                case SMIT_BIND:
+                    // 绑定请求消息 返回成功
+                    return getSmitBindRespMessage(parseSmitBindMessage(message));
+                default:
+                    throw new ParseException("消息Command_Id错误");
+            }
+        } catch (ParseException e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    /*
+     * SMIT_BIND 0x00000001 绑定请求
+     */
+    public static BindRespStatus parseSmitBindMessage(byte[] message) {
+        try {
+            MessageHeader header = MessageHeader.parse(message);
+            // 判断消息类型
+            if (CommandId.SMIT_BIND.equals(header.getCommandId())) {
+                // 获取并对比systemId
+                byte[] systemIdByteArray = Arrays.copyOfRange(message, 12, 28);
+                if (systemId.equals(new String(systemIdByteArray).trim())) {
+                    // 获取和判断鉴权信息
+                    String authCodeString = password + dateFormat.format(new Date());
+                    MessageDigest digest = MessageDigest.getInstance("SHA-1");
+                    // sha1后的本地字符串
+                    String authCodeLocal = new String(digest.digest(authCodeString.getBytes()));
+                    // 获取到的authCode
+                    String authCode = new String(Arrays.copyOfRange(message, 28, 48));
+                    if (authCodeLocal.equals(authCode)) {
+                        return BindRespStatus.SUCCESS;
+                    }
+                }
+                return BindRespStatus.AUTH_ERROR;
+            }
+        } catch (ParseException e) {
+            e.printStackTrace();
+        } catch (NoSuchAlgorithmException e) {
+            e.printStackTrace();
+        }
+        return BindRespStatus.STRUCTURE_ERROR;
+    }
+}