开源模型应用落地-业务整合篇(四)

一、前言

    通过学习第三篇文章,我们已经成功地建立了IM与AI服务之间的数据链路。然而,我们目前面临一个紧迫需要解决的安全性问题,即非法用户可能会通过获取WebSocket的连接信息,顺利地连接到我们的服务。这不仅占用了大量的无效连接和资源,还对业务数据带来了潜在的风险。因此,我们需要逐步完善这个安全问题。


二、术语

2.1. 多设备登录

    是指在一个应用或平台上使用多个设备进行登录和访问。传统上,用户只能使用单个设备(如个人电脑或手机)登录到应用程序,但随着技术的发展,许多应用和平台开始支持多设备登录功能。

2.2. 黑名单

    是一种记录被列入不受欢迎或禁止的个人、组织、IP地址或其他实体的列表。在各种环境中,黑名单用于限制或阻止对特定实体的访问、参与或特权。


三、前置条件

3.1. 调通IM与AI服务的数据链路(参见开源模型应用落地-业务整合篇(三))

3.2. 了解Netty的基本使用


四、技术实现

4.1. 业务流程

# 上游服务(即ws的客户福安)先发送MsgType为2消息,进行全局初始化,示例:

{"userId":12345,"msgType":2}

# 认证通过后,再进行业务对话,示例:

{"userId":12345,"msgType":1,"contents":"你好","history":[]}

4.2. 消息类型枚举类增加初始化类型

import lombok.Getter;

@Getter
public enum MsgType {
	CHAT(1, "聊天消息"),
	INIT(2, "初始化"),
	SYSTEM(9, "系统消息");

	private int code;
	private String desc;

	MsgType(int code, String desc) {
		this.code = code;
		this.desc = desc;
	}

}

4.3. 修改IM的业务逻辑处理类

注释上一篇的代码

增加这一篇的代码

PS: 此处USERID的校验规则应该根据实际业务调整,这里仅简单判断是否小于10000.

import com.alibaba.fastjson.JSON;
import io.netty.channel.ChannelHandler;
import lombok.extern.slf4j.Slf4j;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;


/**
 * @Description: 处理消息的handler
 */
@Slf4j
@ChannelHandler.Sharable
@Component
public class BusinessHandler extends AbstractBusinessLogicHandler<TextWebSocketFrame> {
    @Autowired
    private AIChatUtils aiChatUtils;

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        String channelId = ctx.channel().id().asShortText();
        log.info("add client,channelId:{}", channelId);
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        String channelId = ctx.channel().id().asShortText();
        log.info("remove client,channelId:{}", channelId);
    }


    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, TextWebSocketFrame textWebSocketFrame)
            throws Exception {
        // 获取客户端传输过来的消息
        String content = textWebSocketFrame.text();
        log.info("接收到客户端发送的信息: {}",content);

        Long userIdForReq;
        String msgType = "";
        String contents = "";

        try {
            ApiReqMessage apiReqMessage = JSON.parseObject(content, ApiReqMessage.class);
            msgType = apiReqMessage.getMsgType();
            contents = apiReqMessage.getContents();

            userIdForReq = apiReqMessage.getUserId();
            // 用户身份标识校验
            if((long)userIdForReq <= 10000){
                ApiRespMessage apiRespMessage = ApiRespMessage.builder().code(String.valueOf(StatusCode.SYSTEM_ERROR.getCode()))
                        .respTime(String.valueOf(System.currentTimeMillis()))
                        .contents("用户身份标识有误!")
                        .msgType(String.valueOf(MsgType.SYSTEM.getCode()))
                        .build();
                buildResponseAndClose(channelHandlerContext, apiRespMessage);
                return;
            }

            // 添加用户
//            if(!isExists(userIdForReq)){
//                addChannel(channelHandlerContext, userIdForReq);
//            }

            if(StringUtils.equals(msgType,String.valueOf(MsgType.CHAT.getCode()))){
//                ApiRespMessage apiRespMessage = ApiRespMessage.builder().code(String.valueOf(StatusCode.SUCCESS.getCode()))
//                        .respTime(String.valueOf(System.currentTimeMillis()))
//                        .contents("测试通过,很高兴收到你的信息")
//                        .msgType(String.valueOf(MsgType.CHAT.getCode()))
//                        .build();
//                String response = JSON.toJSONString(apiRespMessage);
//                channelHandlerContext.writeAndFlush(new TextWebSocketFrame(response));
                if(!isExists(userIdForReq)){
                    String respMessage = "用户标识: "+userIdForReq+" 未登录";

                    buildResponse(channelHandlerContext, ApiRespMessage.builder().code(String.valueOf(StatusCode.NO_LOGIN_711.getCode()))
                            .respTime(String.valueOf(System.currentTimeMillis()))
                            .msgType(String.valueOf(MsgType.INIT.getCode()))
                            .contents(respMessage)
                            .build());

                }else{
                    aiChatUtils.chatStream(apiReqMessage);
                }


            }else if(StringUtils.equals(msgType,String.valueOf(MsgType.INIT.getCode()))){
                //一、业务黑名单检测(多次违规,永久锁定)

                //二、账户锁定检测(临时锁定)

                //三、多设备登录检测

                //四、剩余对话次数检测

                //检测通过,绑定用户与channel之间关系
                addChannel(channelHandlerContext, userIdForReq);
                String respMessage = "用户标识: "+userIdForReq+" 登录成功";

                buildResponse(channelHandlerContext, ApiRespMessage.builder().code(String.valueOf(StatusCode.SUCCESS.getCode()))
                        .respTime(String.valueOf(System.currentTimeMillis()))
                        .msgType(String.valueOf(MsgType.INIT.getCode()))
                        .contents(respMessage)
                        .build());

            }else{
                log.info("用户标识: {}, 消息类型有误,不支持类型: {}",userIdForReq,msgType);
            }


        } catch (Exception e) {
            log.warn("【BusinessHandler】接收到请求内容:{},异常信息:{}", content, e.getMessage(), e);
            // 异常返回
            return;
        }

    }

}

