SpringBoot+Dubbo+环信(即时通信)整合
1.注册环信账号
官网:https://www.easemob.com/ 稳定健壮,消息必达,亿级并发的即时通讯云
2. 了解平台架构
文档地址:http://docs-im.easemob.com/
平台架构:

3. 创建应用
这里选择授权注册,注册需要进行校验,防止随意添加用户
创建成功
创建应用成功后,注意下面标红的几个参数,需要在配置文件中配置一下的参数


4. 后端集成用户体系
文档:http://docs-im.easemob.com/im/server/ready/user
集成环信需要参考上面这个官方开发文档
5. 功能整体流程图

说明:

  • 在APP端与后端系统,都需要完成与环信的集成。
  • 在APP端,使用Android的SDK与环信进行通信,通信时需要通过后台系统的接口查询当前用户的环信用户名和密码,进行登录环信。
  • 后台系统,在用户注册后,同步注册环信用户到环信平台,在后台系统中保存环信的用户名和密码。
  • APP拿到用户名和密码后,进行登录环信,登录成功后即可向环信发送消息给好友。
  • 后台系统也可以通过管理员的身份给用户发送系统信息。
    6. 创建dubbo工程
    dubbo工程创建采用开闭原则,开放接口,封装服务,只对外暴露接口,通过接口对外提供服务
    先创建一个父工程my-tanhua-dubbo(maven工程),然后在父工程下面创建两个子模块:my-tanhua-dubbo-interface(maven工程) my-tanhua-dubbo-huanxin(springboot工程),接口模块只提供对外服务的接口,服务模块是具体的服务提供方,共工程结构如下图所示:


    7. 编写dubbo服务

- 导入坐标
注意:这里导入的坐标是springboot的初始坐标和一些用到的坐标
**[**插入一个小知识点:这里my-tanhua-dubbo-huanxin模块引入了my-tanhua-dubbo-interface模块,如果interface使用了mongodb,而huanxin模块间接引入了mongodb服务,huanxin没有mongodb的配置,启动的时候会报错,如下图所示:

解决办法:
1.在pom中引入interface使用exclusions标签排除monggodb服务
2.在huanxin模块的引导类上排除mongodb

@SpringBootApplication(exclude = {MongoAutoConfiguration.class, MongoDataAutoConfiguration.class})

]

<dependencies><!--引入interface依赖--><dependency><groupId>cn.itcast.tanhua</groupId><artifactId>my-tanhua-dubbo-interface</artifactId><version>1.0-SNAPSHOT</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus</artifactId></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><!--dubbo的springboot支持--><dependency><groupId>com.alibaba.boot</groupId><artifactId>dubbo-spring-boot-starter</artifactId></dependency><!--dubbo框架--><dependency><groupId>com.alibaba</groupId><artifactId>dubbo</artifactId></dependency><!--zk依赖--><dependency><groupId>org.apache.zookeeper</groupId><artifactId>zookeeper</artifactId></dependency><dependency><groupId>com.github.sgroschupf</groupId><artifactId>zkclient</artifactId></dependency><dependency><groupId>io.netty</groupId><artifactId>netty-all</artifactId></dependency><!--实用工具hutool--><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId></dependency>
<!-- spring中的实现重试的一个框架--><dependency><groupId>org.springframework.retry</groupId><artifactId>spring-retry</artifactId></dependency><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId></dependency></dependencies>

- 编写配置文件
application.properties:

