个人项目小结
技术方案
前端
flutter + android + 环信SDK + redux + shared_preferences + connectivity + iconfont + webview + sqflite
后端
Spring Boot + SpringMVC + Mybatis + MybatisPlus + Dubbo
Elasticsearch geo 实现地理位置查询
MongoDB 实现海量数据的存储
Redis 数据的缓存
Spark + MLlib 实现智能推荐
第三方服务 环信即时通讯
第三方服务 阿里云 OSS 、 短信服务
第三方服务 虹软开放平台
技术架构
技术解决方案
使用Elasticsearch geo实现附近的人的解决方案
使用Spark + Mllib实现智能推荐的解决方案
使用MongoDB进行海量数据的存储的解决方案
使用采用分布式文件系统存储小视频数据的解决方案
使用虹软开放平台进行人脸识别的解决方案
技术亮点
采用Elasticsearch geo实现地理位置查询
采用RocketMQ作为消息服务中间件
采用MongoDB进行海量数据的存储
采用Spark + Mllib实现智能推荐
采用环信服务实现即时通讯
采用分布式文件系统存储小视频数据
采用Apache Dobbo作为微服务架构技术
采用SpringBoot + Mybatis实现系统主架构
采用Redis集群实现缓存的高可用
主要功能
注册、登录
用户通过手机验证码进行登录,如果是第一次登录则需要完善个人信息,在上传图片时,需要对上传的图片做人像的校验,防止用户上传非人像的图片作为头像。流程完成后,则登录成功。
交友
交友是探花项目的核心功能之一,用户可以查看好友,添加好友,搜索好友等操作。
首页
主要功能有“今日佳人”、“推荐”、“最近访客”等
· – – 按照“缘分值”进行匹配,将“缘分值”最高的用户展现出来
· 按照“缘分值”进行推荐,由后台的推荐系统计算得出,展现出来
· 显示最近来看“我”的用户
探花
说明:左划喜欢,右划不喜欢,每天限量不超过100个,开通会员可增加限额。双方互相喜欢则配对成功 数据来源推荐系统计算后的结果。
搜附近
根据用户当前所在的位置进行查询,并且在10km的范围内进行查询,可以通过筛选按钮进行条件筛选。
桃花传音
功能类似QQ中的漂流瓶,用户可以发送和接收语音消息,陌生人就会接收到消息
测灵魂
测试题用于对用户进行分类,每次提交答案后更新用户属性
测试题在后台进行维护
测试题测试完后产生结果页可以进行分享
测试题为顺序回答,回答完初级题解锁下一级问题
点击锁定问题 显示提示 请先回答上一级问题
圈子
推荐频道为根据问卷及喜好推荐相似用户动态
显示内容为用户头像、用户昵称、用户性别、用户年龄、用户标签和用户发布动态
图片最多不超过6张或发布一个小视频
动态下方显示发布时间距离当时时间,例如10分钟前、3小时前、2天前,显示时间进行取整
动态下方显示距离为发布动态地与本地距离
显示用户浏览量
显示点赞数、评论数 转发数
消息
消息包含通知类的消息和好友消息。
小视频
用户可以上传小视频,也可以查看小视频列表,并且可以进行点赞操作。
我的
显示关注数、喜欢数、粉丝数、我的动态等信息。
开发方式
探花交友项目采用前后端分离的方式开发,就是前端由前端团队负责开发,后端负责接口的开发
好处
扬长避短,每个团队做自己擅长的事情
– 前后端并行开发,需要事先约定好接口地址以及各种参数、响应数据结构等对于接口的定义我们采用YApi进行管理,YApi是一个开源的接口定义、管理、提供mock数据的管理平台
多线程技术
好友时间线表通过异步执行,使用了spring的@Async注解实现异步执行,底层就是通过启动独立线程来执行,从而可以异步执行通过返回的CompletableFuture来判断是否执行成功以及是否存在异常。同时需要在启动类中添加@EnableAsync 开启异步的支持。
reids存储
用户的验证码,五分钟有效,登陆成功直接进行删除
get请求重复逻辑进行缓存
每次符合拦截的请求会先查缓存是否 有该数据,有的话直接返回,没的话则查数据库,将数据库查到的数据存入 缓存并响应给前端
通过ResponseBodyAdvice进行对响应的拦截,可以将数据缓存到Redis中
不能对于所有的请求都一刀切,所以需要创建@Cache注解进行标记,只有标记的Controller才进行缓存处理。
缓存的处理中,仅针对GET请求处理,其他的请求均不做处理
点赞数,是否点赞采用hash存储这样的好处就在于一条动态的点赞、喜欢等数据都会集中的存储到一起,从而减少了Redis中数据条数。
用户的关注列表,保存到redis中,方便后续的查询
请求到的Token
验证码登录功能
采用技术单点登录
以前实现的登录和注册是在同一个tomcat内部完成,我们现在的系统架构是每一个系统都是由一个团队进行维护,每个系统都是单独部署运行一个单独的tomcat,所以,不能将用户的登录信息保存到session中(多个tomcat的session是不能共享的),所以我们需要一个单独的系统来维护用户的登录信息。
· 客户端需要通过SSO系统才能获取到token
· 客户端在请求服务系统时,服务系统需要通过SSO系统进行对token进行校验;
· SSO系统在整个系统架构中处于核心位置;
使用Lombok优化臃肿的实体类
lombok 提供了简单的注解的形式来帮助我们简化消除一些必须有但显得很臃肿的 java 代码,尤其是针对pojo。
常用注解
· @Data:注解在类上;提供类所有属性的 getting 和 setting 方法,此外还提供了equals、canEqual、hashCode、toString 方法
· @Setter:注解在属性上;为属性提供 setting 方法
· @Getter:注解在属性上;为属性提供 getting 方法
· @Slf4j:注解在类上;为类提供一个 属性名为log 的 slf4j日志对象
· @NoArgsConstructor:注解在类上;为类提供一个无参的构造方法
· @AllArgsConstructor:注解在类上;为类提供一个全参的构造方法
· @Builder:使用Builder模式构建对象
枚举自动映射
,定义枚举类:实现IEnum
实现接口方法:getValue返回一个指定的编码 (1,2,3)
重写toString方法:返回编码对应的字符串(男,女,未知)
配置文件中配置mybatis-plus扫描枚举
·
可以在相关的实体中将需要自动转换的属性声明为枚举类型的
RedisTemplate添加泛型
StringRedisTemplate:它是RedisTemplate的子类,表示存取数据时候采用string的序列 化策略,如果是RedisTemplate表示采用默认的JDK序列化策略:加范型string作用和StringRedisTemplate相同。
· 使用RedisTemplate存储的效果:采用JDK的序列化模式,存储的是二进制数据,此时在客户端使用get name查不到值,使用API的get方法可以(进行了反序列化)
· 使用RedisTemplate<String,String>存储的效果:采用String字符串的序列化模式,存储是纯字符串对象
发送验证码流程
用户向SSO系统发送请求,在请求中传递手机号;
SSO系统接收到请求后,生成随机验证码以及短信内容,请求阿里云短信服务;
阿里云短信服务接收到请求后,会进行一系列的验证,比如账号余额、短信模板是否正确等,最后向运营商发起请求
运营商接收到请求后,向该手机号下发短信,用户即可收到短信;
用户登录流程分析
用户接收到验证码后,进行输入验证码,点击登录,前端系统将手机号以及验证码提交到SSO进行校验。校验成功,删除redis中的验证码信息,生成tocken返回给前端
易错点分析
对业务逻辑不清晰,不清楚自己做到那一步和当前所做阶段应当在前台是什么效果
· 短信验证码获取只实现了单个手机号五分钟只能获取一次,再次获取会提示验证码尚未失效,因为只对数据库里面的key进行了是否存在校验,并没有器他校验逻辑业务实现
· 短信验证码写完后是会出现验证码错误现象的,因为对登录功能尚未实现,需要完善用户登录代码才可进入
配置文件只修改了ip地址而没有修改数据库,会报出sql的异常
pom中<dependencyManagement>定义依赖无法下载而报红
· 解决方案:可以先将依赖放到该标签外定义下载,待下载完成,在定义<dependencyManagement>标签中
访问路径混淆:
· postman路径:127.0.0.1:18080/user/login请求体点body点raw格式选json:内容为{ “phone”: “17602026868”}
•
· 虚拟手机探花软件中测试ip为:本机ip(可以通过cmd窗口:ipconfig命令查询本机ip)加:18080点击保存进行测试,
用手机测试,需跟电脑是同一个网段,输入电脑ip,电脑启动服务,即可用手机进行测试:电脑连接手机网络需拔掉网线才是同一网段,或者电脑手机连得同一个wifi
完善个人信息
技术点
采用阿里云oss技术存储图片
采用虹软进行人脸认证
业务逻辑
用户在首次登录时需要完善个人信息,包括性别、昵称、生日、城市、头像等。其中,头像数据需要做图片上传,这里采用阿里云的OSS服务作为我们的图片服务器,并且对头像要做人脸识别,非人脸照片不得上传。
业务流程
用户通过手机验证码进行登录,如果是第一次登录则需要完善个人信息,在上传图片时,需要对上传的图片做人像的校验,防止用户上传非人像的图片作为头像。流程完成后,则登录成功。
token意义
在整个系统架构中,只有SSO保存了JWT中的秘钥,所以只能通过SSO系统提供的接口服务进行对token的校验,所以在SSO系统中,需要对外开放接口,通过token进行查询用户信息,如果返回null说明用户状态已过期或者是非法的token,否则返回User对象数据。
· /** * 校验token,根据token查询用户数据 * * @param token * @return */ @GetMapping(“{token}”) public User queryUserByToken(@PathVariable(“token”) String token) { return this.userService.queryUserByToken(token); }
token作用:
· 用来获取标识,可理解为自己的令牌
· 携带数据
文件上传
前端三要素
· post请求
· 表单有enctype=”multipart/form-data属性
· 类型为type=file
参数中定义MultipartFile参数,用于接收页面提交的type=file类型的表单,要求表单名称与参数名相同
file.getOriginalFilename():文件命名问题, 获取上传文件名,并解析文件名与扩展名
MultipartFile参数中封装了上传的文件的相关信息
MongoDB
简介:c++语言编写,是介于关系型数据库和非关系型数据库之间的产品,支持的数据结构非常松散,是类似json和bson格式,因此可存储比较复杂的数据类型
特点:MongoDB最大的特点是它支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引。
常用命令
· #进入admin数据库mongouse admin
· #添加管理员,其拥有管理用户和角色的权限db.createUser({ user: ‘root’, pwd: ‘root’, roles: [ { role: “root”, db: “admin” } ] })
· #进行认证mongo -u “root” -p “root” –authenticationDatabase “admin”
· #通过admin添加普通用户use admindb.createUser({ user: ‘tanhua’, pwd: ‘l3SCjl0HvmSkTtiSbN0Swv40spYnHhDV’, roles: [ { role: “readWrite”, db: “tanhua” } ] });
· #查看所有的数据库> show dbs
· #通过use关键字切换数据库> use admin
· #创建数据库#说明:在MongoDB中,数据库是自动创建的,通过use切换到新数据库中,进行插入数据即可自动创建数据库> use testdb
· > db.user.insert({id:1,name:’zhangsan’}) #插入数据
· #查看表> show tables
· #删除集合(表)> db.user.drop()
· #删除数据库> use testdb #先切换到要删除的数据库中
· #查看执行计划> db.user.find({age:{$gt:100},id:{$lt:200}}).explain()
索引
· 索引通常能够极大的提高查询的效率,如果没有索引,MongoDB在读取数据时必须扫描集合中的每个文件并选取那些符合查询条件的记录。这种扫描全集合的查询效率是非常低的,特别在处理大量的数据时,查询可以要花费几十秒甚至几分钟,这对网站的性能是非常致命的。索引是特殊的数据结构,索引存储在一个易于遍历读取的数据集合中,索引是对数据库表中一列或多列的值进行排序的一种结构
易错点
aliyun.properties配置文件值写错,要对应自己的oss账户,每个人不相同
虹软的三个dll配置与配置文件中指定的路径不一致
虹软的配置参数未进行修改值,需要和自己的虹软进行对应,每个人不相同
部分电脑环境缺失:Caused by: java.lang.UnsatisfiedLinkError: D:\gongju\renlian\haha\libs\WIN64\libarcsoft_face.dll: Can’t find dependent libraries安装资料中的环境即可
请求需是post请求,头信息需要跟生成的令牌一直,每次生成的是不一样的
项目结构和配置不一样,没随着项目结构更改
新用户测试跳过了基本信息直接测试了头像上传,出现报错
·
问题:虹软分析是否人像时 WIN64下的dll文件解决不了解决方案:检查配置路径是否有中文,检查路径是否是上下级目录关系 路径需是双斜杠分割 检查自己的虹软ID及sdk码是否正确
在自定义密钥(盐)时,报密钥字节数组不能为空或null的两个方面的问题:1. 检查一下是否能获取到secret2. secret不能太短 最短四个字符
图片大于1M无法上传问题:# 文件上传/请求数据的最大值spring.servlet.multipart.max-request-size=30MBspring.servlet.multipart.max-file-size=30MB
图片能上传至oss,却出现返回连接不带http,或者retun false 原因没有充钱 白嫖方案:强制true
今日佳人功能
工程分析
客户端APP发起请求到Nginx,在Nginx中对请求做出判断,将请求转发至sso系统或server系统。
sso系统中,将对接第三方平台以及完成数据的缓存、消息发送、用户的注册登录功能。
server系统为APP提供了接口服务的支撑
· 用户请求中携带的token需要到sso系统中进行校验
· 通过rpc调用dubbo中提供的服务,在dubbo服务中与MongoDB对接,完成数据的CRUD操作
· 将一些数据缓存到Redis,从而提升数据查询性能
· 用户数据的查询将基于MySQL数据库进行查询
流程图
nginx配置
location /user/ { #请求路径中凡是以/user/开头的请求,转发到sso系统 client_max_body_size 300m; #设置最大的请求体大小,解决大文件上传不了的问题 proxy_connect_timeout 300s; #代理连接超时时间 proxy_send_timeout 300s; #代理发送数据的超时时间 proxy_read_timeout 300s; #代理读取数据的超时时间 proxy_pass http://127.0.0.1:18080; #转发请求 } location / { #上面未匹配到的在这里处理 client_max_body_size 300m; proxy_connect_timeout 300s; proxy_send_timeout 300s; proxy_read_timeout 300s; proxy_pass http://127.0.0.1:18081; #转发请求到server系统 } }
四大功能
· 静态WEB服务器
· 反向代理服务器
· 负载均衡服务器
· 虚拟主机
今日佳人执行流程
dubbo
dubbo流程图
·
@Reference//远程注入
· 未实现serializable接口会出现以下报错
知识点回顾
异常处理
· 名称: @ControllerAdvice 类型: 类注解 位置:异常处理器类上方 作用:设置当前类为异常处理器类
· 名称: @ExceptionHandler 类型: 方法注解 位置:异常处理器类中针对指定异常进行处理的方法上方 作用:设置指定异常的处理方式 说明:处理器方法可以设定多个
· @RestControllerAdvice = @ControllerAdvice + @ResponseBody
拦截器
· 拦截器执行流程
•
· 拦截器过滤器
• 1.. 归属不同: Filter属于Servlet技术, 拦截器属于SpringMVC技术
• 2. 拦截内容不同: Filter对所有访问进行增强, 拦截器仅针对SpringMVC的访问进行增强
· 拦截器链
• 拦截器链:多个拦截器按照一定的顺序,对原始被调用功能进行增强
缓存实现思路
通过拦截器实现对请求的拦截,在拦截器中实现缓存的命中
通过ResponseBodyAdvice进行对响应的拦截,可以将数据缓存到Redis中。
考虑到,不能对于所有的请求都一刀切,所以需要创建@Cache注解进行标记,只有标记的Controller才进行缓存处理。
缓存的处理中,仅针对GET请求处理,其他的请求均不做处理。
* 使用 @ControllerAdvice & ResponseBodyAdvice 拦截Controller方法默认返回参数,统一处理返回值/响应体 * ResponseBodyAdvice接口是在Controller执行return之后,在response返回给浏览器或者APP客户端之前, * 执行的对response的一些处理。可以实现对response数据的一些统一封装或者加密等操作. * 通过supports方法,我们可以选择哪些类,或者哪些方法要对response进行处理,其余的则不处理。 * * beforeBdoyWrite方法中,为对response处理的具体代码
易错点
远程调用dubbo服务注解使用错误
测试出现postman 下方无响应,80错误:原因nginx未启动
分页两种实现
物理分页用多少取多少
· 优点:避免内存溢出
· 缺点:频发操作数据库
逻辑分页,一次性取出所需数据
· 优点:只操作一次数据库
· 缺点:占用大量内存
圈子功能
核心表
发布表:记录了所有用户的发布的东西信息,如图片、视频等。
· #表名:quanzi_publish{ “_id”:”5fae53d17e52992e78a3db61″,#主键id “pid”:1001, #发布id(Long类型) “userId”:1, #用户id “text”:”今天心情很好“, #文本内容 “medias”:”http://xxxx/x/y/z.jpg”, #媒体数据,图片或小视频 url “seeType”:1, #谁可以看,1-公开,2-私密,3-部分可见,4-不给谁看 “seeList”:[1,2,3], #部分可见的列表 “notSeeList”:[4,5,6],#不给谁看的列表“longitude”:108.840974298098,#经度“latitude”:34.2789316522934,#纬度 “locationName”:”上海市浦东区“, #位置名称 “created”,1568012791171 #发布时间}
相册:相册是每个用户独立的,记录了该用户所发布的所有内容。
· #表名:quanzi_album_{userId}{ “_id”:”5fae539d7e52992e78a3b684″,#主键id “publishId”:”5fae53d17e52992e78a3db61″, #发布id “created”:1568012791171 #发布时间}
时间线:所谓“刷朋友圈”,就是刷时间线,就是一个用户所有的朋友的发布内容。
· #表名:quanzi_time_line_{userId}{ “_id”:”5fae539b7e52992e78a3b4ae”,#主键id, “userId”:2, #好友id “publishId”:”5fae53d17e52992e78a3db61″, #发布id “date”:1568012791171 #发布时间}
评论:针对某个具体发布的朋友评论和点赞操作。
· #表名:quanzi_comment{ “_id”:”5fae539d7e52992e78a3b648″, #主键id “publishId”:”5fae53d17e52992e78a3db61″, #发布id “commentType”:1, #评论类型,1-点赞,2-评论,3-喜欢 “content”:”给力!“, #评论内容 “userId”:2, #评论人 “publishUserId”:9, #发布动态的人的id “isParent”:false, #是否为父节点,默认是否 “parentId”:1001, #父节点id “created”:1568012791171}
圈子读写流程
流程说明
· 用户发布动态,动态中一般包含了图片和文字,图片上传到阿里云,上传成功后拿到图片地址,将文字和图片地址进行持久化存储
· 首先,需要将动态数据写入到发布表中,其次,再写入到自己的相册表中,需要注意的是,相册表中只包含了发布id,不会冗余存储发布数据
· 最后,需要将发布数据异步的写入到好友的时间线表中,之所以考虑异步操作,是因为希望发布能够尽快给用户反馈,发布成功
· 好友刷朋友圈时,实际上只需要查询自己的时间线表即可,这样最大限度的提升了查询速度,再配合redis的缓存,那速度将是飞快的
· 用户在对动态内容进行点赞、喜欢、评论操作时,只需要写入到评论表即可,该表中也是只会记录发布id,并不会冗余存储发布数据
查看动态
查询好友的动态流程
· 查询时间线表
· 获取时间线列表中的发布id的列表
· 根据动态id查询动态列表
· 将查询到的数据set到Records
实现功能表的调用流程
· 校验令牌
· 通过dubbo查询数据
· 将查询到的数据填充到QuanziVo,存入集合中
· 通过dubbo数据,获取用户id集合
· 通过id集合查询用户列表,获取用户详细信息,进行数据填充
· 封装为pageResult进行返回
ObjectId
ObjectId 是一个12字节 BSON 类型数据,有以下格式:前4个字节表示时间戳接下来的3个字节是机器标识码紧接的两个字节由进程id组成(PID)最后三个字节是随机数
易错点
server测试src/test/java/com/tanhua/server/TestQuanZiApi.java出现测试通过,数据未写入成功,dubbo服务报错
· 报错信息
· 错误原因:dubbo配置中为对reids进行配置,则默认连接本地redis
定义依赖报错,死活下载不了
·
启动sso:com.tanhua.common.service.PicUploadService’ that could not be found.异常:
·
统一校验token
ThreadLocalthreadlocal而是一个线程内部的存储类,可以在指定线程内存储数据,数据存储以后,只有指定线程可以得到存储数据。每个线程都有一个ThreadLocalMap对象用于存储线程的变量,它交由ThreadLocal来是行维护。当客户发送一个请求来访问controller时,系统就会为其分配一个线程,拦截器查询到token对应的用户信息,就将用户信息保存在ThreadLocal的ThreadLocalMap对象中,用户线程在调用service方法时,会从当前线程中获得用户信息以便使用。//本地线程中获得存储的user:如果能获得,说明已经登陆User user = UserThreadLocal.get();
内部通过ThreadLocal在同一个线程中共享数据 * ThreadLocal是一个本地线程对象:用于在同一个线程中共享变量 * 它内部使用Thread对象的threadLocals (ThreadLocalMap类)进行变量的共享 * ThreadLocal的set方法其实就是调用ThreadLocalMap.set(ThreadLocal对象,value) * ThreadLocal的set方法其实就是调用ThreadLocalMap.get(ThreadLocal对象) * ThreadLocal的remove方法可以将保存的变量清除
圈子点赞小视频
点赞,喜欢
在Redis的存储结构中,采用的是Hash存储,这样的好处就在于一条动态的点赞、喜欢等数据都会集中的存储到一起,从而减少了Redis中数据条数。
点赞流程
· 判断用户是否已经点赞,如果已经点赞,直接返回
· 保存comment数据
• 动态id
• 用户id
• 动态发布者id
• 评论类型
• 评论内容
• 评论时间
· 修改redis中的点赞数以及是否点赞
• 点赞数+1
• 添加点赞标记
取消点赞流程
· 判断是否已经点赞,没有点赞就返回
· 删除评论数据
• 用户id
• 动态id
• 类型
· 修改reids中数据
• 点赞数-1
• 取消点赞标记
点赞数
· 从reids中命中查询,如果命中直接返回
· 查询mongoDB
· 写入redis
是否已经点赞
· 从redis中查询数据
· reids未查询到,查询monggoDB确定已经点赞
· 数据库也没有则直接返回
· 数据库有则写入redis
小视频
核心
· 存储 + 推荐 + 加载速度
· 使用分布式存储系统FastDfs进行存储
FastDFS
· FastDFS 架构包括 Tracker server 和 Storage server。客户端请求 Tracker server 进行文件上传、下载,通过 Tracker server 调度最终由 Storage server 完成文件上传和下载。
· 概念
• Tracker server 作用是负载均衡和调度,通过 Tracker server 在文件上传时可以根据一些策略找到 Storage server 提供文件上传服务。可以将 tracker 称为追踪服务器或调度服务器。
• Storage server 作用是文件存储,客户端上传的文件最终存储在 Storage 服务器上,Storage server 没有实现自己的文件系统而是利用操作系统的文件系统来管理文件。可以将storage称为存储服务器。
· 文件ID内含了文件在服务器上存储路径
• 进入tracker的容器内部
• 进入/etc/fdfs
• 查看storage.conf
· 数据存储storage 容器的:/var/fdfs/data/FILEID: /group1/M00/00/00/aadfasdfadfa.jpg
发布流程
·
· 用户发通过客户端APP上传视频到server服务
· server服务上传视频到FastDFS文件系统,上传成功后返回视频的url地址
· server通过rpc的调用dubbo服务进行保存小视频数据
即时通信
流程
前后端都需要集成环信
在APP端,使用Android的SDK与环信进行通信,通信时需要通过后台系统的接口查询当前用户的环信用户名和密码,进行登录环信
后台系统,在用户注册后,同步注册环信用户到环信平台,在后台系统中保存环信的用户名和密码
APP拿到用户名和密码后,进行登录环信,登录成功后即可向环信发送消息给好友
后台系统也可以通过管理员的身份给用户发送系统信息
获取token流程
先从redis中获取,如果没有再从环信接口获取
访问环信接口 获取token
将获取到的token进行reids缓存,提前expires_in一个小时失效
新用户注册后同时注册环信
用户在登录时在sso系统中进行判断,如果是新用户,在注册完成后,需要调用dubbo中的环信服务进行注册环信用户
dubbo-huanxin服务在注册环信用户时,需要随机生成密码,携带token请求环信的REST API进行用户注册。
注册成功后,需要将环信的用户信息保存到MySQL中。
用户在APP端使用即时通讯功能时,需要通过环信用户信息登录到环信平台,由于数据存储到服务端,所以需要通过dubbo-huanxin进行查询
在拿到环信账号信息后,登录环信,登录成功后即可与环信平台进行交互。
需要注意的是,APP端与环信平台交互,是不走后端系统的,是直连操作
重试机制
@EnableRetry注解来激活重试功能
导入依赖
· dependency> <groupId>org.springframework.retry</groupId> <artifactId>spring-retry</artifactId></dependency><dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId></dependency>
参数说明
· value:抛出指定异常才会重试
· maxAttempts:最大重试次数,默认3次
· backoff:重试等待策略,默认使用@Backoff
• @Backoff 的value默认为1000L,我们设置为2000L;
• multiplier(指定延迟倍数)默认为0,表示固定暂停1秒后进行重试,如果把multiplier设置为2,则第一次重试为2秒,第二次为4秒,第三次为6秒
· Recover标注的方法,是在所有的重试都失败的情况下,最后执行该方法
• 方法的第一个参数必须是 Throwable 类型,最好与 @Retryable 中的 value一致
• 方法的返回值必须与@Retryable的方法返回值一致,否则该方法不能被执行
添加联系人
实现
· 将好友写入到MongoDB中
· 将好友关系注册到环信
流程
·
联系人列表
根据用户id查询全部Users列表
环信一管理员身份发送消息
@param targetUserName 发送目标的用户名 @param huanXinMessageType 消息类型 @param msg 消息体
String targetUrl = this.huanXinConfig.getUrl() + this.huanXinConfig.getOrgName() + “/” + this.huanXinConfig.getAppName() + “/messages”; /*{“target_type”: “users”,”target”: [“user2″,”user3″],”msg”: {“type”: “txt”,”msg”: “testmessage”},”from”: “user1”}*/ String body = JSONUtil.createObj() .set(“target_type”, “users”) .set(“target”, JSONUtil.createArray().set(targetUserName)) .set(“msg”, JSONUtil.createObj() .set(“type”, huanXinMessageType.getType()) .set(“msg”, msg)).toString(); //表示消息发送者;无此字段Server会默认设置为“from”:“admin”,有from字段但值为空串(“”)时请求失败// .set(“from”, “”) return this.requestService.execute(targetUrl, body, Method.POST).isOk();
易错
要查询的id数据库有多条数据
·
排除mongoDB
·
端口占用
· 原因:idea卡死或者异常关闭
搜附近以及探花功能
es地理位置存储到Elasticsearch中
判断索引是否存在
判断表是否存在
查询个人的地理位置数据,如果不存在创建,存在则修改
根据id查询数据是否存在
· GetQuery getQuery = new GetQuery(); getQuery.setId(String.valueOf(userId)); UserLocation userLocation = this.elasticsearchTemplate.queryForObject(getQuery, UserLocation.class);
新增
· IndexQuery indexQuery = new IndexQueryBuilder().withObject(userLocation).build(); //保存数据到ES中 this.elasticsearchTemplate.index(indexQuery);
修改
· UpdateRequest updateRequest = new UpdateRequest(); updateRequest.doc(map); UpdateQuery updateQuery = new UpdateQueryBuilder() .withId(String.valueOf(userId)) .withClass(UserLocation.class) .withUpdateRequest(updateRequest).build(); //更新数据 this.elasticsearchTemplate.update(updateQuery);
esApi回顾
JavaAPI
· 添加索引
• 1. 获取索引对象
• client.indices()
• 2. 创建添加索引的请求
• new CreateIndexRequest(“itheima”)
• 3. 发起创建请求
• indicesClient.create(createRequest,
• RequestOptions.DEFAULT)
· 添加索引+映射mapping
• createRequest.mapping(mapping, XContentType.JSON)
· 了解:查询、删除、判断索引
· 添加文档
• 1. 构建文档数据new HashMap()
• JSON.toJSONString(person)
• 2. new IndexRequest(“itcast”).id(“1”).source(data)
• new IndexRequest(“itcast”).id(p.getId()).source(data, XContentType.JSON)
• 3, client.index(request, RequestOptions.DEFAULT)
· 修改文档
• 添加文档时,如果id存在则修改,id不存在则添加
· 根据ID查询
• 1. new GetRequest(“itcast”, “1”)
• 2. client.get(getReqeust, RequestOptions.DEFAULT)
• 3. response.getSourceAsString()
· 根据ID删除
• 1. new DeleteRequest(“itcast”, “1”)
• 2. client.delete(deleteRequest, RequestOptions.DEFAULT)
• 3. response.getId()
子主题 2
范围和排序查询
· 范围
• range
• gte
• lte
• QueryBuilders.rangeQuery(“price”).gte(2000).lte(2500)
· 排序
• sort
• order: desc, asc
• 在构建条件查询器指定:sourceBuilder.sort(“price”, SortOrder.DESC)