public VCodeCheckResponseModel VCodeCheck(VerifyInfoModel verifyInfo, string userIp)
        {
            VCodeCheckResponseModel rtnResult = new VCodeCheckResponseModel();
            // 允许的偏移量(点触容错)
            int allowOffset = 10;

            #region 尝试从内存中取出对应的 VCodeKey
            // 获取此用户会话的验证码效验 vCodeKey
            string cacheKeyVCodeKey = CachePrefixVCodeKey + verifyInfo.UserId;
            if (!_cacheHelper.Exists(cacheKeyVCodeKey))
            {
                // 验证码无效,1.此验证码已被销毁
                rtnResult = new VCodeCheckResponseModel {
                    code = -5, message = "验证码过期, 获取新验证码"
                };
                return(rtnResult);
            }
            string rightVCodeKey = _cacheHelper.Get <string>(cacheKeyVCodeKey);
            // AES解密
            string vCodeKeyJsonStr = AesHelper.DecryptEcbMode(rightVCodeKey, _options.AesKey);
            // json -> 对象
            VCodeKeyModel vCodeKeyModel = null;
            try
            {
                // TODO: fixed: 临时修复, 直接将全部为0的字节去除,
                byte[] bytes          = Encoding.UTF8.GetBytes(vCodeKeyJsonStr);
                byte[] remove0Bytes   = bytes.Where(m => m != 0).ToArray();
                string remove0ByteStr = Encoding.UTF8.GetString(remove0Bytes);

                // 能够转换为 对象, 则说明 vCodeKey 无误, 可以使用
                //vCodeKeyModel = JsonHelper.Deserialize<VCodeKeyModel>(vCodeKeyJsonStr);
                vCodeKeyModel = JsonHelper.Deserialize <VCodeKeyModel>(remove0ByteStr);
            }
            catch (Exception ex)
            {
                // TODO: BUG: 经加密再解密后的jsonStr,虽然看起来一样,但发生了一点改变, 导致无法转换
                // '0x00' is invalid after a single JSON value. Expected end of data. LineNumber: 0 | BytePositionInLine: 110.
            }
            if (vCodeKeyModel == null)
            {
                // 验证码无效,被篡改导致解密失败
                rtnResult.code    = -3;
                rtnResult.message = "验证码无效, 获取新验证码";
                return(rtnResult);
            }
            #endregion

            #region 验证码是否过期
            // 验证码是否过期
            bool isExpired = ((DateTimeHelper.NowTimeStamp13() - vCodeKeyModel.TS) / 1000) > _options.ExpiredSec;
            if (isExpired)
            {
                // 验证码过期
                rtnResult.code    = -4;
                rtnResult.message = "验证码过期, 获取新验证码";
                RemoveCacheVCodeKey(verifyInfo.UserId);
                return(rtnResult);
            }
            #endregion

            #region 效验点触位置数据
            // 效验点触位置数据
            IList <PointPosModel> rightVCodePos = vCodeKeyModel.VCodePos;
            IList <PointPosModel> userVCodePos  = verifyInfo.VCodePos;
            // 验证码是否正确
            bool isPass = false;
            if (userVCodePos.Count != rightVCodePos.Count)
            {
                // 验证不通过
                isPass = false;
            }
            else
            {
                isPass = true;
                for (int i = 0; i < userVCodePos.Count; i++)
                {
                    int xOffset = userVCodePos[i].X - rightVCodePos[i].X;
                    int yOffset = userVCodePos[i].Y - rightVCodePos[i].Y;
                    // x轴偏移量
                    xOffset = Math.Abs(xOffset);
                    // y轴偏移量
                    yOffset = Math.Abs(yOffset);
                    // 只要有一个点的任意一个轴偏移量大于allowOffset,则验证不通过
                    if (xOffset > allowOffset || yOffset > allowOffset)
                    {
                        isPass = false;
                    }
                }
            }

            #endregion

            #region 未通过->错误次数达到上限?
            if (!isPass)
            {
                // 本次没通过验证 -> 错误次数+1
                vCodeKeyModel.ErrorNum++;
                // 错误次数是否达上限
                bool isMoreThanErrorNum = vCodeKeyModel.ErrorNum > _options.AllowErrorNum;
                if (isMoreThanErrorNum)
                {
                    // 错误 -> 2.code:-2 验证码错误 且 错误次数已达上限 -> message: 这题有点难,为你换一个试试吧
                    rtnResult.code    = -2;
                    rtnResult.message = "这题有点难, 为你换一个试试吧";
                    RemoveCacheVCodeKey(verifyInfo.UserId);
                    return(rtnResult);
                }
                else
                {
                    // 错误 -> 1.code:-1 验证码错误 且 错误次数未达上限 -> message: 点错啦,请重试
                    string vCodekeyJsonStrTemp = JsonHelper.Serialize(vCodeKeyModel);
                    // AES加密 vCodekeyJsonStrTemp
                    string vCodeKeyStrTemp = AesHelper.EncryptEcbMode(vCodekeyJsonStrTemp, _options.AesKey);
                    // 更新 Cache 中的 vCodeKey
                    _cacheHelper.Insert <string>(CachePrefixVCodeKey + verifyInfo.UserId, vCodeKeyStrTemp);

                    rtnResult.code    = -1;
                    rtnResult.message = "点错啦,请重试";
                    return(rtnResult);
                }
            }
            #endregion

            #region 验证通过->下发ticket
            // 正确 -> code:0 下发票据 ticket
            TicketModel ticketModel = new TicketModel {
                IP = userIp, IsPass = true, TS = DateTimeHelper.NowTimeStamp13()
            };
            string ticketJsonStr = JsonHelper.Serialize(ticketModel);
            // 对 ticketJsonStr 加密
            string ticket = AesHelper.EncryptEcbMode(ticketJsonStr, _options.AesKey);
            // 内存中存一份ticket, 用于效验
            _cacheHelper.Insert <string>(CachePrefixTicket + verifyInfo.UserId, ticket);

            rtnResult.code    = 0;
            rtnResult.message = "验证通过";
            rtnResult.data    = new VCodeCheckResponseModel.DataModel {
                appId = verifyInfo.AppId, ticket = ticket
            };
            return(rtnResult);

            #endregion
        }
        /// <summary>
        /// ticket效验
        /// </summary>
        /// <param name="appId"></param>
        /// <param name="appSecret"></param>
        /// <param name="ticket"></param>
        /// <param name="userId">用户唯一标识</param>
        /// <param name="userIp"></param>
        /// <param name="aesKey"></param>
        /// <returns></returns>
        public TicketVerifyResponseModel TicketVerify(string appId, string appSecret, string ticket, string userId, string userIp)
        {
            TicketVerifyResponseModel rtnResult = null;
            // 解密ticket -> 转为实体对象
            TicketModel ticketModel = null;

            try
            {
                string ticketJsonStr = AesHelper.DecryptEcbMode(ticket, _options.AesKey);

                // TODO: fixed: 临时修复, 直接将全部为0的字节去除,
                byte[] bytes          = Encoding.UTF8.GetBytes(ticketJsonStr);
                byte[] remove0Bytes   = bytes.Where(m => m != 0).ToArray();
                string remove0ByteStr = Encoding.UTF8.GetString(remove0Bytes);

                // 能够转换为 对象, 则说明 vCodeKey 无误, 可以使用
                ticketModel = JsonHelper.Deserialize <TicketModel>(remove0ByteStr);

                //ticketModel = JsonHelper.Deserialize<TicketModel>(ticketJsonStr);
            }
            catch (Exception ex)
            {
                // TODO: AES加解密后多出0, 导致无法转为json对象, 和验证码效验时一样
                // '0x00' is invalid after a single JSON value. Expected end of data. LineNumber: 0 | BytePositionInLine: 110.
            }
            if (ticketModel == null)
            {
                // ticket无效,被篡改
                rtnResult = new TicketVerifyResponseModel {
                    code = -4, message = "ticket无效"
                };
                return(rtnResult);
            }
            // 从内存中取出此用户会话保存的独有Ticket,进行比对
            string cacheKeyTicket = CachePrefixTicket + userId;

            if (!_cacheHelper.Exists(cacheKeyTicket))
            {
                // ticket无效,1.此ticket已被效验过一次,用完销毁 2.其它原因: 伪造ticket
                rtnResult = new TicketVerifyResponseModel {
                    code = -5, message = "ticket无效"
                };
                return(rtnResult);
            }
            string rightTicket = _cacheHelper.Get(cacheKeyTicket).ToString();

            if (ticket != rightTicket)
            {
                // ticket无效,1.篡改ticket
                rtnResult = new TicketVerifyResponseModel {
                    code = -6, message = "ticket无效"
                };
                RemoveCacheTicket(userId);
                return(rtnResult);
            }
            if (!ticketModel.IsPass)
            {
                // ticket 标识 验证不通过
                rtnResult = new TicketVerifyResponseModel {
                    code = -1, message = "验证不通过"
                };
                RemoveCacheTicket(userId);
                return(rtnResult);
            }
            int secOffset = (int)((DateTimeHelper.NowTimeStamp13() - ticketModel.TS) / 1000);

            if (secOffset > _options.ExpiredSec)
            {
                // ticket 已过期
                rtnResult = new TicketVerifyResponseModel {
                    code = -2, message = "ticket过期"
                };
                RemoveCacheTicket(userId);
                return(rtnResult);
            }
            if (ticketModel.IP != userIp)
            {
                // ip不匹配
                rtnResult = new TicketVerifyResponseModel {
                    code = -3, message = "ip不匹配"
                };
                RemoveCacheTicket(userId);
                return(rtnResult);
            }
            // 验证通过
            rtnResult = new TicketVerifyResponseModel {
                code = 0, message = "验证通过"
            };
            RemoveCacheTicket(userId);

            return(rtnResult);
        }