Przeglądaj źródła

行情订阅部分代码

zhou.xiaoning 4 lat temu
rodzic
commit
d0ca0f8528

+ 12 - 3
RMA/.idea/codeStyles/Project.xml

@@ -3,9 +3,18 @@
     <JetCodeStyleSettings>
       <option name="PACKAGES_TO_USE_STAR_IMPORTS">
         <value>
-          <package name="java.util" withSubpackages="false" static="false" />
-          <package name="kotlinx.android.synthetic" withSubpackages="true" static="false" />
-          <package name="io.ktor" withSubpackages="true" static="false" />
+          <package name="java.util" alias="false" withSubpackages="false" />
+          <package name="kotlinx.android.synthetic" alias="false" withSubpackages="true" />
+          <package name="io.ktor" alias="false" withSubpackages="true" />
+        </value>
+      </option>
+      <option name="PACKAGES_IMPORT_LAYOUT">
+        <value>
+          <package name="" alias="false" withSubpackages="true" />
+          <package name="java" alias="false" withSubpackages="true" />
+          <package name="javax" alias="false" withSubpackages="true" />
+          <package name="kotlin" alias="false" withSubpackages="true" />
+          <package name="" alias="true" withSubpackages="true" />
         </value>
       </option>
       <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />

+ 161 - 0
RMA/app/src/main/java/cn/muchinfo/rma/business/quote/QuoteManager.kt

