环信实现聊天功能

简介: 环信实现聊天功能

1、即时通信

如果想简易打造一套聊天方案参照我的,websocket聊天室的制作:

https://blog.csdn.net/ZGL_cyy/article/details/118438572

1.1 什么是即时通信?

1.2 功能说明

聊天功能,用户可以和好友或陌生人聊天。

如果是陌生人,通过聊一下功能进行打招呼,如果对方同意后,就成为了好友,可以进行聊天了。

2 技术方案

对于高并发的即时通讯实现,还是很有挑战的,所需要考虑的点非常多,除了要实现功能,还要考虑并发、流量、负载、服务器、容灾等等。虽然有难度也并不是高不可攀。

对于现实即时通讯往往有两种方案:

  • 方案一:
  • 自主实现,从设计到架构,再到实现。
  • 技术方面可以采用:Netty + WebSocket + RocketMQ + MongoDB + Redis + ZooKeeper + MySQL
  • 方案二:
  • 对接第三方服务完成。
  • 这种方式简单,只需要按照第三方的api进行对接就可以了。
  • 如:环信、网易、容联云通讯等。

如何选择呢?


如果是中大型企业做项目可以选择自主研发,如果是中小型企业研发中小型的项目,选择第二种方案即可。方案一需要有大量的人力、物力的支持,开发周期长,成本高,但可控性强。方案二,成本低,开发周期短,能够快速的集成起来进行功能的开发,只是在可控性方面来说就差了一些。


交友项目选择方案二进行实现。

3 环信

官网:https://www.easemob.com/ 稳定健壮,消息必达,亿级并发的即时通讯云

3.1 开发简介

集成:

环信和用户体系的集成主要发生在2个地方,服务器端集成和客户端集成。

3.2 环信Console

需要使用环信平台,那么必须要进行注册,登录之后即可创建应用。环信100以内的用户免费使用,100以上就要注册企业版了。

4 用户体系集成

4.1 Appkey 数据结构

当您申请了 AppKey 后,会得到一个 xxxx#xxxx 格式的字符串,字符串只能由小写字母数字组成,AppKey是环信应用的唯一标识。前半部分 org_name 是在多租户体系下的唯一租户标识,后半部分 app_name 是租户下的app唯一标识(在环信后台创建一个app时填写的应用 id 即是 app_name )。下述的 REST API 中,/{org_name}/{app_name}的请求,均是针对一个唯一的appkey进行的。目前环信注册的appkey暂不能由用户自己完成删除操作,如果对 APP 删除需要联系环信操作完成。

Appkey xxxx 分隔符 xxxx
环信应用的唯一标识 org_name # app_name

4.2 环信 ID 数据结构

环信作为一个聊天通道,只需要提供环信 ID (也就是 IM 用户名)和密码就够了。

名称 字段名 数据类型 描述
环信 ID username String 在 AppKey 的范围内唯一用户名。
用户密码 password String 用户登录环信使用的密码。

4.3 环信 ID 使用规则

当 APP 和环信集成的时候,需要把 APP 系统内的已有用户和新注册的用户和环信集成,为每个已有用户创建一个环信的账号(环信 ID),并且 APP 有新用户注册的时候,需要同步的在环信中注册。

在注册环信账户的时候,需要注意环信 ID 的规则:


使用英文字母和(或)数字的组合

不能使用中文

不能使用 email 地址

不能使用 UUID

用户ID的长度在255字节以内