# Spring boot application
spring.application.name = itcast-tanhua-dubbo-huanxin# dubbo 扫描包配置
dubbo.scan.basePackages = com.tanhua.dubbo.server
dubbo.application.name = dubbo-provider-huanxin#dubbo 对外暴露的端口信息
dubbo.protocol.name = dubbo
dubbo.protocol.port = 20881#dubbo注册中心的配置
dubbo.registry.address = zookeeper://192.168.200.129:2181
dubbo.registry.client = zkclient
dubbo.registry.timeout = 60000 # Redis相关配置
spring.redis.jedis.pool.max-wait = 5000ms
spring.redis.jedis.pool.max-Idle = 100
spring.redis.jedis.pool.min-Idle = 10
spring.redis.timeout = 10s
#下面两行注释采用的是redis集群的方式配置
#spring.redis.cluster.nodes = 192.168.200.129:6379,192.168.200.129:6380,192.168.200.129:6381
#spring.redis.cluster.max-redirects=5
#这里我们使用的是单节点
spring.redis.port=6379
spring.redis.host=192.168.200.129#数据库连接信息
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/tanhua?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&useSSL=falsespring.datasource.username=root
spring.datasource.password=root# 表名前缀
mybatis-plus.global-config.db-config.table-prefix=tb_
# id策略为自增长
mybatis-plus.global-config.db-config.id-type=auto

huanxin.properties:切记改成自己创建的应用的信息

#环信参数要使用自己创建的应用的信息
tanhua.huanxin.url=http://a1.easemob.com/
tanhua.huanxin.orgName=1105190515097562
tanhua.huanxin.appName=tanhua
tanhua.huanxin.clientId=YXA67ZofwHblEems-_Fh-17T2g
tanhua.huanxin.clientSecret=YXA60r45rNy2Ux5wQ7YYoEPwynHmUZk

- 编写配置类:加载环信的配置文件

package com.tanhua.dubbo.server.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 = "tanhua.huanxin")
@Data
public class HuanXinConfig {private String url;private String orgName;private String appName;private String clientId;private String clientSecret;}

- 编写配置类:

package com.tanhua.dubbo.server.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 = "tanhua.huanxin")
//lomback生成set和get等方法
@Data
public class HuanXinConfig {private String url;private String orgName;private String appName;private String clientId;private String clientSecret;}
  • 管理员获取token
    官方文档
    环信提供的 REST API 需要权限才能访问,权限通过发送 HTTP 请求时携带 token 来体现,具体的获取token的业务逻辑在TokenService中完成。实现要点:

     - 分析官方文档中的请求url、参数、响应数据等内容- 请求到token需要缓存到redis中,不能频繁的获取token操作,可能会被封号
    