4.4. 修改IM的业务逻辑处理抽象类

增加这一篇的代码

import com.alibaba.fastjson.JSON;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.AttributeKey;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.DisposableBean;
import java.util.concurrent.ConcurrentHashMap;


@SuppressWarnings("all")
@Slf4j
public abstract class AbstractBusinessLogicHandler<I> extends SimpleChannelInboundHandler<I> implements DisposableBean {


    protected static final ConcurrentHashMap<Long, ChannelHandlerContext> USER_ID_TO_CHANNEL = new ConcurrentHashMap<>();
    // 用户特征属性与channel绑定
    public static final AttributeKey<Long> USER_ID_ATTRIBUTE_KEY = AttributeKey.valueOf("userId");





    /**
     * 添加socket通道
     *
     * @param channelHandlerContext socket通道上下文
     */
    protected void addChannel(ChannelHandlerContext channelHandlerContext, Long userId) {
        // 将当前通道存放起来
        USER_ID_TO_CHANNEL.put(userId, channelHandlerContext);
        // 记录用户ID
        channelHandlerContext.channel().attr(USER_ID_ATTRIBUTE_KEY).set(userId);
    }

    /**
     * 判斷用戶是否存在
     * @param userId
     * @return
     */
    protected boolean isExists(Long userId){
        return USER_ID_TO_CHANNEL.containsKey(userId);
    }

    protected  void buildResponse(ChannelHandlerContext channelHandlerContext, int code, long respTime, int msgType, String msg) {
        buildResponse(channelHandlerContext, ApiRespMessage.builder().code(String.valueOf(code))
                .respTime(String.valueOf(respTime))
                .msgType(String.valueOf(msgType))
                .contents(msg).build());
    }

    protected  void buildResponseIncludeOperateId(ChannelHandlerContext channelHandlerContext, int code, long respTime, int msgType, String msg, String operateId) {
        buildResponse(channelHandlerContext, ApiRespMessage.builder().code(String.valueOf(code))
                .respTime(String.valueOf(respTime))
                .msgType(String.valueOf(msgType))
                .operateId(operateId)
                .contents(msg).build());
    }




