Gateway网关参数进行验签POST 包含requestbody 请求体封装
Gateway网关自定义拦截器的不可重复读取数据
特别注意一点, 因为在网关层 拿出 request 流之后,必须重写getbody()方法把所有的参数放进去,否则后面转发的请求无法接收到任何数据,
坑,巨坑,因为版本问题网上很多都不能兼容,
我的springboot环境 依赖包
<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.2.8.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><!-- gateway版本号, 必须对应,此版本已经包含 web包 --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId><version>2.1.0.RELEASE</version></dependency><!-- servlet 验证post请求需要重写request流 --><dependency><groupId>javax.servlet</groupId><artifactId>servlet-api</artifactId><version>2.5</version><scope>provided</scope></dependency>
需求描述:前端发起请求的参数携带sign=xxxx,后台验证签名是够正确
sign签名生成规则:
1.将post请求的body转成jsonstring (按照body里key的自然升序排列),
2 get请求的话, 把所有参数都进行排序,生成sign与前端传来的值进行验证对比
下面是非对称加密算法工 具
ApiAuthAES 类工具
package com.platform.platformgateway.util;import org.springframework.util.StringUtils;import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.SecretKeySpec;
import java.math.BigInteger;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;/*** AES的加密和解密** @author wxq*/
public class ApiAuthAES {// 密钥// private static final String KEY = "c542384322662d446b2302faf2ab3737";// 算法private static final String ALGORITHMSTR = "AES/ECB/PKCS5Padding";/*** 将byte[]转为各种进制的字符串** @param bytes byte[]* @param radix 可以转换进制的范围,从Character.MIN_RADIX到Character.MAX_RADIX,超出范围后变为10进制* @return 转换后的字符串*/public static String binary(byte[] bytes, int radix) {return new BigInteger(1, bytes).toString(radix);}/*** base 64 encode** @param bytes 待编码的byte[]* @return 编码后的base 64 code*/public static String base64Encode(byte[] bytes) {Base64.Encoder encoder = Base64.getEncoder();return encoder.encodeToString(bytes);}/*** base 64 decode** @param base64Code 待解码的base 64 code* @return 解码后的byte[]* @throws Exception*/public static byte[] base64Decode(String base64Code) throws Exception {Base64.Decoder decoder = Base64.getDecoder();return StringUtils.isEmpty(base64Code) ? null : decoder.decode(base64Code);}/*** AES加密** @param content 待加密的内容* @param encryptKey 加密密钥* @return 加密后的byte[]* @throws Exception*/public static byte[] aesEncryptToBytes(String content, String encryptKey) throws Exception {KeyGenerator kgen = KeyGenerator.getInstance("AES");kgen.init(128);Cipher cipher = Cipher.getInstance(ALGORITHMSTR);cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(encryptKey.getBytes(), "AES"));return cipher.doFinal(content.getBytes("utf-8"));}/*** AES加密为base 64 code** @param content 待加密的内容* @param encryptKey 加密密钥* @return 加密后的base 64 code* @throws Exception*/public static String aesEncrypt(String content, String encryptKey) throws Exception {return base64Encode(aesEncryptToBytes(content, encryptKey));}/*** AES解密** @param encryptBytes 待解密的byte[]* @param decryptKey 解密密钥* @return 解密后的String* @throws Exception*/public static String aesDecryptByBytes(byte[] encryptBytes, String decryptKey) throws Exception {KeyGenerator kgen = KeyGenerator.getInstance("AES");kgen.init(128);Cipher cipher = Cipher.getInstance(ALGORITHMSTR);cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(decryptKey.getBytes(), "AES"));byte[] decryptBytes = cipher.doFinal(encryptBytes);return new String(decryptBytes);}/*** 将base 64 code AES解密** @param encryptStr 待解密的base 64 code* @param decryptKey 解密密钥* @return 解密后的string* @throws Exception*/public static String aesDecrypt(String encryptStr, String decryptKey) throws Exception {return StringUtils.isEmpty(encryptStr) ? null : aesDecryptByBytes(base64Decode(encryptStr), decryptKey);}public static String urlEncode(String str) {try {return URLEncoder.encode(str, "GBK");} catch (Exception e) {e.printStackTrace();}return "";}public static String urlDncode(String str) throws Exception {return URLDecoder.decode(str, "GBK");}public static Map<String, String> URLRequestParamMap(String strUrlParam) {Map<String, String> mapRequest = new HashMap<String, String>();String[] arrSplit = null;if (StringUtils.isEmpty(strUrlParam)) {return mapRequest;}arrSplit = strUrlParam.split("[&]");for (String strSplit : arrSplit) {String[] arrSplitEqual = null;arrSplitEqual = strSplit.split("[=]");//解析出键值if (arrSplitEqual.length > 1) {//正确解析mapRequest.put(arrSplitEqual[0], arrSplitEqual[1]);} else {if (arrSplitEqual[0] != "") {//只有参数没有值,不加入mapRequest.put(arrSplitEqual[0], "");}}}return mapRequest;}public static void main(String[] args) throws Exception {Long time = System.currentTimeMillis();System.out.println("signature:"+aesEncrypt(System.currentTimeMillis()+"","WYEB77T")); ;System.out.println(time); ;Map map = new HashMap();map.put("flag", "system");map.put("dateStr", "2022-09-01");map.put("time", time);System.out.println("signature:" + SignUtil.createSign(map, "WYEBHWgS"));Map map1 = new HashMap();map1.put("startPage", 0);map1.put("pageSize", 10);
//不能嵌套签名,无法解析/* Map map2 = new HashMap();map2.put("app_id","app_id");map1.put("conditions",map2);
*/map1.put("time", time);System.out.println("signature:" + SignUtil.createSign(map1, "WYEB7qf9O1lg"));}}
RequestWrapper 处理body流对象封装,这里使用缓存流处理, 是避免body请求数据太多,导致截取失败的问题
RequestWrapper.java
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import reactor.core.publisher.Flux;import java.io.IOException;
import java.io.InputStream;
import java.nio.CharBuffer;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.atomic.AtomicReference;/*** @Classname TestReQuest* @Description TODO* @Date 2023/5/24 17:32* @Created by mingcai*/
@Slf4j
@Component
public class RequestWrapper {@Autowiredprivate ObjectMapper objectMapper;public String getRequestBodyDoctor(Flux<DataBuffer> bodys) {String resultJson = "";Flux<DataBuffer> body3 = bodys;InputStreamHolder holder = new InputStreamHolder();body3.subscribe(buffer -> holder.inputStream = buffer.asInputStream());if (null != holder.inputStream) {// 解析JSON的节点JsonNode jsonNode = null;try {jsonNode = objectMapper.readTree(holder.inputStream);} catch (IOException e) {throw new RuntimeException(e);}Assert.isTrue(jsonNode instanceof ObjectNode, "JSON格式异常");ObjectNode objectNode = (ObjectNode) jsonNode;// JSON节点最外层写入新的属性//objectNode.put("userId", "accessToken");// DataBuffer dataBuffer = dataBufferFactory.allocateBuffer();String json = objectNode.toString();log.info("最终的JSON数据为:{}", json);// this.setBodyString(json);return json;//dataBuffer.write(json.getBytes(StandardCharsets.UTF_8));//Flux.just(dataBuffer);}return resultJson;}private class InputStreamHolder {InputStream inputStream;}//也是取不到值public static String resolveBodyFromRequest(Flux<DataBuffer> body){AtomicReference<String> bodyRef = new AtomicReference<>();// 缓存读取的request body信息body.subscribe(dataBuffer -> {CharBuffer charBuffer = StandardCharsets.UTF_8.decode(dataBuffer.asByteBuffer());DataBufferUtils.release(dataBuffer);bodyRef.set(charBuffer.toString());});//获取request bodyreturn bodyRef.get();}}
重点来了 ,全局的过滤器, 我把重点画出来了, 至少坑了一天时间,各种尝试都无果,
最终就封装成下面这样, 返回参数无法是一个
ServerHttpRequest, 导致我一直不能有效获取参数,每次读取之后,无法重新放入流中

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.platform.platformgateway.config.db.ConfigBean;
import com.platform.platformgateway.config.redis.BaseRedisCache;
import com.platform.platformgateway.constant.CommonConstants;
import com.platform.platformgateway.util.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.*;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;import javax.annotation.Resource;
import java.net.URI;
import java.util.*;/*** @author mingcai*/@Slf4j
@Configuration
public class AccessGatewayFilter implements GlobalFilter, Ordered {@Resourceprivate BaseRedisCache redisCache;@Resourceprivate ConfigBean configBean;@Resourceprivate ThreadPoolTaskExecutor asyncExecutor;@ResourceRequestWrapper requestWrapper;@Overridepublic Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {ServerHttpRequest originalRequest = exchange.getRequest();String token = originalRequest.getHeaders().getFirst("token");String appId = originalRequest.getHeaders().getFirst("appId");String dateTime = "";// = originalRequest.getHeaders().getFirst("time");String signature = originalRequest.getHeaders().getFirst("signature");ServerHttpResponse response = exchange.getResponse();URI originalRequestUrl = originalRequest.getURI();String path = originalRequest.getPath().toString();log.info("请求路径:" + originalRequestUrl + " service Path:" + path + " 访问IP: " + originalRequestUrl.getHost());if (StringUtils.isNotBlank(path) && path.toLowerCase().contains(CommonConstants.V2_PATH)) {token = redisCache.get(CommonConstants.V2_TOKEN_CACHE);}if (StringUtils.isNotBlank(path) && path.toLowerCase().contains(CommonConstants.V3_PATH)) {token = redisCache.get(CommonConstants.V3_TOKEN_CACHE);}// 特殊处理/* if (path.contains(CommonConstants.V2_FIND_STUDENT_URL)) {ServerWebExchange build = getServerWebExchange(exchange, token);return chain.filter(build);}*/if (StringUtils.isBlank(signature)) {response.setStatusCode(HttpStatus.UNAUTHORIZED);log.debug("signature: 为空! ");return returnJson(response, "用户的signature参数不能为空!");}String secret = (String) redisCache.get_obj(CommonConstants.PLATFORM_CACHE + appId);if (StringUtils.isBlank(secret)) {return returnJson(response, "用户的appId错误,请核验后重试");}String method = String.valueOf(originalRequest.getMethod());String contentType = originalRequest.getHeaders().getFirst("Content-Type");try {if ("GET".equals(method)) {dateTime = originalRequest.getQueryParams().getFirst("time");if (!doGet(originalRequest, secret, signature)) {return returnJson(response, "get请求参数验证失败");}} else if ("POST".equals(method) && !Objects.requireNonNull(contentType).startsWith("multipart/form-data")) {//当body中没有缓存时,只会执行这一个拦截器, 原因是fileMap中的代码没有执行,所以需要在波多野为空时构建一个空的缓存DefaultDataBufferFactory defaultDataBufferFactory = new DefaultDataBufferFactory();DefaultDataBuffer defaultDataBuffer = defaultDataBufferFactory.allocateBuffer(0);// mediaTypeFlux<DataBuffer> bodyDataBuffer = exchange.getRequest().getBody().defaultIfEmpty(defaultDataBuffer);String finalToken = token;return DataBufferUtils.join(bodyDataBuffer).flatMap(dataBuffer -> {DataBufferUtils.retain(dataBuffer);Flux<DataBuffer> cachedFlux = Flux.defer(() -> Flux.just(dataBuffer.slice(0, dataBuffer.readableByteCount())));ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(exchange.getRequest()) {@Overridepublic Flux<DataBuffer> getBody() {return cachedFlux;}};String json = requestWrapper.getRequestBodyDoctor(cachedFlux);JSONObject jb = JSONObject.parseObject(json);if (null == jb) {return returnJson(response, "post请求参数为空");}String time = String.valueOf(jb.getOrDefault("time", ""));log.info("str: " + time);if (!doPost(jb, secret, signature)) {return returnJson(response, "post请求参数验证失败");}Mono<Void> verifyMono = verifyUser(exchange, exchange.getResponse(), exchange.getRequest(), time);if (null != verifyMono) {return verifyMono;}ServerWebExchange newexchange = exchange.mutate().request(mutatedRequest).build();return chain.filter(getServerWebExchange(newexchange, finalToken, null));});} else {return returnJson(response, "不支持的请求方式,仅支持(GET,POST)");}} catch (Exception e) {// throw new RuntimeException(e);log.error(" 签名错误! {}", e.getMessage());return returnJson(response, method + " 签名错误");}Mono<Void> verifyMono = verifyUser(exchange, exchange.getResponse(), exchange.getRequest(), dateTime);if (null != verifyMono) {return verifyMono;}ServerWebExchange build = getServerWebExchange(exchange, token, originalRequest);return chain.filter(build);}private static ServerWebExchange getServerWebExchange(ServerWebExchange exchange, String token,ServerHttpRequest serverHttpRequest) {ServerHttpRequest host = exchange.getRequest().mutate().header("Authorization", token).header("userTokenHead",token).header("businessId", "10000000100001").header("serviceGroup", "sky").build();return exchange.mutate().request(host).build();}@Overridepublic int getOrder() {return -200;}private String authorUser(ServerHttpRequest originalRequest) {String appId = originalRequest.getHeaders().getFirst("appId");String path = originalRequest.getPath().toString();if (StringUtils.isBlank(appId)) {return "用户的appId和secret不能为空";}String secret = (String) redisCache.get_obj(CommonConstants.PLATFORM_CACHE + appId);if (StringUtils.isBlank(secret)) {return "用户的appId错误,请核验后重试";}// 用户权限接口if (null == redisCache.get_obj(CommonConstants.PLATFORM_CACHE + appId + path)) {return "用户" + appId + "无此接口权限";}return "";}/*** GET请求*/public Boolean doGet(ServerHttpRequest request,String secret,String sign) throws Exception {//从request获取到所有的参数及其值String queryParams = request.getQueryParams().toString();if (queryParams == null) {return false;}MultiValueMap<String, String> pNames = request.getQueryParams();Map<String, Object> map = new HashMap<>();for (String entry:pNames.keySet()){Object pValue = request.getQueryParams().getFirst(entry);map.put(entry, pValue);}String newSign = SignUtil.createSign(map,secret);if (!newSign.equals(sign)) {//returnJson(response, "get: signature 签名错误");return false;}return true;}public Boolean doPost(JSONObject json, String secret, String sign) {try {// String sign = (String) json.get("signature");String newSign = SignUtil.createSign(json, secret);if (!Objects.equals(newSign, sign)) {return false;}} catch (Exception e) {//throw new RuntimeException(e);log.error("参数请求错误: {}", e.getMessage());}return true;}public Mono<Void> verifyUser(ServerWebExchange exchange,ServerHttpResponse response,ServerHttpRequest serverHttpRequest,String dateTime) {String path = serverHttpRequest.getPath().toString();String appId = serverHttpRequest.getHeaders().getFirst("appId");if (StringUtils.isBlank(dateTime)) {response.setStatusCode(HttpStatus.UNAUTHORIZED);log.debug("用户的time时间戳参数不能为空");return returnJson(response, "用户的time时间戳参数不能为空!");}Long time = Long.valueOf(dateTime);Long nowTime = System.currentTimeMillis();//5分钟if (nowTime - time > 300000) {response.setStatusCode(HttpStatus.UNAUTHORIZED);log.debug("用户的time时间戳过期");return returnJson(response, "用户的time时间戳过期!");}// 从request对象中获取客户端ipString clientIp = Objects.requireNonNull(serverHttpRequest.getRemoteAddress()).getHostString();String authUserMsg = authorUser(serverHttpRequest);if (StringUtils.isNotBlank(authUserMsg)) {//401 网关层没有权限response.setStatusCode(HttpStatus.UNAUTHORIZED);return returnJson(response, authUserMsg);} else {//记录日志try {asyncExecutor.execute(() -> {JSONObject json = new JSONObject();Map params = serverHttpRequest.getQueryParams();json.put("path", path);json.put("clientIp", clientIp);json.put("params", params);json.put("appId", appId);OkHttpClientUtils.postJsonParams(configBean.getPlatformUrl() + "/hoe/platform/log/save", json.toString());});} catch (Exception e) {log.error("保存日志错误: {} ", e.getMessage());}}return null;}/*** 错误信息响应到客户端* @param mes response* @date: 2023/5/6 14:13*/private Mono<Void> returnJson(ServerHttpResponse response, String mes) {log.info(mes);JSONObject json = new JSONObject();json.put("msg", mes);String message = JSON.toJSONString(json);response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");DataBuffer dataBuffer = response.bufferFactory().wrap(message.getBytes());return response.writeWith(Flux.just(dataBuffer));}}
对于这种封装方式,我是不太满意的,
return DataBufferUtils.join(bodyDataBuffer).flatMap(dataBuffer -> {} 这部分应该重新定义,然后统一放到后面处理的, 目前不知道怎么拆分,如果有知道的大佬,麻烦分享一下了!
下面是排序验签的工具类
import java.util.Comparator;
import java.util.Map;
import java.util.TreeMap;/*** @Classname SignUtil* @Description TODO* @Date 2023/5/23 19:00* @Created by mingcai*/
public class SignUtil {public static String createSign(Map<String, Object> originMap, String secret) throws Exception {if (originMap == null) {return null;}originMap = sortMapByKey(originMap);StringBuilder originStr = new StringBuilder();for (Map.Entry<String, Object> entry : originMap.entrySet()) {originStr.append(entry.getKey()).append("=").append(entry.getValue());originStr.append("&");}return ApiAuthAES.aesEncrypt(String.valueOf(originStr), secret);}public static Map<String, Object> sortMapByKey(Map<String, Object> map) {/*对Map对象的key升序(a->z)排列*/if (map == null || map.isEmpty()) {return null;}Map<String, Object> sortMap = new TreeMap<>(Comparator.naturalOrder());sortMap.putAll(map);return sortMap;}}
相关文章:
Gateway网关参数进行验签POST 包含requestbody 请求体封装
Gateway网关自定义拦截器的不可重复读取数据 特别注意一点, 因为在网关层 拿出 request 流之后,必须重写getbody()方法把所有的参数放进去,否则后面转发的请求无法接收到任何数据, 坑,巨坑,因为版本问题网上很多都不能兼容, 我的springboot环境 依赖包 <parent><gr…...
【Netty】字节缓冲区 ByteBuf (六)(上)
文章目录 前言一、ByteBuf类二、ByteBuffer 实现原理2.1 ByteBuffer 写入模式2.2 ByteBuffer 读取模式2.3 ByteBuffer 写入模式切换为读取模式2.4 clear() 与 compact() 方法2.5 ByteBuffer 使用案例 总结 前言 回顾Netty系列文章: Netty 概述(一&…...
Python - 面向对象编程 - 实例方法、静态方法、类方法
实例方法 在类中定义的方法默认都是实例方法,前面几篇文章已经大量使用到实例方法 实例方法栗子 class PoloBlog:def __init__(self, name, age):print("自动调用构造方法")self.name nameself.age agedef test(self):print("一个实例方法&…...
性能测试——基本性能监控系统使用
这里写目录标题 一、基本性能监控系统组成二、环境搭建1、准备数据文件 type.db collectd.conf2、启动InfluxDB3、启动grafana4、启动collectd5、Grafana中配置数据源 一、基本性能监控系统组成 Collectd InfluxdDB Grafana Collectd 是一个守护(daemon)进程,用来…...
JavaCollection集合
5 Collection集合 5.1 Collection集合概述 是单列集合的顶层接口,它表示一组对象,这些对象也称Collection元素JDK不提供此接口的直接实现,它提供更具体的子接口(Set 和 List)实现package ceshi;import java.util.AbstractCollection; import java.util.ArrayList; import…...
C++中string的用法
博主简介:Hello大家好呀,我是陈童学,一个与你一样正在慢慢前行的人。 博主主页:陈童学哦 所属专栏:CSTL 前言:Hello各位小伙伴们好!欢迎来到本专栏CSTL的学习,本专栏旨在帮助大家了解…...
目标检测YOLO实战应用案例100讲-基于深度学习的交通场景多尺度目标检测算法研究与应用
目录 基于深度学习的交通目标检测算法研究 传统的目标检测算法 基于深度学习的目标检测算法 </...
面试:vue事件绑定修饰符
stop - 调用 event.stopPropagation()。 prevent - 调用 event.preventDefault()。 trim 自动过滤用户输入的首尾空格 number 将输出字符串转为Number类型 enter 回车键 capture - 添加事件侦听器时使用 capture 模式。 self - 只当事件是从侦听器绑定的元素本身触发时才触发…...
优思学院|从0到1,认识精益生产管理
精益生产是一种系统性的生产管理方法,旨在最大化价值,最小化浪费,以及提高产品质量和客户满意度。它源于丰田生产系统(TPS),是一种基于流程优化、以人为本的管理方法,强调优化生产流程、减少浪费…...
HashSet创建String类型的数据
package com.test.Test07;import java.util.HashSet;public class TestString {//这是一个main方法,是程序的入口public static void main(String[] args) {//创建一个HashSet集合HashSet<String> hs new HashSet<>();hs.add("hello");Syste…...
真会玩:莫言用ChatGPT为余华写了一篇获奖词
5月16日,《收获》杂志65周年庆典暨新书发布活动在上海舞蹈中心举行。 典礼现场,余华凭借《文城》获得收获文学榜2021年长篇小说榜榜首。 作为老友,莫言在颁奖时故意卖了个关子:“这次获奖的是一个了不起的人物,当然了&…...
10 工具Bootchart的使用(windows)
Bootchart的使用方法(windows) 下载bootchart.jar并拷贝到windows, 然后保证windows也安装了open jdk 1.8; 下载地址:https://download.csdn.net/download/Johnny2004/87807973 打开设备开机启动bootchart的开关: adb shell touch /data/boo…...
电磁频谱异常监测论文阅读 | 《战场电磁环境下的电磁频谱管控指标体系研究》
文章目录 1.《战场电磁环境下的电磁频谱管控指标体系研究》1.1 电磁频谱管控的基本概念:1.2 电磁频谱管控的主要内容:1.3 指标体系1.3.1 技术指标体系1.3.2 战术指标体系1.《战场电磁环境下的电磁频谱管控指标体系研究》 1.1 电磁频谱管控的基本概念: 频谱管控是指军队领导…...
掌握好linkedin的这些技巧,你就已经超越了80%的跨境人
领英作为一款目前市面上最多人使用的在线职场社交媒体软件,是我们这些跨境电商人拓展客户的好渠道,并且很容易找到相应的外贸客户。但是领英的玩法并不像其他社交媒体平台一样简单,规则比较多,很多国内的用户都是胡乱操作…...
Linux——网络套接字1|socket编程
IP地址(公网IP),标定了主机的唯一性。 通常情况,把数据送到对方的机器是目的吗? 不是的,真正的网络通信过程其实是进程间通信,如客户端进程和服务器进程,我们把数据在主机间转发仅仅是手段,机器收到数据之后,需要将数据交付给指定的进程,当客户端有多个进程在运行时…...
stable-diffusion-webui服务器centos部署实践(成功)
之前关注stable-diffusion仅仅是因为stable-diffusion模型,但实践证明,stable-diffusion如果么有那么好的提示词功力,恐怕生成的图就是“畸形的,缺胳膊少腿的,多一块,少一块的”,如V1实践,V2实践,纸糊效果。 如果做不到其他人那样“美女自给自足”,那么我这个“大佬…...
北京筑龙作为软件服务商出席《国企阳光采购标准》研讨会
近日,由中国企业国有产权交易机构协会市场创新专业委员会主办、青岛产权交易所有限公司承办的《国企阳光采购标准》研讨会在青岛召开,该会议共有19家已开展采购业务的各地产权交易机构参加,北京筑龙作为软件服务商出席会议。 《国企阳光采购标…...
如何动态显示物品提示?
UE5 插件开发指南 前言0 提示信息窗口类前言 为了使物品的排列简洁,各种游戏里的物品信息都是以提示的形式展示出来,而不是整个铺排陈列,只需要玩家鼠标悬停在物品上就自动显示出提示窗口,如下图所示: 这些提示信息在物品定义数据资产中已经定义了,所以这里要做的只是将…...
推荐试试这个简单好用的手机技巧
技巧一:一键锁屏 除了按住手机电源键进行锁屏外,还有其他一些快捷方法可以实现锁屏操作。 对于苹果手机用户,可以按照以下步骤进行设置: 1.打开手机的设置应用,通常可以在主屏幕或应用列表中找到该图标。 2.在设置…...
传染病学模型 | Matlab实现SIS传染病学模型 (SIS Epidemic Model)
文章目录 效果一览基本描述模型介绍程序设计参考资料效果一览 基本描述 传染病学模型 | Matlab实现SIS传染病学模型 (SIS Epidemic Model) 模型介绍 SIS模型是一种基本的传染病学模型,用于描述一个人群中某种传染病的传播情况。SIS模型假设每个人都可以被感染,即没有免疫力,…...
Chapter03-Authentication vulnerabilities
文章目录 1. 身份验证简介1.1 What is authentication1.2 difference between authentication and authorization1.3 身份验证机制失效的原因1.4 身份验证机制失效的影响 2. 基于登录功能的漏洞2.1 密码爆破2.2 用户名枚举2.3 有缺陷的暴力破解防护2.3.1 如果用户登录尝试失败次…...
大数据零基础学习day1之环境准备和大数据初步理解
学习大数据会使用到多台Linux服务器。 一、环境准备 1、VMware 基于VMware构建Linux虚拟机 是大数据从业者或者IT从业者的必备技能之一也是成本低廉的方案 所以VMware虚拟机方案是必须要学习的。 (1)设置网关 打开VMware虚拟机,点击编辑…...
抖音增长新引擎:品融电商,一站式全案代运营领跑者
抖音增长新引擎:品融电商,一站式全案代运营领跑者 在抖音这个日活超7亿的流量汪洋中,品牌如何破浪前行?自建团队成本高、效果难控;碎片化运营又难成合力——这正是许多企业面临的增长困局。品融电商以「抖音全案代运营…...
家政维修平台实战20:权限设计
目录 1 获取工人信息2 搭建工人入口3 权限判断总结 目前我们已经搭建好了基础的用户体系,主要是分成几个表,用户表我们是记录用户的基础信息,包括手机、昵称、头像。而工人和员工各有各的表。那么就有一个问题,不同的角色…...
MVC 数据库
MVC 数据库 引言 在软件开发领域,Model-View-Controller(MVC)是一种流行的软件架构模式,它将应用程序分为三个核心组件:模型(Model)、视图(View)和控制器(Controller)。这种模式有助于提高代码的可维护性和可扩展性。本文将深入探讨MVC架构与数据库之间的关系,以…...
剑指offer20_链表中环的入口节点
链表中环的入口节点 给定一个链表,若其中包含环,则输出环的入口节点。 若其中不包含环,则输出null。 数据范围 节点 val 值取值范围 [ 1 , 1000 ] [1,1000] [1,1000]。 节点 val 值各不相同。 链表长度 [ 0 , 500 ] [0,500] [0,500]。 …...
【Web 进阶篇】优雅的接口设计:统一响应、全局异常处理与参数校验
系列回顾: 在上一篇中,我们成功地为应用集成了数据库,并使用 Spring Data JPA 实现了基本的 CRUD API。我们的应用现在能“记忆”数据了!但是,如果你仔细审视那些 API,会发现它们还很“粗糙”:有…...
select、poll、epoll 与 Reactor 模式
在高并发网络编程领域,高效处理大量连接和 I/O 事件是系统性能的关键。select、poll、epoll 作为 I/O 多路复用技术的代表,以及基于它们实现的 Reactor 模式,为开发者提供了强大的工具。本文将深入探讨这些技术的底层原理、优缺点。 一、I…...
【碎碎念】宝可梦 Mesh GO : 基于MESH网络的口袋妖怪 宝可梦GO游戏自组网系统
目录 游戏说明《宝可梦 Mesh GO》 —— 局域宝可梦探索Pokmon GO 类游戏核心理念应用场景Mesh 特性 宝可梦玩法融合设计游戏构想要素1. 地图探索(基于物理空间 广播范围)2. 野生宝可梦生成与广播3. 对战系统4. 道具与通信5. 延伸玩法 安全性设计 技术选…...
Mac下Android Studio扫描根目录卡死问题记录
环境信息 操作系统: macOS 15.5 (Apple M2芯片)Android Studio版本: Meerkat Feature Drop | 2024.3.2 Patch 1 (Build #AI-243.26053.27.2432.13536105, 2025年5月22日构建) 问题现象 在项目开发过程中,提示一个依赖外部头文件的cpp源文件需要同步,点…...