@@ -0,0 +1,161 @@
+package cn.muchinfo.rma.business.quote
+
+import cn.muchinfo.rma.business.account.adapter.AccountAdapter
+import cn.muchinfo.rma.business.quote.adapter.QuoteAdapter
+import cn.muchinfo.rma.global.GlobalDataCollection
+import cn.muchinfo.rma.netcore.packet.Packet40
+import cn.muchinfo.rma.netcore.packet.Packet50
+import cn.muchinfo.rma.netcore.socket.Callback
+import cn.muchinfo.rma.protobuf.classNumber.ClassNumber
+import cn.muchinfo.rma.view.MyApplication
+import cn.muchinfo.rma.view.autoWidget.guard
+import java.util.concurrent.ConcurrentHashMap
+import java.util.concurrent.ConcurrentSkipListSet
+
+/**
+ * 行情管理类
+ */
+class QuoteManager {
+    /** 当前已订阅行情商品列表 */
+    private var currentGoodsSet = ConcurrentSkipListSet<String>()
+
+    /** 订阅Map */
+    private var subcriptMap = ConcurrentHashMap<String, ConcurrentSkipListSet<String>>()
+
+    /**
+     * 按需订阅行情添加订阅商品
+     * @param tag String 订阅方标志,一般使用UUID生成的唯一码
+     * @param goodsCodeSet ConcurrentSkipListSet<String> 待订阅商品代码列表
+     * @param callback Function2<[@kotlin.ParameterName] Boolean, [@kotlin.ParameterName] Error?, Unit>? 回调
+     */
+    fun addSubscriptQuote(
+        tag: String,
+        goodsCodeSet: ConcurrentSkipListSet<String>,
+        callback: ((isCompleted: Boolean, err: Error?) -> Unit)?
+    ) {
+        val app = MyApplication.getInstance().guard {
+            if (callback != null) {
+                callback(false, Error("Application未初始化"))
+            }
+            return
+        }
+        val quoteSocketManager = app.quoteSocketManager.guard {
+            if (callback != null) {
+                callback(false, Error("行情链路未初始化"))
+            }
+            return
+        }
+        // 判断当前是否已经连接行情链路
+        if (quoteSocketManager.connState != 2) {
+            if (callback != null) {
+                callback(false, Error("行情链路未连接"))
+            }
+            return
+        }
+
+        // 添加到当前订阅列表中
+        subcriptMap[tag] = goodsCodeSet
+        val addSet = HashSet<String>()
+        for (item in goodsCodeSet) {
+            if (item !in currentGoodsSet) {
+                addSet.add(item)
+            }
+        }
+        // 判断是否要重新订阅
+        if (addSet.count() > 0) {
+            currentGoodsSet.addAll(addSet)
+            // 重新订阅行情
+            subscriptQuote(callback)
+        }
+    }
+
+    /**
+     * 按需订阅行情移除订阅商品
+     * @param tag String 订阅方标志,一般使用UUID生成的唯一码
+     */
+    fun removeSubscriptQuote(tag: String) {
+        val app = MyApplication.getInstance().guard {
+            return
+        }
+        val quoteSocketManager = app.quoteSocketManager.guard {
+            return
+        }
+        // 判断当前是否已经连接行情链路
+        if (quoteSocketManager.connState != 2) {
+            return
+        }
+
+        if (subcriptMap.contains(tag)) {
+            subcriptMap.remove(tag)
+            // 构建当前需订阅的商品列表
+            val needSet = HashSet<String>()
+            for (items in subcriptMap.values) {
+                for (goodsCode in items) {
+                    if (goodsCode !in needSet) {
+                        needSet.add(goodsCode)
+                    }
+                }
+            }
+            // 判断与当前订阅列表是否一样
+            if (currentGoodsSet != needSet) {
+                currentGoodsSet.clear()
+                currentGoodsSet.addAll(needSet)
+
+                // 重新订阅行情
+                subscriptQuote(null)
+            }
+        }
+    }
+
+    /**
+     * 订阅商品实时行情
+     * @param callback Function2<[@kotlin.ParameterName] Boolean, [@kotlin.ParameterName] Error?, Unit>?
+     */
+    private fun subscriptQuote(callback: ((isCompleted: Boolean, err: Error?) -> Unit)?) {
+        // 当前是否已经登陆
+        val loginRsp = GlobalDataCollection.instance?.loginRsp.guard {
+            if (callback != null) {
+                callback(false, Error("当前未登录"))
+            }
+            return
+        }
+
+        val reqPacket = QuoteAdapter.buildSubscriptPacket(
+            currentGoodsSet.toSet(),
+            loginRsp.token,
+            loginRsp.loginID.toInt()
+        )
+        // 发送订阅请求
+        val quoteSocketManager = MyApplication.getInstance()?.quoteSocketManager
+        quoteSocketManager?.send(
+            reqPacket,
+            ClassNumber.MainClassNumber_Quota_SubscriptRsp,
+            object :
+                Callback<Packet40> {
+                override fun onSuccess(rsp: Packet40?) {
+                    val rst = QuoteAdapter.analySubscriptRsp(rsp!!)
+                    if (!rst.first) {
+                        // 订阅失败
+                        if (callback != null) {
+                            callback(false, Error("行情订阅失败"))
+                        }
+                    }
+
+                    // 订阅成功后通知网络开始发送心跳
+                    quoteSocketManager.startBeatTime()
+                    // 通知网络可进行断网重连
+                    quoteSocketManager.canReconnect = true
+
+                    if (callback != null) {
+                        callback(true, null)
+                    }
+                }
+
+                override fun onFail(err: java.lang.Error?) {
+                    if (callback != null) {
+                        callback(false, err)
+                    }
+                }
+            })
+    }
+}

+ 92 - 0
RMA/app/src/main/java/cn/muchinfo/rma/business/quote/adapter/QuoteAdapter.kt