package com.tanhua.dubbo.server.service;import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.tanhua.dubbo.server.config.HuanXinConfig;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
//管理员(就是创建应用生成的那个id和密码的那个)获取token
@Service
@Slf4j
public class TokenService {@Autowiredprivate RedisTemplate<String, String> redisTemplate;private static final String REDIS_KEY = "HX_TOKEN";@Autowiredprivate HuanXinConfig huanXinConfig;/*** 获取token,先从redis中获取,如果没有,再去环信接口获取** @return*/public String getToken() {String token = this.redisTemplate.opsForValue().get(REDIS_KEY);if (StrUtil.isNotEmpty(token)) {return token;}//访问环信接口获取tokenreturn this.refreshToken();}/*** 刷新token,请求环信接口,将token存储到redis中** @return*/public String refreshToken() {String targetUrl = this.huanXinConfig.getUrl() +this.huanXinConfig.getOrgName() + "/" +this.huanXinConfig.getAppName() + "/token";Map<String, Object> param = new HashMap<>();param.put("grant_type", "client_credentials");param.put("client_id", this.huanXinConfig.getClientId());param.put("client_secret", this.huanXinConfig.getClientSecret());HttpResponse response = HttpRequest.post(targetUrl).body(JSONUtil.toJsonStr(param)).timeout(20000) //请求超时时间.execute();if (!response.isOk()) {log.error("刷新token失败~~~ ");return null;}//拿到环信响应的响应体,获取到里面的token和过期时间expires_in.有效期为7天,但不能完全保证String jsonBody = response.body();JSONObject jsonObject = JSONUtil.parseObj(jsonBody);String token = jsonObject.getStr("access_token");if (StrUtil.isNotEmpty(token)) {//将token数据缓存到redis中,缓存时间由expires_in决定//提前1小时失效long timeout = jsonObject.getLong("expires_in") - 3600;this.redisTemplate.opsForValue().set(REDIS_KEY, token, timeout, TimeUnit.SECONDS);return token;}return null;}
}

注意:到现在为止真正的dubbo从下面开始编写,以上只是获取token并存储到redis中的逻辑实现

- 在interface中定义pojo对象

package com.tanhua.dubbo.server.pojo;import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import java.util.Date;/*** 环信用户对象*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("tb_huanxin_user")
public class HuanXinUser implements java.io.Serializable{private static final long serialVersionUID = -6400630011196593976L;private Long id; //主键Id/*** 环信 ID ;也就是 IM 用户名的唯一登录账号,长度不可超过64个字符长度*/private String username;/*** 登录密码,长度不可超过64个字符长度*/private String password;/*** 昵称(可选),在 iOS Apns 推送时会使用的昵称(仅在推送通知栏内显示的昵称),* 并不是用户个人信息的昵称,环信是不保存用户昵称,头像等个人信息的,* 需要自己服务器保存并与给自己用户注册的IM用户名绑定,长度不可超过100个字符*/private String nickname;private Long userId; //用户idprivate Date created; //创建时间private Date updated; //更新时间}

- 编写HuanXinUserMapper
注册成功后,在环信上的用户名和密码需要和其他信息需要存储到mysql数据库中

package com.tanhua.dubbo.server.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.tanhua.dubbo.server.pojo.HuanXinUser;
import org.apache.ibatis.annotations.Mapper;
//这个用于保存环信注册成功用户的用户名和密码
@Mapper
public interface HuanXinUserMapper extends BaseMapper<HuanXinUser> {
}

- 定义interface中的接口

package com.tanhua.dubbo.server.api;import com.tanhua.dubbo.server.pojo.HuanXinUser;/*** 与环信平台集成的相关操作*/
public interface HuanXinApi {/*** 获取环信token(获取管理员权限)* 参见:http://docs-im.easemob.com/im/server/ready/user#%E8%8E%B7%E5%8F%96%E7%AE%A1%E7%90%86%E5%91%98%E6%9D%83%E9%99%90** @return*/String getToken();/*** 注册环信用户* 参见:http://docs-im.easemob.com/im/server/ready/user#%E6%B3%A8%E5%86%8C%E5%8D%95%E4%B8%AA%E7%94%A8%E6%88%B7_%E5%BC%80%E6%94%BE** @param userId 用户id* @return*/Boolean register(Long userId);/*** 根据用户Id询环信账户信息** @param userId* @return*/HuanXinUser queryHuanXinUser(Long userId);/*** 根据环信用户名查询用户信息** @param userName* @return*/HuanXinUser queryUserByUserName(String userName);}

- 实现接口
在my-tanhua-dubbo-huanxin中完成。

package com.tanhua.dubbo.server.api;import cn.hutool.core.util.IdUtil;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.Method;
import cn.hutool.json.JSONUtil;
import com.alibaba.dubbo.config.annotation.Service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.tanhua.dubbo.server.config.HuanXinConfig;
import com.tanhua.dubbo.server.mapper.HuanXinUserMapper;
import com.tanhua.dubbo.server.pojo.HuanXinUser;
import com.tanhua.dubbo.server.service.RequestService;
import com.tanhua.dubbo.server.service.TokenService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;import java.util.Arrays;
import java.util.Date;@Service(version = "1.0.0")
@Slf4j
public class HuanXinApiImpl implements HuanXinApi {@Autowiredprivate TokenService tokenService;@Autowiredprivate HuanXinConfig huanXinConfig;@Autowiredprivate RequestService requestService;@Autowiredprivate HuanXinUserMapper huanXinUserMapper;/*** 管理员获取token* @return*/@Overridepublic String getToken() {return this.tokenService.getToken();}/*** 普通用户注册环信账号* @param userId 用户id* @return*/@Overridepublic Boolean register(Long userId) {String targetUrl = this.huanXinConfig.getUrl()+ this.huanXinConfig.getOrgName() + "/" +this.huanXinConfig.getAppName() + "/users";HuanXinUser huanXinUser = new HuanXinUser();huanXinUser.setUsername("HX_" + userId);  // 用户名huanXinUser.setPassword(IdUtil.simpleUUID()); //随机生成的密码,这里的密码是服务器端自动生成的,不是环信给你生成的//调用通用的方法向环信的rest接口发送请求,批量添加数据HttpResponse response = this.requestService.execute(targetUrl, JSONUtil.toJsonStr(Arrays.asList(huanXinUser)), Method.POST);if (response.isOk()) {//将环信的账号信息保存到数据库huanXinUser.setUserId(userId);huanXinUser.setCreated(new Date());huanXinUser.setUpdated(huanXinUser.getCreated());this.huanXinUserMapper.insert(huanXinUser);return true;}return false;}/*** 根据用户的id查询环信信息* @param userId* @return*/@Overridepublic HuanXinUser queryHuanXinUser(Long userId) {QueryWrapper<HuanXinUser> wrapper = new QueryWrapper<>();wrapper.eq("user_id", userId);return this.huanXinUserMapper.selectOne(wrapper);}/*** 根据环信用户名查询用户名,注意:这里生成的环信用户名是HX_103,对应唯一的用户id* @param userName* @return*/@Overridepublic HuanXinUser queryUserByUserName(String userName) {QueryWrapper<HuanXinUser> wrapper = new QueryWrapper<>();wrapper.eq("username", userName);return this.huanXinUserMapper.selectOne(wrapper);}
}

- 编写统一的请求环信rest接口的请求类
这里使用了hutool工具来发送http请求的一个方法

1.先自定义一个异常

package com.tanhua.dubbo.server.exception;import cn.hutool.http.Method;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
//自定义异常注解
@AllArgsConstructor
@NoArgsConstructor
@Data
public class UnauthorizedException extends RuntimeException {private String url;private String body;private Method method;}

2.导入重试框架(Spring-Retry)的坐标
Spring提供了重试的功能,使用非常的简单、优雅。

<!--Spring重试模块-->
<dependency><groupId>org.springframework.retry</groupId><artifactId>spring-retry</artifactId>
</dependency>
<dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId>
</dependency>

3.编写统一的请求逻辑
请求逻辑中使用了重试框架
@Retryable用在重试方法上
@Recover用在重试全部失败后执行的方法上

package com.tanhua.dubbo.server.service;import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.Method;
import com.tanhua.dubbo.server.exception.UnauthorizedException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Recover;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Service;/*** 环信接口通用请求服务*/
@Service
@Slf4j
public class RequestService {@Autowiredprivate TokenService tokenService;/*** 通用的发送请求方法(这里使用了一个spring里面的retry重试框架)* 1.向环信rest接口发送的http请求,这里使用的是hutool的一个工具* 2.这里我们在环信中创建的应用使用的是授权模式,每一次请求环信的rest接口都需要校验令牌(放在请求头中携带),切记Bearer ${token}中间有一个空格,* 3.@Retryable 这个是重试注解,放在需要重试的方法上面* @param url    请求地址* @param body   请求参数* @param method 请求方法* @return*///注解参数说明:UnauthorizedException:自定义的异常,触发重试的条件//maxAttempts:最多重试次数//backoff:重试的时间间隔策略,delay表示第一次重试后间隔两秒,multiplier表示以后每次间隔时间进行倍增@Retryable(value = UnauthorizedException.class, maxAttempts = 5, backoff = @Backoff(delay = 2000L, multiplier = 2))public HttpResponse execute(String url, String body, Method method) {String token = this.tokenService.getToken();HttpRequest httpRequest;switch (method) {case POST: {httpRequest = HttpRequest.post(url);break;}case DELETE: {httpRequest = HttpRequest.delete(url);break;}case PUT: {httpRequest = HttpRequest.put(url);break;}case GET: {httpRequest = HttpRequest.get(url);break;}default: {return null;}}//使用hutool的后台发送http请求HttpResponse response = httpRequest.header("Content-Type", "application/json") //设置请求内容类型.header("Authorization", "Bearer " + token)  //设置token,注意bearer后面有一个空格(不可缺),Bearer ${token}.body(body) // 设置请求数据.timeout(20000) // 超时时间.execute(); // 执行请求//返回值401,未授权[无token、token错误、token过期]if (response.getStatus() == 401) {//token失效,重新刷新tokenthis.tokenService.refreshToken();//抛出异常,需要进行重试,重试到最大次数后就会执行下面的recover方法逻辑throw new UnauthorizedException(url, body, method);}return response;}/*** 全部重试失败后执行* @param e 参数异常类型必须和触发重试的异常类型一致,因为这个方法要查看异常中的内容* @return  方法返回值类型也必须和重试方法的返回值类型一致*/@Recoverpublic HttpResponse recover(UnauthorizedException e) {log.error("获取token失败!url = " + e.getUrl() + ", body = " + e.getBody() + ", method = " + e.getMethod().toString());//如果重试5次后,依然不能获取到token,说明网络或账号出现了问题,只能返回null了,后续的请求将无法再执行return null;}
}

@Retryable参数说明:

  • value:抛出指定异常才会重试
  • maxAttempts:最大重试次数,默认3次
  • backoff:重试等待策略,默认使用@Backoff
    • @Backoff 的value默认为1000L,我们设置为2000L;
    • multiplier(指定延迟倍数)默认为0,表示固定暂停1秒后进行重试,如果把multiplier设置为2,则第一次重试为2秒,第二次为4秒,第三次为6秒。

@Recover标注的方法,是在所有的重试都失败的情况下,最后执行该方法,该方法有2个要求:

  • 方法的第一个参数必须是 Throwable 类型,最好与 @Retryable 中的 value一致。
  • 方法的返回值必须与@Retryable的方法返回值一致,否则该方法不能被执行。
  • huanxin模块的引导类如下
package com.tanhua.dubbo.server;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration;
import org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration;
import org.springframework.retry.annotation.EnableRetry;@SpringBootApplication(exclude = {MongoAutoConfiguration.class, MongoDataAutoConfiguration.class}) //排除mongo的自动配置
//开启重试功能
@EnableRetry
public class HuanXinDubboApplication {public static void main(String[] args) {SpringApplication.run(HuanXinDubboApplication.class, args);}
}

- 测试

package com.tanhua.dubbo.server.api;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;@SpringBootTest
@RunWith(SpringRunner.class)
public class TestHuanXinApi {@Autowiredprivate HuanXinApi huanXinApi;@Testpublic void testGetToken(){String token = this.huanXinApi.getToken();System.out.println(token);}@Testpublic void testRegister(){//注册用户id为1的用户到环信System.out.println(this.huanXinApi.register(1L));}@Testpublic void testQueryHuanXinUser(){//根据用户id查询环信用户信息System.out.println(this.huanXinApi.queryHuanXinUser(1L));}
}

SpringBoot+Dubbo+环信(即时通信)整合相关推荐

