拥抱UniHttp,规范Http接口对接之旅
前言
如果你项目里还在用传统的编程式Http客户端比如HttpClient、Okhttp去直接对接第三方Http接口,
那么你项目一定充斥着大量的对接逻辑和代码, 并且针对不同的对接渠道方需要每次封装一次调用的简化,
一旦封装不好系统将会变得难以维护,难以阅读, 甚至不同的开发同学会用自己的方式用不同的Http客户端用不同的封装逻辑去对接接口,
这种情况一般发生于项目换了维护者,然后代码负责人也没把控代码质量和规范。
如果你的项目里也存在这样的问题,那么UniHttp
就是你的规范你的版本答案。
1、简介
一个声明式的Http接口对接框架
,能以极快的方式完成对一个第三方Http接口的对接和使用,只要配置一下即可重复使用,
不需要开发者去关注如何发送一个请求,如何去传递Http请求参数,以及如何对请求结果进行处理和反序列化,这些框架都帮你一一实现
就像配置 Spring的Controller
那样简单,只不过相当于是反向配置而已
该框架更注重于
如何保持高内聚和可读性高的代码情况下与快速第三方渠道接口进行对接和集成
,而非像传统编程式的Http请求客户端(比如HttpClient、Okhttp)那样专注于如何去发送Http请求
,虽然底层也是用的Okhttp去发送请求。 与其说的是对接的Http接口,不如说是对接的第三方渠道,UniHttp可支持自定义接口渠道方HttpAPI注解以及一些自定义的对接和交互行为 ,为此扩展了发送和响应和反序列化一个Http请求的各种生命周期钩子
,开发者可自行去扩展实现。
2、快速开始
2.1、引入依赖
<dependency><groupId>io.github.burukeyou</groupId><artifactId>uniapi-http</artifactId><version>0.0.4</version></dependency>
2.2、对接接口
在类上标记@HttpApi注解,然后指定请求的域名url, 然后就可以为方法配发去对接哪个接口。
比如下面两个方法的配置则对接了 GET http://localhost:8080/getUser和 POST http://localhost:8080/addUser 两个接口
方法返回值定义成Http响应body对应的类型即可,默认会使用fastjson反序列化Http响应body的值为该类型对象。
@HttpApi(url = "http://localhost:8080")
interface UserHttpApi {@GetHttpInterface("/getUser")BaseRsp<String> getUser(@QueryPar("name") String param,@HeaderPar("userId") Integer id);@PostHttpInterface("/addUser")BaseRsp<Add4DTO> addUser(@BodyJsonPar Add4DTO req);}
@QueryPar 表示将参数值放到Http请求的查询参数内
@HeaderPar 表示将参数值放到Http请求的请求头里
@BodyJsonPar 表示将参数值放到Http请求body内,并且content-type是application/json
1、getUser方法最终构建的Http请求报文为
GET http://localhost:8080/getUser?name=param
Header:userId: id
2、addUser最终构建的Http请求报文为
POST: http://localhost:8080/addUser Header: Content-Type: application/jsonBody:{"id":1,"name":"jay"}
2.3、声明定义的HttpAPI的包扫描路径
在spring的配置类上使用@UniAPIScan注解标记定义的HttpAPI的包扫描路径,会自动为标记了@HttpApi接口生成代理对象并且注入到Spring容器中,
之后只需要像使用Spring的其他bean一样,依赖注入使用即可
@UniAPIScan("com.xxx.demo.api")
@SpringBootApplication
public class DemoApplication {public static void main(String[] args) {SpringApplication.run(DemoApplication.class,args);}
}
2.4 依赖注入使用即可
@Service
class UserAppService {@Autowiredprivate UserHttpApi userHttpApi;public void doSomething(){userHttpApi.getUser("jay",3);}
}
3、说明介绍
3.1、@HttpApi注解
用于标记接口上,该接口上的方法会被代理到对应的Http请求接口,可指定请求的域名,也可指定自定义的Http代理逻辑等等。
3.2、@HttpInterface注解
用于配置一个接口的参数,包括请求方式、请求路径、请求头、请求cookie、请求查询参数等等
并且内置了以下请求方式的@HttpInterface,不必再每次手动指定请求方式
- @PostHttpInterface
- @PutHttpInterface
- @DeleteHttpInterface
- @GetHttpInterface
@PostHttpInterface(// 请求路径path = "/getUser",// 请求头headers = {"clientType:sys-app","userId:99"},// url查询参数 params = {"name=周杰伦","age=1"},// url查询参数拼接字符串paramStr = "a=1&b=2&c=3&d=哈哈&e=%E7%89%9B%E9%80%BC",// cookie 字符串cookie = "name=1;sessionId=999")BaseRsp<String> getUser();
3.3、@Par注解
以下各种Par后缀的注解,主要用于方法参数上,用于指定在发送请求时将参数值放到Http请求体的哪部分上。
为了方便描述,下文描述的普通值就是表示String,基本类型、基本类型的包装类型等类型.
简单复习下Http协议报文
@QueryPar注解
标记Http请求url的查询参数
支持以下方法参数类型的标记: 普通值、普通值集合、对象、Map
@PostHttpInterfaceBaseRsp<String> getUser(@QueryPar("id") String id, // 普通值 @QueryPar("ids") List<Integer> idsList, // 普通值集合@QueryPar User user, // 对象@QueryPar Map<String,Object> map); // Map
如果类型是普通值或者普通值集合需要手动指定参数名,因为是当成单个查询参数传递
如果类型是对象或者Map是当成多个查询参数传递,字段名或者map的key名就是参数名,字段值或者map的value值就是参数值。
如果是对象,参数名默认是字段名,由于用的是fastjson序列化可以用@JSONField指定别名
@PathPar注解
标记Http请求路径变量参数,仅支持标记普通值类型
@PostHttpInterface("/getUser/{userId}/detail")BaseRsp<String> getUser(@PathPar("userId") String id); // 普通值
@HeaderPar注解
标记Http请求头参数
支持以下方法参数类型: 对象、Map、普通值
@PostHttpInterfaceBaseRsp<String> getUser(@HeaderPar("id") String id, // 普通值 @HeaderPar User user, // 对象@HeaderPar Map<String,Object> map); // Map
如果类型是普通值类型需要手动指定参数名,当成单个请求头参数传递. 如果是对象或者Map当成多个请求头参数。
@CookiePar注解
用于标记Http请求的cookie请求头
支持以下方法参数类型: Map、Cookie对象、字符串
@PostHttpInterfaceBaseRsp<String> getUser(@CookiePar("id") String cookiePar, // 普通值 (指定name)当成单个cookie键值对处理@CookiePar String cookieString, // 普通值 (不指定name),当成完整的cookie字符串处理@CookiePar com.burukeyou.uniapi.http.support.Cookie cookieObj, // 单个Cookie对象 @CookiePar List<com.burukeyou.uniapi.http.support.Cookie> cookieList // Cookie对象列表@CookiePar Map<String,Object> map); // Map
如果类型是字符串时,当指定参数名时
,当成单个cookie键值对处理,如果不指定参数名时
当成完整的cookie字符串处理比如a=1;b=2;c=3 这样
如果是Map当成多个cookie键值对处理。
如果类型是内置的 com.burukeyou.uniapi.http.support.Cookie
对象当成单个cookie键值对处理
@BodyJsonPar注解
用于标记Http请求体内容为json形式: 对应content-type为 application/json
支持以下方法参数类型: 对象、对象集合、Map、普通值、普通值集合
@PostHttpInterfaceBaseRsp<String> getUser(@BodyJsonPar String id, // 普通值@BodyJsonPar String[] id // 普通值集合@BodyJsonPar List<User> userList, // 对象集合@BodyJsonPar User user, // 对象@BodyJsonPar Map<String,Object> map); // Map
序列化和反序列化默认用的是fastjson,所以如果想指定别名,可以在字段上标记 @JSONField 注解取别名
@BodyFormPar注解
用于标记Http请求体内容为普通表单形式: 对应content-type为 application/x-www-form-urlencoded
支持以下方法参数类型: 对象、Map、普通值
@PostHttpInterfaceBaseRsp<String> getUser(@BodyFormPar("name") String value, // 普通值@BodyFormPar User user, // 对象@BodyFormPar Map<String,Object> map); // Map
如果类型是普通值类型需要手动指定参数名,当成单个请求表单键值对传递
BodyMultiPartPar注解
用于标记Http请求体内容为复杂形式: 对应content-type为 multipart/form-data
支持以下方法参数类型: 对象、Map、普通值、File对象
@PostHttpInterfaceBaseRsp<String> getUser(@BodyMultiPartPar("name") String value, // 单个表单文本值@BodyMultiPartPar User user, // 对象@BodyMultiPartPar Map<String,Object> map, // Map@BodyMultiPartPar("userImg") File file); // 单个表单文件值
如果参数类型是普通值或者File类型,当成单个表单键值对处理,需要手动指定参数名。
如果参数类型是对象或者Map,当成多个表单键值对处理。 如果字段值或者map的value参数值是File类型,则自动当成是文件表单字段传递处理
@BodyBinaryPar注解
用于标记Http请求体内容为二进制形式: 对应content-type为 application/octet-stream
支持以下方法参数类型: InputStream、File、InputStreamSource
@PostHttpInterfaceBaseRsp<String> getUser(@BodyBinaryPar InputStream value, @BodyBinaryPar File user, @BodyBinaryPar InputStreamSource map);
@ComposePar注解
这个注解本身不是对Http请求内容的配置,仅用于标记一个对象,然后会对该对象内的所有标记了其他@Par注解的字段进行嵌套解析处理,
目的是减少方法参数数量,支持都内聚到一起传递
支持以下方法参数类型: 对象
@PostHttpInterfaceBaseRsp<String> getUser(@ComposePar UserReq req);
比如UserReq里面的字段可以嵌套标记其他@Par注解,具体支持的标记类型和处理逻辑与前面一致
class UserReq {@QueryParprivate Long id;@HeaderParprivate String name;@BodyJsonParprivate Add4DTO req;@CookieParprivate String cook;
}
3.4、原始的HttpResponse
HttpResponse表示Http请求的原始响应对象,如果业务需要关注拿到完整的Http响应,只需要在方法返回值包装返回即可。
如下面所示,此时HttpResponse<Add4DTO>
里的泛型Add4DTO才是代表接口实际返回的响应内容,后续可直接手动获取
@PostHttpInterface("/user-web/get")HttpResponse<Add4DTO> get();
通过它我们就可以拿到响应的Http状态码、响应头、响应cookie等等,当然也可以拿到我们的响应body的内容通过getBodyResult方法
3.5、处理文件下载接口
对于若是下载文件的类型的接口,可将方法返回值定义为 HttpBinaryResponse、HttpFileResponse、HttpInputStreamResponse 的任意一种,
这样就可以拿到下载后的文件。
-
HttpBinaryResponse
: 表示下载的文件内容以二进制形式返回,如果是大文件请谨慎处理,因为会存放在内存中 -
HttpFileResponse
: 表示下载的文件内容以File对象返回,这时文件已经被下载到了本地磁盘 -
HttpInputStreamResponse
: 表示下载的文件内容输入流的形式返回,这时文件其实还没被下载到客户端,调用者可以自行读取该输入流进行文件的下载
3.6、HttpApiProcessor 生命周期钩子
HttpApiProcessor是一个Http请求接口的各种生命周期钩子,开发者可以实现它在里面自定义编写各种对接逻辑。 然后可以配置到@HttpApi注解或者@HttpInterface注解上, 然后框架内部默认会从SpringContext获取,获取不到则手动new一个。
- 通常一个Http请求需要经历 构建请求参数、发送Http请求时,Http响应后获取响应内容、反序列化Http响应内容成具体对象。
目前提供了4种钩子,执行顺序流程如下:
postBeforeHttpMetadata (请求发送前)在发送请求之前,对Http请求体后置处理|VpostSendingHttpRequest (请求发送时)在Http请求发送时处理|VpostAfterHttpResponseBodyString (请求响应后)对响应body文本字符串进行后置处理|VpostAfterHttpResponseBodyResult (请求响应后)对响应body反序列化后的结果进行后置处理|VpostAfterMethodReturnValue (请求响应后)对代理的方法的返回值进行后置处理,类似aop的后置处理
.
1、postBeforeHttpMetadata
: 可在发送http请求之前对请求体进行二次处理,比如加签之类
2、postSendHttpRequest
: Http请求发送时会回调该方法,可以在该方法执行自定义的发送逻辑或者打印发送日志
3、postAfterHttpResponseBodyString
: Http请求响应后,对响应body字符串进行进行后置处理,比如如果是加密数据可以进行解密
4、postAfterHttpResponseBodyResult
: Http请求响应后,对响应body反序列化后的对象进行后置处理,比如填充默认返回值
5、postAfterMethodReturnValue
: Http请求响应后,对代理的方法的返回值进行后置处理,类似aop的后置处理
.
回调参数说明:
-
HttpMetadata
: 表示此次Http请求的请求体,包含请求url,请求头、请求方式、请求cookie、请求体、请求参数等等。 -
HttpApiMethodInvocation
: 继承自MethodInvocation, 表示被代理的方法调用上下文,可以拿到被代理的类,被代理的方法,被代理的HttpAPI注解、HttpInterface注解等信息
3.7、配置自定义的Http客户端
默认使用的是Okhttp客户端,如果要重新配置Okhttp客户端,注入spring的bean即可,如下
@Configuration
public class CusotmConfiguration {@Beanpublic OkHttpClient myOHttpClient(){return new OkHttpClient.Builder().readTimeout(50, TimeUnit.SECONDS).writeTimeout(50, TimeUnit.SECONDS).connectTimeout(10, TimeUnit.SECONDS).connectionPool(new ConnectionPool(20,10, TimeUnit.MINUTES)).build();}
}
4、企业级渠道对接实战
案例背景:
- 假设现在需要对接一个某天气服务的所有接口,需要在请求cookie带上一个token字段和sessionId字段, 这两个字段的值需要每次接口调用前先手动调渠道方的一个特定的接口申请获取,token值在该接口返回值中返回,sessionId在该接口的响应头中返回。然后还需要在请求头上带上一个sign签名字段, 该sign签名字段生成规则需要用渠道方提供的公钥对所有请求体和请求参数进行加签生成。
然后还需要在每个接口的查询参数上都带上一个渠道方分配的客户端appId。
4.1 在application.yml中配置对接渠道方的信息
channel:mtuan:# 请求域名url: http://127.0.0.1:8999# 分配的渠道appIdappId: UUU-asd-01# 分配的公钥publicKey: fajdkf9492304jklfahqq
4.2 自定义该渠道方的HttpAPI注解
假设现在对接的是某团,所以自定义注解叫@MTuanHttpApi
吧,然后需要在该注解上标记@HttpApi注解,并且需要配置processor字段,需要去自定义实现一个HttpApiProcessor这个具体实现后续讲。
有了这个注解后就可以自定义该注解与对接渠道方相关的各种字段配置,当然也可以不定义。 注意这里url的字段是使用 @AliasFor(annotation = HttpApi.class),
这样构建的HttpMetadata中会默认解析填充要请求体,不标记则也可自行处理。
@Inherited
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@HttpApi(processor = MTuanHttpApiProcessor.class)
public @interface MTuanHttpApi {/*** 渠道方域名地址*/@AliasFor(annotation = HttpApi.class)String url() default "${channel.mtuan.url}";/*** 渠道方分配的appId*/String appId() default "${channel.mtuan.appId}";
}
@Slf4j
@Component
public class MTuanHttpApiProcessor implements HttpApiProcessor<MTuanHttpApi> {}
注意实现的HttpApiProcessor泛型要指定为刚才定义的注解@MTuanHttpApi类型,因为这个HttpApiProcessor配置到它上面,如果需要通用处理可以定义为Annocation类型
4.3 对接接口
有了@MTuanHttpApi注解之后就可以开始对接接口了,比如假设有两个接口要对接。一个就是前面说的获取令牌的接口。 一个是获取天气情况的接口。
- 为什么getToken方法返回值是
HttpResponse
, 这是UniHttp内置的原始Http响应对象,方便我们去拿到原始Http响应体的一些内容(比如响应状态码、响应cookie)。
其中的泛型BaseRsp才是实际的Http响应体反序列化后的内容。 而getCityWeather方法没有使用HttpResponse包装,
BaseRsp只是单纯Http响应体反序列化后的内容,这是两者的区别。 前面介绍过HttpResponse
,其实大部份接口是不关注HttpResponse的可以不用去配置。
@MTuanHttpApi
public interface WeatherApi {/*** 根据城市名获取天气情况*/@GetHttpInterface("/getCityByName")BaseRsp<WeatherDTO> getCityWeather(@QueryPar("city") String cityName);/*** 根据appId和公钥获取令牌*/@PostHttpInterface("/getToken")HttpResponse<BaseRsp<TokenDTO>> getToken(@HeaderPar("appId") String appId, @HeaderPar("publicKey")String publicKey);}
4.4、自定义HttpApiProcessor
在之前我们自定义了一个@MTuanHttpApi注解上指定了一个MTuanHttpApiProcessor,接下来我们去实现他的具体内容为了实现我们案例背景里描述的功能。
@Slf4j
@Component
public class MTuanHttpApiProcessor implements HttpApiProcessor<MTuanHttpApi> {/*** 渠道方分配的公钥*/@Value("${channel.mtuan.publicKey}")private String publicKey;@Value("${channel.mtuan.appId}")private String appId;@Autowiredprivate Environment environment;@Autowiredprivate WeatherApi weatherApi;/** 实现-postBeforeHttpMetadata: 发送Http请求之前会回调该方法,可对Http请求体的内容进行二次处理** @param httpMetadata 原来的请求体* @param methodInvocation 被代理的方法* @return 新的请求体*/@Overridepublic HttpMetadata postBeforeHttpMetadata(HttpMetadata httpMetadata, HttpApiMethodInvocation<MTuanHttpApi> methodInvocation) {/*** 在查询参数中添加提供的appId字段*/// 获取MTuanHttpApi注解MTuanHttpApi apiAnnotation = methodInvocation.getProxyApiAnnotation();// 获取MTuanHttpApi注解的appId,由于该appId是环境变量所以我们从environment中解析取出来String appIdVar = apiAnnotation.appId();appIdVar = environment.resolvePlaceholders(appIdVar);// 添加到查询参数中httpMetadata.putQueryParam("appId",appIdVar);/*** 生成签名sign字段*/// 获取所有查询参数Map<String, Object> queryParam = httpMetadata.getHttpUrl().getQueryParam();// 获取请求体参数HttpBody body = httpMetadata.getBody();// 生成签名String signKey = createSignKey(queryParam,body);// 将签名添加到请求头中httpMetadata.putHeader("sign",signKey);return httpMetadata;}private String createSignKey(Map<String, Object> queryParam, HttpBody body) {// todo 伪代码// 1、将查询参数拼接成字符串String queryParamString = queryParam.entrySet().stream().map(e -> e.getKey() + "="+e.getValue()).collect(Collectors.joining(";"));// 2、将请求体参数拼接成字符串String bodyString = "";if (body instanceof HttpBodyJSON){// application/json 类型的请求体bodyString = body.toStringBody();}else if (body instanceof HttpBodyFormData){// application/x-www-form-urlencoded 类型的请求体bodyString = body.toStringBody();}else if (body instanceof HttpBodyMultipart){// multipart/form-data 类型的请求体bodyString = body.toStringBody();}// 使用公钥publicKey 加密拼接起来String sign = publicKey + queryParamString + bodyString;try {MessageDigest md = MessageDigest.getInstance("SHA-256");byte[] digest = md.digest(sign.getBytes());return new String(digest);} catch (NoSuchAlgorithmException e) {throw new RuntimeException(e);}}/*** 实现-postBeforeHttpMetadata: 发送Http请求时,可定义发送请求的行为 或者打印请求和响应日志。*/@Overridepublic HttpResponse<?> postSendHttpRequest(HttpSender httpSender, HttpMetadata httpMetadata) {// 忽略 weatherApi.getToken的方法回调,否则该方法也会回调此方法会递归死循环。 或者该接口指定自定义的HttpApiProcessor重写postSendingHttpRequestMethod getTokenMethod = ReflectionUtils.findMethod(WeatherServiceApi.class, "getToken",String.class,String.class);if (getTokenMethod == null || getTokenMethod.equals(methodInvocation.getMethod())){return httpSender.sendHttpRequest(httpMetadata);}// 1、动态获取token和sessionIdHttpResponse<String> httpResponse = weatherApi.getToken(appId, publicKey);// 从响应体获取令牌tokenString token = httpResponse.getBodyResult();// 从响应头中获取sessionIdString sessionId = httpResponse.getHeader("sessionId");// 把这两个值放到此次的请求cookie中httpMetadata.addCookie(new Cookie("token",token));httpMetadata.addCookie(new Cookie("sessionId",sessionId));log.info("开始发送Http请求 请求接口:{} 请求体:{}",httpMetadata.getHttpUrl().toUrl(),httpMetadata.toHttpProtocol());// 使用框架内置工具实现发送请求HttpResponse<?> rsp = httpSender.sendHttpRequest(httpMetadata);log.info("开始发送Http请求 响应结果:{}",rsp.toHttpProtocol());return rsp;}/*** 实现-postAfterHttpResponseBodyResult: 反序列化后Http响应体的内容后回调,可对该结果进行二次处理返回* @param bodyResult Http响应体反序列化后的结果* @param rsp 原始Http响应对象* @param method 被代理的方法* @param httpMetadata Http请求体*/@Overridepublic Object postAfterHttpResponseBodyResult(Object bodyResult, HttpResponse<?> rsp, Method method, HttpMetadata httpMetadata) {if (bodyResult instanceof BaseRsp){BaseRsp baseRsp = (BaseRsp) bodyResult;// 设置baseRsp.setCode(999);}return bodyResult;}
}
上面我们分别重写了postBeforeHttpMetadata、postSendHttpRequest、postAfterHttpResponseBodyResult三个生命周期的钩子方法去完成我们的需求,在发送请求前对请求体进行加签、在发送请求时动态获取令牌重新构建请求体和打印日志、在发送请求后给响应对象设置code为999。
最后
gitHub代码地址,如果觉得项目有用,可以star下感谢
相关文章:
拥抱UniHttp,规范Http接口对接之旅
前言 如果你项目里还在用传统的编程式Http客户端比如HttpClient、Okhttp去直接对接第三方Http接口, 那么你项目一定充斥着大量的对接逻辑和代码, 并且针对不同的对接渠道方需要每次封装一次调用的简化, 一旦封装不好系统将会变得难以维护&am…...
Python 给存入 Redis 的键值对设置过期时间
Redis 是一种内存中的数据存储系统,与许多传统数据库相比,它具有一些优势,其中之一就是可以设置数据的过期时间。通过 Redis 的过期时间设置,可以为存储在 Redis 中的数据设置一个特定的生存时间。一旦数据到达过期时间࿰…...
在linux中安装docker
文章目录 1、安装依赖2、安装docker的下载源3、安装docker4、设置Docker服务开机自启 1、安装依赖 sudo yum install -y yum-utils2、安装docker的下载源 sudo yum-config-manager \--add-repo \https://download.docker.com/linux/centos/docker-ce.repohttps://download.do…...
【JVM-04】线上CPU100%
【JVM-04】线上CPU100% 1. 如何排查2. 再举一个例子 1. 如何排查 ⼀般CPU100%疯狂GC,都是死循环的锅,那怎么排查呢?先进服务器,⽤top -c 命令找出当前进程的运⾏列表按⼀下 P 可以按照CPU使⽤率进⾏排序显示Java进程 PID 为 2609…...
try catch 解决大问题
项目开发中遇到一个棘手的bug,react前端项目独自运行时一切正常,但是把项目集成到使用wujie的大平台微前端项目中之后,突然有个地方无故报错,导致程序运行停止,后续的方法不再执行。报错如下: DOMExceptio…...
手动解析Collection
即将被解析的json {"collection": {"templates": [{"data": [{"name": "plantCode","value": "MSHG_KFXHS02"}, {"name": "details","value": [{"plantMedicament…...
list模拟实现【C++】
文章目录 全部的实现代码放在了文章末尾准备工作包含头文件定义命名空间类的成员变量为什么节点类是用struct而不是class呢?为什么要写get_head_node? 迭代器迭代器在list类里的实例化和重命名普通迭代器operator->()的作用是什么? const迭代器反向迭…...
nginx正向代理、反向代理、负载均衡
nginx.conf nginx首要处理静态页面 反向代理 动态请求 全局模块 work processes 1; 设置成服务器内核数的两倍(一般不不超过8个超过8个反而会降低性能一般4个 1-2个也可以) netstat -antp | grep 80 查端口号 *1、events块:* 配置影响ngi…...
matlab 有倾斜的椭圆函数图像绘制
matlab 有倾斜的椭圆函数图像绘制 有倾斜的椭圆函数图像绘制xy交叉项引入斜线负向斜线成分正向斜线成分 x^2 y^2 xy 1 (负向)绘制结果 x^2 y^2 - xy 1 (正向)绘制结果 有倾斜的椭圆函数图像绘制 为了确定椭圆的长轴和短轴的…...
PTK是如何加密WLAN单播数据帧的?
1. References WLAN 4-Way Handshake如何生成PTK?-CSDN博客 2. 概述 在Wi-Fi网络中,单播、组播和广播帧的加密算法是由AP决定的。其中单播帧的加密使用PTK密钥,其PTK的密钥结构如下图所示: PTK的组成如上图所示,由K…...
Django之登录权限系统
本文参考链接django之auth模块(用户认证) - chchcharlie、 - 博客园 (cnblogs.com) 执行完迁移命令,会自动生成admin表,迁移命令如下: python manage.py makemigrations python manage.py migrate 相关模块 from django.contrib …...
rust way step 1
install rust CARGO_HOME D:\rust\.cargo RUSTUP_HOME D:\rust\.rustup [dependencies] ferris-says "0.2" vscode 安装rust 插件 use ferris_says::say; // from the previous step use std::io::{stdout, BufWriter};fn main() {let stdout stdout();let m…...
视觉语言模型导论:这篇论文能成为你进军VLM的第一步
近些年,语言建模领域进展非凡。Llama 或 ChatGPT 等许多大型语言模型(LLM)有能力解决多种不同的任务,它们也正在成为越来越常用的工具。 这些模型之前基本都局限于文本输入,但现在也正在具备处理视觉输入的能力。如果…...
Postman工具基本使用
一、安装及基本使用 安装及基本使用参见外网文档:全网最全的 postman 工具使用教程_postman使用-CSDN博客 建议版本:11以下,比如10.x.x版本。11版本以后貌似是必须登录使用 二、禁止更新 彻底禁止postman更新 - 简书 host增加࿱…...
uni-app三部曲之三: 路由拦截
1.引言 路由拦截,个人理解就是在页面跳转的时候,增加一级拦截器,实现一些自定义的功能,其中最重要的就是判断跳转的页面是否需要登录后查看,如果需要登录后查看且此时系统并未登录,就需要跳转到登录页&…...
专注于国产FPGA芯片研发的异格技术Pre-A+轮融资,博将控股再次投资
近日,苏州异格技术有限公司(以下简称“异格技术”)宣布成功完成数亿元的Pre-A轮融资,由博将控股在参与Pre-A轮投资后,持续投资。这标志着继2022年获得经纬中国、红点中国、红杉中国等机构数亿元天使轮融资后࿰…...
【python】QWidget父子关系,控件显示优先级原理剖析与应用实战演练
✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,…...
CTF php RCE(三)
0x07 日志文件包含 判断类型 使用kali curl -I urlF12 打开F12开发者工具,选中之后F5刷新查看server类型即可 配置文件 直接包含或者访问如果有回显就是, NGINX:NGINX 的配置文件通常位于 /etc/nginx/ 目录下,具体的网站配…...
Android 注解的语法原理和使用方法
Android 注解的语法原理和使用方法 关于我 在 Android 开发中,注解(Annotation)是一种强大的工具,用于在代码中添加元数据。注解可以简化代码、提高可读性、减少样板代码,并且在一定程度上增强编译时的类型检查。本文…...
YOLOv10改进 | Conv篇 | 利用FasterBlock二次创新C2f提出一种全新的结构(全网独家首发,参数量下降70W)
一、本文介绍 本文给大家带来的改进机制是利用FasterNet的FasterBlock改进特征提取网络,将其用来改进ResNet网络,其旨在提高计算速度而不牺牲准确性,特别是在视觉任务中。它通过一种称为部分卷积(PConv)的新技术来减少…...
实验-ENSP实现防火墙区域策略与用户管理
目录 实验拓扑 自己搭建拓扑 实验要求 实验步骤 整通总公司内网 sw3配置vlan 防火墙配置IP 配置安全策略(DMZ区内的服务器,办公区仅能在办公时间内(9: 00- 18:00)可以访问,生产区的设备全天可以访问) 配置nat策…...
【游戏客户端】大话slg玩法架构(二)背景地图
【游戏客户端】大话slg玩法架构(二)背景地图 大家好,我是Lampard家杰~~ 今天我们继续给大家分享SLG玩法的实现架构,关于SLG玩法的介绍可以参考这篇上一篇文章:【游戏客户端】制作率土之滨Like玩法 PS:和之前…...
git-工作场景
1. 远程分支为准 强制切换到远程分支并忽略本地未提交的修改 git fetch origin # 获取最新的远程分支信息 git reset --hard origin/feature_server_env_debug_20240604 # 强制切换到远程分支,并忽略本地修改 2. 切换分支 1. **查看所有分支:**…...
coco dataset标签数据结构(json文件)
COCO数据集现在有3种标注类型:object instances(目标实例), object keypoints(目标上的关键点), 和image captions(看图说话),使用json文件存储。 NameImagesLabelstrain linkhttp:…...
GaussDB关键技术原理:高性能(四)
GaussDB关键技术原理:高性能(三)从查询重写RBO、物理优化CBO、分布式优化器、布式执行框架、轻量全局事务管理GTM-lite等五方面对高性能关键技术进行了解读,本篇将从USTORE存储引擎、计划缓存计划技术、数据分区与分区剪枝、列式存…...
总结之企业微信(一)——创建外部群二维码,用户扫码入群
创建外部群 企微接口中没有直接通过服务端API接口创建外部群 可以通过jssdk创建外部群:引用jssdk调用会话接口wx.openEnterpriseChat https://work.weixin.qq.com/api/doc/90000/90136/90511 创建外部群二维码 需要通过企业微信的应用,并且配置客户联…...
透视数据治理:企业如何衡量数据治理的效果?
在企业运营中,各个业务部门的成功与否都是直观且易于量化的,像销售部门卖了多少产品又为企业带来多少盈利,这些都能用具体的数字来说话。但当谈到数据治理的成效时,许多企业与决策者却感到迷茫。 数据治理的重要性不言而喻&#…...
ERC20查询操作--获取ERC20 Token的余额
获取ERC20 Token的余额 https://blog.csdn.net/wypeng2010/article/details/81362562 通过REST查询 curl -X POST --data-binary {"jsonrpc":"2.0","method":"eth_call","params":[{"from": "0x954d1a58c7a…...
Linux运维:MySQL中间件代理服务器,mycat读写分离应用实验
Mycat适用的场景很丰富,以下是几个典型的应用场景: 1.单纯的读写分离,此时配置最为简单,支持读写分离,主从切换 2.分表分库,对于超过1000万的表进行分片,最大支持1000亿的单表分片 3.多租户应…...
css文字自适应宽度动态出现省略号...
前言 在列表排行榜中通常会出现的一个需求:从左到右依次是名次、头像、昵称、徽标、分数。徽标可能会有多个或者没有徽标,徽标长度是动态的,昵称如果过长要随着有无徽标进行动态截断出现省略号。如下图布局所示(花里胡哨的底色是…...
外贸独立网站如何推广/如何做免费网站推广
https://mp.weixin.qq.com/s/B5xiVeW22ZumbI9KfrYJSghttps://mp.weixin.qq.com/s/nUuOofsQg2OjFdVsK4s05w 转载于:https://www.cnblogs.com/zhangww/p/9961697.html...
网站建设案例基本流程图/枸橼酸西地那非片的作用及功效
sudo npm install -g n固定版本:sudo n 12.18.3; 最新版本:n lastest; 稳定版本:n stable...
ipad网站开发/如何做网络营销?
还在用mvn archetype:create -DarchetypeArtifactIdmaven-archetype-quickstart -DgroupIdcom.ryanote -Dartifactcommon这样的方式来创建mvn项目吗?一长串需要输入的名字让人记着头疼,更要命的是,你不知道有哪些可用的archetypeArtifactId和…...
app开发公司 弙东/谷歌seo课程
软文推广比传统推广更加容易让大众接受,因为其“含蓄”的软文营销植入广告营销被作为整合营销传播的一个重要支点。软文推广也有自己的特点和禁区,不是随随便便就能拿捏准确,只有明白了软文营销的特殊性才能玩儿得转。 1、不了解软文推广相关…...
上海建站网站的企业/海外推广方案
四、链表 链表常用算法及思想:快慢指针、哈希表 注意点:注意链表的边界情况,如头结点 4.1删除链表的倒数第n个结点 原题链接 给定一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。 输入:h…...
山东莱芜疫情最新消息/seo课程培训
数组和链表数组:所谓数组就是相同数据类型的元素按照一定顺序排列的集合。它的存储区间是连续的,占用内存严重,所以空间复杂度很大,为o(n),但是数组的二分查找时间复杂度很小为o(1)。特点是大小固定,不可变,…...