@@ -0,0 +1,92 @@
+package cn.muchinfo.rma.business.quote.adapter
+
+import cn.muchinfo.rma.business.quote.models.MoQuotaSubscriptReq
+import cn.muchinfo.rma.business.quote.models.MoQuoteSubscriptGoodsInfo
+import cn.muchinfo.rma.netcore.packet.Packet40
+import cn.muchinfo.rma.protobuf.classNumber.ClassNumber
+import java.lang.Exception
+import java.nio.ByteBuffer
+import java.nio.ByteOrder
+
+/**
+ * 行情数据解析类
+ */
+class QuoteAdapter {
+    companion object {
+        /**
+         * 构建行情订阅请求包
+         * @param goodsCodes HashSet<String> 待订阅商品集合
+         * @param token String 登录令牌
+         * @param loginID Int 账户编号
+         * @return Packet40 行情订阅请求包
+         */
+        fun buildSubscriptPacket(
+            goodsCodes: Set<String>,
+            token: String,
+            loginID: Int
+        ): Packet40 {
+            // 订阅商品列表
+            val quoteSubscriptGoodsIfs: ArrayList<MoQuoteSubscriptGoodsInfo> =
+                ArrayList()
+            for (goodsCode in goodsCodes) {
+                quoteSubscriptGoodsIfs.add(MoQuoteSubscriptGoodsInfo(goodsCode = goodsCode))
+            }
+
+            // 订阅请求对象
+            val moQuoteSubscriptReq = MoQuotaSubscriptReq(
+                accountID = loginID,
+                token = token,
+                count = quoteSubscriptGoodsIfs.size,
+                quoteSubscriptGoodsInfo = quoteSubscriptGoodsIfs
+            )
+
+            return Packet40(
+                ClassNumber.MainClassNumber_Quota_SubscriptReq.toByte(),
+                0,
+                moQuoteSubscriptReq.getData()
+            )
+        }
+
+        /**
+         * 解析实时行情订阅回复报文的方法
+         * @param packet Packet40 实时行情订阅回复报文
+         * @return Triple<Boolean, Error?, List<MoQuoteSubscriptGoodsInfo>?>
+         */
+        fun analySubscriptRsp(packet: Packet40): Triple<Boolean, Error?, List<MoQuoteSubscriptGoodsInfo>?> {
+            if (packet.content.isEmpty()) {
+                // 实时行情订阅回复报文错误
+                return Triple(false, Error("实时行情订阅回复报文错误"), null)
+            }
+
+            // 判断订阅是否成功
+            val count = ByteBuffer.wrap(packet.content.sliceArray(0..4), 1, 4)
+                .order(ByteOrder.BIG_ENDIAN).int
+            if (count == -1 || count == -2 || count == -3) {
+                // 订阅失败, Token校验失败, 无对应商品信息
+                return Triple(false, Error("订阅失败"), null)
+            }
+
+            // 判断返回的Count是否有问题
+            if (count > (packet.content.size - 4) / 66) {
+                return Triple(false, Error("订阅返回数量错误"), null)
+            }
+
+            // 获取商品订阅信息
+            val quoteSubscriptGoodsInfos: ArrayList<MoQuoteSubscriptGoodsInfo> = ArrayList()
+            try {
+                for (i in 4..packet.content.size step 66) {
+                    quoteSubscriptGoodsInfos.add(
+                        MoQuoteSubscriptGoodsInfo(
+                            data = packet.content.sliceArray(
+                                i..i + 66
+                            )
+                        )
+                    )
+                }
+            } catch (e: Exception) {
+            }
+
+            return Triple(true, null, quoteSubscriptGoodsInfos)
+        }
+    }
+}

+ 45 - 0
RMA/app/src/main/java/cn/muchinfo/rma/business/quote/models/MoQuotaSubscriptReq.kt

@@ -0,0 +1,45 @@
+package cn.muchinfo.rma.business.quote.models
+
+import java.nio.ByteBuffer
+import java.nio.ByteOrder
+
+/**
+ * 行情订阅请求包数据体模型类
+ * @property accountID Int 账户编号,8 Byte
+ * @property token String 登录令牌,64 Byte
+ * @property count Int 4 Byte,有符号
+ * @property quoteSubscriptGoodsInfo List<MoQuoteSubscriptGoodsInfo> 行情订阅请求包商品信息数组
+ * @constructor
+ */
+class MoQuotaSubscriptReq(
+    var accountID: Int = 0,
+    var token: String = "",
+    var count: Int = 0,
+    var quoteSubscriptGoodsInfo: List<MoQuoteSubscriptGoodsInfo> = ArrayList<MoQuoteSubscriptGoodsInfo>()
+) {
+    /**
+     * 获取订阅请求对象二进制流的方法
+     * @return ByteArray 订阅请求对象二进制流
+     */
+    fun getData(): ByteArray {
+        var data = byteArrayOf()
+
+        // accountID
+        data += ByteBuffer.allocate(8).order(ByteOrder.BIG_ENDIAN).putInt(accountID).array()
+        // Token, 不足64 byte需要补足
+        val tmp = token.toByteArray()
+        data += tmp
+        val size = 64 - tmp.size
+        for (i in 1..size) {
+            data += 0
+        }
+        // count
+        data += ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).putInt(count).array()
+        // 订阅商品信息
+        for (item in quoteSubscriptGoodsInfo) {
+            data += item.getData()
+        }
+
+        return data
+    }
+}