  1. 环信即时通信jdk,java版本服务器端的集成

    最近用到了环信即时通信云服务,在服务器集成的时候遇到了一些麻烦在这里记录一下: 首先,通过查看环信的开发者文档,地址在这里:http://docs.easemob.com/doku.php?id=st ...

  2. 安卓开发环信即时通信,聊天软件-可实现单聊群聊

    最近花了一个星期的时间在B站跟着老师学了环信即时聊天工具的使用 附上学习网站:https://www.bilibili.com/video/BV1cW411V7yd?p=1 附上自己的代码 https ...

  3. Android环信即时通信集成全过程(含demo)

    最近闲来无事,就使用环信提供的文档写了一篇详细的即时通信的软件, ok 为了感谢老东家的突出贡献 先将环信的详细文档地址贴出来: http://docs-im.easemob.com/im/andro ...

  4. 关于环信即时通信的使用小结

    因项目中涉及即时通讯的模块,老大跟我说用环信的,所以去熟悉了下后搬到项目中使用,环信文档地址点击打开链接 首先应该在AndroidManifest.xml文件中配置环信相关 <!-- 设置环信应 ...

  5. 基于python的MQTT和环信即时通信的MQTT通信

    代码如下: # -*- coding: utf-8 -*- # 以下代码在2021年10月21日 python3.10环境下运行通过 import paho.mqtt.client as mqtt i ...