    /**
     * 获取用户ID
     *
     * @param channelHandlerContext socket通道上下文
     */
    protected Long GetUserIdByChannel(ChannelHandlerContext channelHandlerContext) {

        if (channelHandlerContext.channel().hasAttr(USER_ID_ATTRIBUTE_KEY)) {
            Long userId = channelHandlerContext.channel().attr(USER_ID_ATTRIBUTE_KEY).get();
            if (userId == null) {
                return null;
            }
            if (USER_ID_TO_CHANNEL.containsKey(userId)) {
                return userId;
            }
        }
        return null;
    }

    protected void buildResponseAndClose(ChannelHandlerContext channelHandlerContext, ApiRespMessage apiRespMessage) {
        String response = JSON.toJSONString(apiRespMessage);
        Long userId = GetUserIdByChannel(channelHandlerContext);
        if(null == userId){
            log.warn("【AbstractBusinessLogicHandler】关闭通道!响应内容: {}", response);

            ChannelFuture future = channelHandlerContext.writeAndFlush(new TextWebSocketFrame(response));

            future.addListener(new GenericFutureListener<Future<? super Void>>() {
                public void operationComplete(Future future) throws Exception {
                    channelHandlerContext.close();
                }
            });
        }else{
            log.warn("【AbstractBusinessLogicHandler】关闭通道!用户ID:{},响应内容: {}", userId, response);
            ChannelFuture future = channelHandlerContext.writeAndFlush(new TextWebSocketFrame(response));

            future.addListener(new GenericFutureListener<Future<? super Void>>() {
                public void operationComplete(Future future) throws Exception {
                    // 清除离线用户
                    channelHandlerContext.channel().attr(USER_ID_ATTRIBUTE_KEY).remove();
                    channelHandlerContext.close();
                }
            });
        }
    }



    @Override
    public void destroy() throws Exception {
        try {
            USER_ID_TO_CHANNEL.clear();
        } catch (Throwable e) {

        }
    }

    protected static void buildResponse(ChannelHandlerContext channelHandlerContext, ApiRespMessage apiRespMessage) {
        String response = JSON.toJSONString(apiRespMessage);
        channelHandlerContext.writeAndFlush(new TextWebSocketFrame(response));
    }

    public static void pushChatMessageForUser(Long userId,String chatRespMessage) {
        ChannelHandlerContext channelHandlerContext = USER_ID_TO_CHANNEL.get(userId);

        if (channelHandlerContext != null ) {
            buildResponse(channelHandlerContext, ApiRespMessage.builder().code(String.valueOf(StatusCode.SUCCESS.getCode()))
                    .respTime(String.valueOf(System.currentTimeMillis()))
                    .msgType(String.valueOf(MsgType.CHAT.getCode()))
                    .contents(chatRespMessage)
                    .build());
            return;
        }
    }

}


五、测试

5.1. 用户未登录场景测试

# 测试参数:

{"userId":12345,"msgType":1,"contents":"你好","history":[]}

5.2. 用户登录场景测试

# 测试参数:

{"userId":12345,"msgType":2}

# 测试参数:

{"userId":12345,"msgType":1,"contents":"你好","history":[]}


六、附带说明

6.1. 业务黑名单检测

    用户在指定周期内被多次锁定,触发系统阈值,被系统自动或人工拉黑

6.2. 账户锁定检测

    用户在指定周期内多次发起违规对话(例如:涉黄/涉政/血腥/暴恐等),触发系统阈值,被系统自动锁定

6.3. 多设备登录检测

    在业务上只允许单设备在线,但用户在多个设备(例如:手机、平板等)发起登录操作,触发系统阈值

6.4. 剩余对话次数检测

    在业务上,我们限制未付费用户每天只能进行N次对话。

PS:上述内容的完善将放在“业务安全系列”文章中,里面包含算法备案、违规词检测、重新开始新的话题等复杂业务。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/343918.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

jenkins安装配置,使用Docker发布maven项目全过程记录(2)

2、使用Docker发布Maven项目过程的配置 首先说明&#xff0c;在这里仅介绍我使用Jenkins的发布过程的配置&#xff0c;不涉及Dockerfile、docker-compose.yml文件的内容。 2.1 创建Item 在这里&#xff0c;输入item名称&#xff0c;我使用的Freestyle project&#xff0c;点击…