中间不能有空格或者井号(#)等特殊字符

允许的用户名正则 “[a-zA-Z0-9_-.](a~z大小写字母/数字/下划线/横线/英文句号),其他都不允许如果是大写字母会自动转成小写`

不区分大小写。系统忽略大小写,认为 AA、Aa、aa、aA 都是一样的。如果系统已经存在了环信 ID 为 AA 的用户,再试图使用 aa 作为环信 ID 注册新用户,系统返回用户名重复,以此类推。但是请注意:环信 ID 在数据上的表现形式还是用户最初注册的形式,注册时候使用的大写就保存大写,是小写就保存小写。即:使用 AA 注册,环信保存的 ID 就是 AA;使用 Aa 注册,环信保存的 ID 就是 Aa,以此类推。

另:本文档中可能会交错使用“环信 ID”和“环信用户名”两个术语,但是请注意,这里两个的意思是一样的。


因为一个用户的环信 ID 和他的在 APP 中的用户名并不需要一致,只需要有一个明确的对应关系。例如,用户名是 example@easemob.com,当这个用户登录到 APP 的时候,可以登录成功之后,再登录环信的服务器,所以这时候,只需要能够从 example@easemob.com 推导出这个用户的环信 ID 即可。


4.4 获取管理员权限

环信提供的 REST API 需要权限才能访问,权限通过发送 HTTP 请求时携带 token 来体现,下面描述获取 token 的方式。说明:API 描述的时候使用到的 {APP 的 client_id} 之类的这种参数需要替换成具体的值。


重要提醒:获取 token 时服务器会返回 token 有效期,具体值参考接口返回的 expires_in 字段值。由于网络延迟等原因,系统不保证 token 在此值表示的有效期内绝对有效,如果发现 token 使用异常请重新获取新的 token,比如“http response code”返回 401。另外,请不要频繁向服务器发送获取 token 的请求,同一账号发送此请求超过一定频率会被服务器封号,切记,切记!!


client_id 和 client_secret 可以在环信管理后台的 [APP 详情页面看到。

Request Headers

参数 说明
Content-Type application/json

Request Body

参数 说明
grant_type client_credentials
client_id App的client_id,可在app详情页找到
client_secret App的client_secret,可在app详情页找到

Response Body

参数 说明
access_token 有效的token字符串
expires_in token 有效时间,以秒为单位,在有效期内不需要重复获取
application 当前 App 的 UUID 值

4.4.1 配置

将用户体系集成的逻辑写入到sso系统中。

huanxin.properties

oldlu.huanxin.url=http://a1.easemob.com/
oldlu.huanxin.orgName=1105190515097562
oldlu.huanxin.appName=oldlu
oldlu.huanxin.clientId=YXA67ZofwHblEems-_Fh-17T2g
oldlu.huanxin.clientSecret=YXA60r45rNy2Ux5wQ7YYoEPwynHmUZk

说明:这配置在控制台可以找到。

package com.oldlu.sso.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
@Configuration
@PropertySource("classpath:huanxin.properties")
@ConfigurationProperties(prefix = "oldlu.huanxin")
@Data
public class HuanXinConfig {
    private String url;
    private String orgName;
    private String appName;
    private String clientId;
    private String clientSecret;
}

4.4.2 获取token

package com.oldlu.sso.service;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.oldlu.sso.config.HuanXinConfig;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
@Service
public class HuanXinTokenService {
    private static final ObjectMapper MAPPER = new ObjectMapper();
    @Autowired
    private HuanXinConfig huanXinConfig;
    @Autowired
    private RestTemplate restTemplate;
    public static final String REDIS_KEY = "HX_TOKEN";
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    private String refreshToken() {
        String targetUrl = this.huanXinConfig.getUrl() + this.huanXinConfig.getOrgName() + "/" + this.huanXinConfig.getAppName() + "/token";
        Map<String, String> param = new HashMap<>();
        param.put("grant_type", "client_credentials");
        param.put("client_id", this.huanXinConfig.getClientId());
        param.put("client_secret", this.huanXinConfig.getClientSecret());
        //请求环信接口
        ResponseEntity<String> responseEntity =
                this.restTemplate.postForEntity(targetUrl, param, String.class);
        if (responseEntity.getStatusCodeValue() != 200) {
            return null;
        }
        String body = responseEntity.getBody();
        try {
            JsonNode jsonNode = MAPPER.readTree(body);
            String accessToken = jsonNode.get("access_token").asText();
            if (StringUtils.isNotBlank(accessToken)) {
                // 将token保存到redis,有效期为5天,环信接口返回的有效期为6天
                this.redisTemplate.opsForValue().set(REDIS_KEY, accessToken, Duration.ofDays(5));
                return accessToken;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    public String getToken() {
        String token = this.redisTemplate.opsForValue().get(REDIS_KEY);
        if (StringUtils.isBlank(token)) {
            return this.refreshToken();
        }
        return token;
    }
}

4.5 注册环信用户

注册环信用户分为2种,开放注册、授权注册,区别在于开发注册不需要token,授权注册需要token。

我们使用的授权注册:

package com.oldlu.sso.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class HuanXinUser {
    private String username;
    private String password;
}
package com.oldlu.sso.service;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.oldlu.sso.config.HuanXinConfig;
import com.oldlu.sso.vo.HuanXinUser;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.util.Arrays;
@Service
public class HuanXinService {
    private static final ObjectMapper MAPPER = new ObjectMapper();
    @Autowired
    private HuanXinTokenService huanXinTokenService;
    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private HuanXinConfig huanXinConfig;
    /**
     * 注册环信用户
     *
     * @param userId
     * @return
     */
    public boolean register(Long userId) {
        String targetUrl = this.huanXinConfig.getUrl()
                + this.huanXinConfig.getOrgName() + "/"
                + this.huanXinConfig.getAppName() + "/users";
        String token = this.huanXinTokenService.getToken();
        try {
            // 请求体
            HuanXinUser huanXinUser = new HuanXinUser(String.valueOf(userId), DigestUtils.md5Hex(userId + "_itlu_oldlu"));
            String body = MAPPER.writeValueAsString(huanXinUser);
            // 请求头
            HttpHeaders headers = new HttpHeaders();
            headers.add("Content-Type", "application/json");
            headers.add("Authorization", "Bearer " + token);
            HttpEntity<String> httpEntity = new HttpEntity<>(body, headers);
            ResponseEntity<String> responseEntity = this.restTemplate.postForEntity(targetUrl, httpEntity, String.class);
            return responseEntity.getStatusCodeValue() == 200;
        } catch (Exception e) {
            e.printStackTrace();
        }
        // 注册失败
        return false;
    }
}

加入到登录逻辑中:

4.6 测试

可以看到已经注册到了环信。

4.7 查询环信用户信息

在app中,用户登录后需要根据用户名密码登录环信,由于用户名密码保存在后台,所以需要提供接口进行返回。

实现:

package com.oldlu.server.controller;
import com.oldlu.server.pojo.User;
import com.oldlu.server.utils.UserThreadLocal;
import com.oldlu.server.vo.HuanXinUser;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("huanxin")
public class HuanXinController {
    @GetMapping("user")
    public ResponseEntity<HuanXinUser> queryHuanXinUser(){
        User user = UserThreadLocal.get();
        HuanXinUser huanXinUser = new HuanXinUser();
        huanXinUser.setUsername(user.getId().toString());
        huanXinUser.setPassword(DigestUtils.md5Hex(user.getId() + "_itlu_oldlu"));
        return ResponseEntity.ok(huanXinUser);
    }
}

4.8 发送消息给客户端

目前已经完成了用户体系的对接,下面我们进行测试发送消息,场景是这样的:

点击“聊一下”,就会给对方发送一条陌生人信息,这个消息由系统发送完成。

消息内容:

{"userId": "1","nickname":"老陆","strangerQuestion": "测试一下问题","reply": "aaaaa"}
目录
相关文章
|
26天前
|
存储 人工智能 Ubuntu
2026年OpenClaw史诗级更新实战:1分钟阿里云/本地部署+免费百炼API配置+ContextEngine记忆自由插拔指南
2026年3月,OpenClaw(曾用名Clawdbot)迎来史上最密集的一次核心更新——v2026.3.7-beta.1版本携89项代码提交、200+Bug修复重磅上线,创始人Peter Steinberger亲自官宣其核心亮点:全新ContextEngine插件接口实现AI记忆“自由插拔”,无需修改核心代码即可切换上下文管理策略;同时首发适配GPT-5.4与Gemini Flash 3.1双引擎,性能与兼容性实现双重飞跃。
936 23
EMQ
|
网络协议 物联网 Linux
2022 年值得尝试的 7 个 MQTT 客户端工具
随着物联网行业的飞速发展,MQTT协议也被越来越多的公司及开发者所使用。鉴于目前MQTT客户端工具种类繁多,本文筛选和整理了截至2022年最新、最实用的7个MQTT客户端工具,希望可以帮助MQTT开发者快速找到合适的客户端工具。
EMQ
4257 1
2022 年值得尝试的 7 个 MQTT 客户端工具
|
安全 Linux
Linux内核OverlayFS子系统权限提升漏洞(CVE-2023-0386)
Linux内核OverlayFS子系统权限提升漏洞,在Linux内核的 OverlayFS子系统中,当用户将一个具有权限的文件从一个nosuid挂载点复制到另一个挂载点时,未经授权的攻击者可以执行setuid文件,导致权限提升。
569 1
|
26天前
|
存储 人工智能 Linux
OpenClaw部署与优化保姆级教程:1分钟阿里云/本地配置百炼+claude-mem+OpenViking Skill,Token 成本降96%
OpenClaw(俗称大龙虾,原Clawdbot、Moltbot)作为一款高性能AI Agent框架,凭借自然语言驱动的任务自动化能力,成为代码开发、流程协作中的重要工具,但在长周期任务执行中,其无状态特性带来的记忆短板与Token高消耗问题,成为企业级落地的核心阻碍。2026年最新技术实践中,通过集成claude-mem与OpenViking两款开源项目,可从底层重构OpenClaw的记忆管理体系,实现Token成本断崖式下降96%,同时结合阿里云与本地多平台部署方案,能让零基础用户快速搭建起高性价比的AI Agent运行环境。本文将深度拆解两款开源项目的核心优化逻辑,同时提供阿里云、Mac
1775 18
|
1月前
|
人工智能 机器人 项目管理
保姆级教程:OpenClaw(Clawdbot)阿里云/本地多Agent部署+飞书机器人协同,搭建专属 AI 打工团队
2026年,AI智能体的核心进化方向从“单一功能执行”转向“多角色协同”——OpenClaw(昵称“龙虾”)凭借成熟的MultiAgent架构,打破了传统AI工具“单打独斗”的局限,让多个智能体分工协作,像真人团队一样拆解任务、并行执行、汇总结果。这种能力在内容创作、项目管理、业务协作等复杂场景中价值凸显:主Agent负责任务拆分与分配,子Agent各司其职(如公众号文案、小红书创作、数据整理),搭配飞书机器人实现实时交互与成果同步,真正实现“一句话启动复杂项目”。
5406 1
|
SQL 存储 分布式计算
ODPS跨集群迁移与数据同步经验分享
集团业务的迅猛发展带来数据量的激增,存储容量告急,迫切需要将生产集群PA上的大量数据迁移到其它集群。如何安全地跨集群迁移几十PB的数据和其上相关业务,是我们面临的第一个挑战。数据迁移之后,两个集群间存在大量的数据依赖,需要互相访问最新的数据,如何安全快速地实现跨集群数据同步,这是紧跟其后的第二个挑战
4494 0
|
8月前
|
存储 人工智能 安全
医学影像PACS系统的设计与实现,PACS源码
医学影像PACS系统是数字化医院的核心,实现影像的采集、存储、传输与辅助诊断。系统基于DICOM、HL7等标准,采用分布式架构与模块化设计,支持影像分层存储、高效调阅、AI集成及多系统联动,提升诊疗效率与数据管理能力。
1359 0
|
机器学习/深度学习 编解码 人工智能
超越Transformer,全面升级!MIT等华人团队发布通用时序TimeMixer++架构,8项任务全面领先
一支由麻省理工学院、香港科技大学(广州)、浙江大学和格里菲斯大学的华人研究团队,开发了名为TimeMixer++的时间序列分析模型。该模型在8项任务中超越现有技术,通过多尺度时间图像转换、双轴注意力机制和多尺度多分辨率混合等技术,实现了性能的显著提升。论文已发布于arXiv。
963 84
|
消息中间件 安全 Python
Python日志管理之Loguru
Python日志管理之Loguru