+ 44 - 0
RMA/app/src/main/java/cn/muchinfo/rma/business/quote/models/MoQuoteSubscriptGoodsInfo.kt

@@ -0,0 +1,44 @@
+package cn.muchinfo.rma.business.quote.models
+
+import java.nio.charset.Charset
+
+/**
+ * 行情订阅商品信息模型类
+ * @property subState Int 订阅成功标志;1为成功,0为失败,订阅的时候填0
+ * @property exchangeCode Int 交易所代码, 目前2.0固定为250
+ * @property goodsCode String 商品代码,64 Byte,不足位使用'\0'补齐
+ * @constructor
+ */
+class MoQuoteSubscriptGoodsInfo(
+    var subState: Int = 0,
+    var exchangeCode: Int = 250,
+    var goodsCode: String = ""
+) {
+    constructor(data: ByteArray) : this() {
+        if (data.size != 66) {
+            throw Exception("错误的数据长度")
+        }
+
+        subState = data[0].toInt()
+        exchangeCode = data[1].toInt()
+        goodsCode = String(data.sliceArray(2..66), Charsets.UTF_8).replace("""\0""", "")
+    }
+
+    /**
+     * 获取行情订阅商品信息对象二进制流
+     * @return ByteArray 行情订阅商品信息对象二进制流
+     */
+    fun getData(): ByteArray {
+        var data = byteArrayOf(subState.toByte(), exchangeCode.toByte())
+
+        // 商品代码需要64 Byte,不足补'\0'
+        val tmp = goodsCode.toByteArray()
+        data += tmp
+        val size = 64 - tmp.size
+        for (i in 1..size) {
+            data += 0
+        }
+
+        return data
+    }
+}

+ 34 - 0
RMA/app/src/main/java/cn/muchinfo/rma/protobuf/classNumber/ClassNumber.java

@@ -0,0 +1,34 @@
+package cn.muchinfo.rma.protobuf.classNumber;
+
+public class ClassNumber {
+    /// 心跳
+    public static final int MainClassNumber_Quota_Beat = 0x12;
+    /// 实时行情订阅请求
+    public static final int MainClassNumber_Quota_SubscriptReq = 0x20;
+    /// 实时行情订阅响应
+    public static final int MainClassNumber_Quota_SubscriptRsp = 0x21;
+    /// 盘面查询请求
+    public static final int MainClassNumber_Quota_QuotationReq = 0x22;
+    /// 盘面查询应答
+    public static final int MainClassNumber_Quota_QuotationRsp = 0x23;
+    /// 历史数据查询请求(1.0带盘面不带结算价)主要用于非日线的解析
+    public static final int MainClassNumber_Quota_HistoryReq = 0x30;
+    /// 历史数据查询应答(1.0带盘面不带结算价)
+    public static final int MainClassNumber_Quota_HistoryRsp = 0x31;
+    /// 历史数据查询请求, 日线专用, 带当日周期数据
+    public static final int MainClassNumber_Quota_HistoryReq_Day = 0x46;
+    /// 历史数据查询应答, 日线专用
+    public static final int MainClassNumber_Quota_HistoryRsp_Day = 0x47;
+    /// 历史数据查询请求(1.0带盘面并带结算价),主要用于日线的解析
+    public static final int MainClassNumber_Quota_HistoryReq_MinutesDay = 0x38;
+    /// 历史数据查询应答(1.0带盘面并带结算价)
+    public static final int MainClassNumber_Quota_HistoryRsp_MinutesDay = 0x39;
+    /// 历史数据查询请求(2.0), 日线专用, 不带当日周期数据
+    public static final int MainClassNumber_Quota_HistoryReq_Day_NoToDay = 0x46;
+    /// 历史数据查询应答(2.0)
+    public static final int MainClassNumber_Quota_HistoryRsp_Day_NoToDay = 0x47;
+    /// 实时行情推送
+    public static final int MainClassNumber_Quota_QuotaPush = 0x41;
+    /// 控制信号
+    public static final int MainClassNumber_Quota_Control = 0x42;
+}