MSP430仿真器使用常见问题

一、 主要是驱动安装问题 有用户反应驱动安装不上&#xff0c;按照用户手册操作一直不能安装成功。 可以尝试如下步骤进行安装。 1. 双击设备管理器中无法安装或者提示有错误的430仿真器设备 选择驱动程序——更新驱动程序 选择手动安装 选择从电脑设备驱动列表中安装 弹出下…

Spring Security 6 学习-1

什么是 Spring Security Spring Security文档 Spring Security中文文档 Spring Security 是 Spring 家族中的安全型开发框架&#xff0c;主要解决三大方面问题&#xff1a;认证&#xff08;你是谁&#xff09;、授权&#xff08;你能干什么&#xff09;、常见攻击保护&#xff…

mysql INSERT数据覆盖现有元素(若存在)

INSERT...ON DUPLICATE KEY UPDATE的使用 如果指定了ON DUPLICATE KEY UPDATE&#xff0c;并且插入行后会导致在一个UNIQUE索引或PRIMARY KEY中出现重复值&#xff0c;则会更新ON DUPLICATE KEY UPDATE关键字后面的字段值。 例如&#xff0c;如果列a被定义为UNIQUE&#xff0…

机器学习实验3——支持向量机分类鸢尾花

文章目录 &#x1f9e1;&#x1f9e1;实验内容&#x1f9e1;&#x1f9e1;&#x1f9e1;&#x1f9e1;数据预处理&#x1f9e1;&#x1f9e1;代码认识数据相关性分析径向可视化各个特征之间的关系图 &#x1f9e1;&#x1f9e1;支持向量机SVM求解&#x1f9e1;&#x1f9e1;直觉…

JavaEE-Nuxt中的vuex

Nuxt中的vuex 参考&#xff1a;https://v2.nuxt.com/docs/directory-structure/store 3.1 根模块数据操作 步骤一&#xff1a;创建 store/index.js 添加一个 counter变量&#xff0c;并可以继续累加操作 export const state () > ({counter: 0 })export const mutations …

用户反映在浏览器中使用AI工具 Copilot 遇到严重卡顿问题,微软官方给出初步解释

近日&#xff0c;多位用户反馈在使用Edge和Chrome浏览器中的Copilot时出现卡顿问题&#xff0c;甚至需要重启浏览器才能解决。对此&#xff0c;微软广告和网络服务部门CEO米哈伊尔帕拉欣表示&#xff0c;问题可能与Edge浏览器的“效率模式”有关。 微软中国官方网址链接&#x…

【GitHub项目推荐--12 年历史的 PDF 工具开源了】【转载】

最近在整理 PDF 的时候&#xff0c;有一些需求普通的 PDF 编辑器没办法满足&#xff0c;比如 PDF 批量合并、编辑等。 于是&#xff0c;我就去 GitHub 上看一看有没有现成的轮子&#xff0c;发现了这个 PDF 神器「PDF 补丁丁」&#xff0c;让人惊讶的是这个 PDF 神器有 12 年的…

C#,计算几何,鼠标点击绘制 (二维,三次)B样条曲线的代码

B样条&#xff08;B-Spline&#xff09;是常用的曲线拟合与插值算法之一。 这里给出在 Form 的 图像 Picturebox 组件上&#xff0c;按鼠标点击点绘制 &#xff08;三次&#xff09;B样条曲线的代码。 2022-12-05 修改了代码。 1 文本格式 using System; using System.Data; …

机器人制作开源方案 | 智能特殊环境清洗机器人

作者&#xff1a;达德聪 袁豪杰 杨垚 单位&#xff1a;邢台学院 指导老师&#xff1a;王承林 杨立芹 智能特殊环境清洗机器人基于STC系列单片机为核心&#xff0c;驱动摄像头模块、超声波模块、ESP8266无线模块、自动寻迹模块、舵机模块、语音识别模块&#xff0c;实现自主寻…

《WebKit 技术内幕》学习之十二(2):安全机制