  6. android环信登录成功但是收不到消息,Android环信即时通信遇到的问题及解决方法...

    导入了EaseUI遇到的问题 问题一:Error:Execution failed for task ':app:transformClassesWithDexForDebug'. > com. ...

  7. ThinkPHP框架整合环信即时通讯DEMO

    环信成立于2013年4月,是一家全通讯能力云服务提供商.产品包括全球最大的即时通讯云 PaaS 平台--环信即时通讯云. 最近在工作中遇到要整合环信即时通讯,通过在网上搜索没有搜到特别全的案例,故此自 ...

  8. 聊天服务器 单机性能,环信即时聊天服务器

    环信即时聊天服务器 内容精选 换一换 本章节通过示例项目"小蝌蚪即时交互游戏"介绍如何使用DevCloud开发基于PHP语言的H5应用.项目名称:小蝌蚪即时交互游戏.项目简介:小蝌 ...

  9. 环信即时通讯IM重大更新助力企业数字化转型

    即时通讯IM是人与人沟通的基础服务,随着线上场景的进一步丰富,用户对于IM的能力要求日益提升.IM本质是一项服务,用户对于体验质量的要求异常严苛,掌握终端用户质量体验的变化和趋势,能够快速发现问题及根 ...

最新文章

  1. linux界面版admin,linux下Nginx+Django Admin界面无样式问题解决方法
  2. 5.7和5.6的mysql_mysql5.6和5.7的区别
  3. android webview点击返回键返回上一页
  4. Google Earth KML格式成为开放式国际标准
  5. java编程思想第四版第十八章总结
  6. JSpider(3):JSpider的结构
  7. 微信仿今日头条导航栏滚动
  8. 【IPC通信】基于管道的popen和pclose函数
  9. 鸿鹄系统和鸿蒙系统区别,鸿蒙系统现身,搭配升降式镜头和鸿鹄芯片,你以为是手机?...
  10. linux root权限_怎样在Linux内核中埋炸弹获取root权限lt;2/2gt;终结篇
  11. oracle 取awr报告,Oracle生成awr报告
  12. 如何快速备份微信聊天记录到电脑
  13. pycharm2019版本去掉下划线的方法
  14. 第二人生的源码分析(10)登录授权的实现过程
  15. 为 Vue 项目添加 cnzz 统计
  16. Burp suite - Burp Clickbandit
  17. .Net Core3.1 Centos离线部署
  18. java+ssm的班级同学录聚会报名网站
  19. Tecplot进阶——如何用Tecplot制作一张满足论文投稿要求的图片
  20. 知识图谱:SPARQL的基本语法示例

热门文章

  1. [石油能源] WIS文件说明及相关算法
  2. Springboot @Scheduled实现原理
  3. RTL8188wifi模块针对IP Camera能优化
  4. OpenCv连通区域分析——Two-Pass 算法区域生长算法
  5. Linux命令——top相关
  6. 调度算法先来先服务(FCFS)、最短作业优先(SJF)和最高响应比优先(HRRN)算法
  7. python中opencv怎么检测双眼_OpenCVPython中的瞳孔检测
  8. bzoj4143: [AMPPZ2014]The Lawyer
  9. 一般办公计算机水平,本以为自己电脑办公水平还可以,直到闯入了这几个网站...
  10. (转载)vue开发实例之按钮的使用