2 沙箱模型 2.1 原理 一般而言&#xff0c;对于网络上的网页中的JavaScript代码和插件是不受信的&#xff08;除非是经过认证的网站&#xff09;&#xff0c;特别是一些故意设计侵入浏览器运行的主机代码更是非常危险&#xff0c;通过一些手段或者浏览器中的漏洞&#xff0c…

中仕教育:事业编招考全流程介绍

一、报名阶段 1. 了解查看招聘信息&#xff1a;查看各类事业编岗位的招聘信息&#xff0c;包括岗位职责、招聘条件、报名时间等。 2. 填写报名表&#xff1a;按照要求填写报名表&#xff0c;包括个人信息、教育背景、工作经历等内容。 3. 提交报名材料&#xff1a;将报名表及…

作物品种测试——批量获取试验站点直线距离

参考资料&#xff1a; 根据经纬度计算两地之间的距离_经纬度计算距离-CSDN博客 用于计算不同试验站点之间的距离&#xff0c;可以据此来评估各试验站点分布的合理性。 1、首选需要准备excel文件&#xff0c;用于存放各试验站点的经纬度信息。数据列内容如下&#xff1a; 2、…

[Android] Android文件系统中存储的内容有哪些?

文章目录 前言root 文件系统/system 分区稳定性:安全性: /system/bin用来提供服务的二进制可执行文件:调试工具:UNIX 命令&#xff1a;调用 Dalvik 的脚本(upall script):/system/bin中封装的app_process脚本 厂商定制的二进制可执行文件: /system/xbin/system/lib[64]/system/…

x-cmd pkg | perl - 具有强大的文本处理能力的通用脚本语言

目录 介绍首次用户技术特点竞品进一步阅读 介绍 Perl 是一种动态弱类型编程语言。Perl 内部集成了正则表达式的功能&#xff0c;以及巨大的第三方代码库 CPAN;在处理文本领域,是最有竞争力的一门编程语言之一 生态系统&#xff1a;综合 Perl 档案网络 (CPAN) 提供了超过 25,0…

记一次Flink通过Kafka写入MySQL的过程

一、前言 总体思路&#xff1a;source -->transform -->sink ,即从source获取相应的数据来源&#xff0c;然后进行数据转换&#xff0c;将数据从比较乱的格式&#xff0c;转换成我们需要的格式&#xff0c;转换处理后&#xff0c;然后进行sink功能&#xff0c;也就是将数…

记一次压测程序时的OOM分析过程

背景&#xff1a;在一个项目调优的过程中&#xff0c;丰富了一些组件后&#xff0c;再次对项目进行压测&#xff0c;发现和之前的性能差距甚大&#xff0c;并且每次运行一段时间后&#xff0c;延迟骤增&#xff0c;带宽骤降&#xff0c;查看程序日志&#xff0c;发现了 OutOfMe…

平复一下心情 愉快一下 部署一款在线图书馆

注意:国内不让随便搞线上图书馆 注意:国内不让随便搞线上图书馆 注意:国内不让随便搞线上图书馆 1安装 1.1.拉取镜像 docker pull talebook/talebook 1.2.创建目录 mkdir -p /opt/talebook 1.3.创建并启动容器 docker run -d --name talebook -p 10015:80 -v /opt/taleb…

机器学习实验2——线性回归求解加州房价问题

文章目录 &#x1f9e1;&#x1f9e1;实验内容&#x1f9e1;&#x1f9e1;&#x1f9e1;&#x1f9e1;数据预处理&#x1f9e1;&#x1f9e1;代码缺失值处理特征探索相关性分析文本数据标签编码数值型数据标准化划分数据集 &#x1f9e1;&#x1f9e1;线性回归&#x1f9e1;&am…

【大数据精讲】全量同步与CDC增量同步方案对比

目录 背景 名词解释 问题与挑战 FlinkCDC DataX 工作原理 调度流程 五、DataX 3.0六大核心优势 性能优化 背景 名词解释 CDC CDC又称变更数据捕获&#xff08;Change Data Capture&#xff09;&#xff0c;开启cdc的源表在插入INSERT、更新UPDATE和删除DELETE活动时…