当前位置: 首页 > news >正文

Spring Boot 接口数据加解密

    今天聊下接口安全问题,涉及到接口的加密和解密

经常和外部单位接口调用梳理了相关技术方案,主要的需求点如下:

1,尽量少改动,不影响之前的业务逻辑

2,考虑到时间紧迫性,可采用对称性加密方式,服务需要对接安卓,IOS,H5三端,另外考虑到H5端存储密钥安全性相对来说比较低一些,故针对H5和安卓,IOS分配两套密钥

3,要兼容低版本的接口,后面新开发的接口可不兼容

4,接口有GET和POST两种接口,需要都进行加密。

需求分析:

1,服务端,客户端和H5统一拦截加密,网上有成熟的方案,也可以按其他服务中实现的加密流程来实现

2,使用AES放松加密,考虑到H5端存储密钥安全性相对来水比较低一些。故针对H5和安卓,IOS分配两套密钥

3,本次涉及客户端和服务器端的整体改造,经讨论,新接口统一加/secret/ 前缀来区分

用户类:

@Data
public class User {private Integer id;private String name;private UserType userType = UserType.COMMON;@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")private LocalDateTime registerTime;
}

用户类型枚举类

@Getter
@JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum UserType {VIP("VIP用户"),COMMON("普通用户");private String code;private String type;UserType(String type) {this.code = name();this.type = type;}
}

简单的用户列表查询示例:

@RestController
@RequestMapping(value = {"/user", "/secret/user"})
public class UserController {@RequestMapping("/list")ResponseEntity<List<User>> listUser() {List<User> users = new ArrayList<>();User u = new User();u.setId(1);u.setName("boyka");u.setRegisterTime(LocalDateTime.now());u.setUserType(UserType.COMMON);users.add(u);ResponseEntity<List<User>> response = new ResponseEntity<>();response.setCode(200);response.setData(users);response.setMsg("用户列表查询成功");return response;}
}

调用:localhost:8080/user/list

运行如下

{"code": 200,"data": [{"id": 1,"name": "boyka","userType": {"code": "COMMON","type": "普通用户"},"registerTime": "2022-03-24 23:58:39"}],"msg": "用户列表查询成功"
}

目前主要是利用ControllerAdvice来对请求和响应体进行拦截,主要定义SecretRequestAdvice对请求进行加密和SecretResponseAdvice对响应进行加密(实际情况会稍微复杂一点,项目中又GET类型请求,自定义了一个Filter进行不同的请求解密处理)。

好了,网上的ControllerAdvice使用示例非常多,我这把两个核心方法给大家展示看看,相信大佬们一看就晓得了

SecretRequestAdvice请求解密

@ControllerAdvice
@Order(Ordered.HIGHEST_PRECEDENCE)
@Slf4j
public class SecretRequestAdvice extends RequestBodyAdviceAdapter {@Overridepublic boolean supports(MethodParameter methodParameter, Type type, Class<? extends HttpMessageConverter<?>> aClass) {return true;}@Overridepublic HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException {//如果支持加密消息,进行消息解密。String httpBody;if (Boolean.TRUE.equals(SecretFilter.secretThreadLocal.get())) {httpBody = decryptBody(inputMessage);} else {httpBody = StreamUtils.copyToString(inputMessage.getBody(), Charset.defaultCharset());}//返回处理后的消息体给messageConvertreturn new SecretHttpMessage(new ByteArrayInputStream(httpBody.getBytes()), inputMessage.getHeaders());}/*** 解密消息体** @param inputMessage 消息体* @return 明文*/private String decryptBody(HttpInputMessage inputMessage) throws IOException {InputStream encryptStream = inputMessage.getBody();String requestBody = StreamUtils.copyToString(encryptStream, Charset.defaultCharset());// 验签过程HttpHeaders headers = inputMessage.getHeaders();if (CollectionUtils.isEmpty(headers.get("clientType"))|| CollectionUtils.isEmpty(headers.get("timestamp"))|| CollectionUtils.isEmpty(headers.get("salt"))|| CollectionUtils.isEmpty(headers.get("signature"))) {throw new ResultException(SECRET_API_ERROR, "请求解密参数错误,clientType、timestamp、salt、signature等参数传递是否正确传递");}String timestamp = String.valueOf(Objects.requireNonNull(headers.get("timestamp")).get(0));String salt = String.valueOf(Objects.requireNonNull(headers.get("salt")).get(0));String signature = String.valueOf(Objects.requireNonNull(headers.get("signature")).get(0));String privateKey = SecretFilter.clientPrivateKeyThreadLocal.get();ReqSecret reqSecret = JSON.parseObject(requestBody, ReqSecret.class);String data = reqSecret.getData();String newSignature = "";if (!StringUtils.isEmpty(privateKey)) {newSignature = Md5Utils.genSignature(timestamp + salt + data + privateKey);}if (!newSignature.equals(signature)) {// 验签失败throw new ResultException(SECRET_API_ERROR, "验签失败,请确认加密方式是否正确");}try {String decrypt = EncryptUtils.aesDecrypt(data, privateKey);if (StringUtils.isEmpty(decrypt)) {decrypt = "{}";}return decrypt;} catch (Exception e) {log.error("error: ", e);}throw new ResultException(SECRET_API_ERROR, "解密失败");}
}

SecretResponseAdvice响应加密

@ControllerAdvice
public class SecretResponseAdvice implements ResponseBodyAdvice {private Logger logger = LoggerFactory.getLogger(SecretResponseAdvice.class);@Overridepublic boolean supports(MethodParameter methodParameter, Class aClass) {return true;}@Overridepublic Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {// 判断是否需要加密Boolean respSecret = SecretFilter.secretThreadLocal.get();String secretKey = SecretFilter.clientPrivateKeyThreadLocal.get();// 清理本地缓存SecretFilter.secretThreadLocal.remove();SecretFilter.clientPrivateKeyThreadLocal.remove();if (null != respSecret && respSecret) {if (o instanceof ResponseBasic) {// 外层加密级异常if (SECRET_API_ERROR == ((ResponseBasic) o).getCode()) {return SecretResponseBasic.fail(((ResponseBasic) o).getCode(), ((ResponseBasic) o).getData(), ((ResponseBasic) o).getMsg());}// 业务逻辑try {String data = EncryptUtils.aesEncrypt(JSON.toJSONString(o), secretKey);// 增加签名long timestamp = System.currentTimeMillis() / 1000;int salt = EncryptUtils.genSalt();String dataNew = timestamp + "" + salt + "" + data + secretKey;String newSignature = Md5Utils.genSignature(dataNew);return SecretResponseBasic.success(data, timestamp, salt, newSignature);} catch (Exception e) {logger.error("beforeBodyWrite error:", e);return SecretResponseBasic.fail(SECRET_API_ERROR, "", "服务端处理结果数据异常");}}}return o;}
}

运行如下:

请求方法:
localhost:8080/secret/user/listheader:
Content-Type:application/json
signature:55efb04a83ca083dd1e6003cde127c45
timestamp:1648308048
salt:123456
clientType:ANDORIDbody体:
// 原始请求体
{"page": 1,"size": 10
}
// 加密后的请求体
{"data": "1ZBecdnDuMocxAiW9UtBrJzlvVbueP9K0MsIxQccmU3OPG92oRinVm0GxBwdlXXJ"
}// 加密响应体:
{"data": "fxHYvnIE54eAXDbErdrDryEsIYNvsOOkyEKYB1iBcre/QU1wMowHE2BNX/je6OP3NlsCtAeDqcp7J1N332el8q2FokixLvdxAPyW5Un9JiT0LQ3MB8p+nN23pTSIvh9VS92lCA8KULWg2nViSFL5X1VwKrF0K/dcVVZnpw5h227UywP6ezSHjHdA+Q0eKZFGTEv3IzNXWqq/otx5fl1gKQ==","code": 200,"signature": "aa61f19da0eb5d99f13c145a40a7746b","msg": "","timestamp": 1648480034,"salt": 632648
}// 解密后的响应体:
{"code": 200,"data": [{"id": 1,"name": "boyka","registerTime": "2022-03-27T00:19:43.699","userType": "COMMON"}],"msg": "用户列表查询成功","salt": 0
}

OK,客户端请求加密-》发起请求-》服务端解密-》业务处理-》服务端响应加密-》客户端解密展示,看起来没啥问题,实际是头天下午花了2小时碰需求,差不多花1小时写好demo测试,然后对所有接口统一进行了处理,整体一下午赶脚应该行了吧,告诉H5和安卓端同学明儿上午联调(不小的大家到这个时候发现猫腻没有,当时确实疏忽了,翻了大车......)

次日,安卓端反馈,你这个加解密有问题,解密后的数据格式和之前不一样,仔细一看,擦,这个userType和registerTime是不对劲,开始思考:这个能是哪儿的问题呢?1s之后,初步定位,应该是响应体的JSON.toJSONString的问题:

String data = EncryptUtils.aesEncrypt(JSON.toJSONString(o)),

Debug断点调试,果然,是JSON.toJSONString(o)这一步骤转换出了问题,那JSON转换时是不是有高级属性可以配置生成想要的序列化格式呢?FastJson在序列化时提供重载方法,找到其中一个"SerializerFeature"参数可以琢磨一下,这个参数是可以对序列化进行配置的,它提供了很多配置类型,其中感觉这几个比较沾边

WriteEnumUsingToString,
WriteEnumUsingName,
UseISO8601DateFormat

对枚举类型来说,默认是使用的WriteEnumUsingName(枚举的Name), 另一种WriteEnumUsingToString是重新toString方法,理论上可以转换成想要的样子,即这个样子

@Getter
@JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum UserType {VIP("VIP用户"),COMMON("普通用户");private String code;private String type;UserType(String type) {this.code = name();this.type = type;}@Overridepublic String toString() {return "{" +"\"code\":\"" + name() + '\"' +", \"type\":\"" + type + '\"' +'}';}
}

结果转换出来的数据是字符串类型"{"code":"COMMON", "type":"普通用户"}",这个方法好像行不通,还有什么好办法呢?思前想后,看文章开始定义的User和UserType类,标记数据序列化格式@JsonFormat,再突然想起之前看到过的一些文章,SpringMVC底层默认是使用Jackson进行序列化的,那好了,就用Jacksong实施呗,将SecretResponseAdvice中的序列化方法替换一下

String data = EncryptUtils.aesEncrypt(JSON.toJSONString(o), secretKey);换为:
String data =EncryptUtils.aesEncrypt(new ObjectMapper().writeValueAsString(o), secretKey);

运行结果如下:

{"code": 200,"data": [{"id": 1,"name": "boyka","userType": {"code": "COMMON","type": "普通用户"},"registerTime": {"month": "MARCH","year": 2022,"dayOfMonth": 29,"dayOfWeek": "TUESDAY","dayOfYear": 88,"monthValue": 3,"hour": 22,"minute": 30,"nano": 453000000,"second": 36,"chronology": {"id": "ISO","calendarType": "iso8601"}}}],"msg": "用户列表查询成功"
}

解密后的userType枚举类型和非加密版本一样了,舒服了,== 好像还不对,registerTime怎么变成这个样子了?原本是"2022-03-24 23:58:39"这种格式的,网上有很多解决方案,不过用在我们目前这个需求里面,就是有损改装了啊,不太可取,遂去Jackson官网上查找一下相关文档,当然Jackson也提供了ObjectMapper的序列化配置,重新再初始化配置ObjectMpper对象

String DATE_TIME_FORMATTER = "yyyy-MM-dd HH:mm:ss";
ObjectMapper objectMapper = new Jackson2ObjectMapperBuilder().findModulesViaServiceLoader(true).serializerByType(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DATE_TIME_FORMATTER))).deserializerByType(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DATE_TIME_FORMATTER))).build();

转化结果:

{"code": 200,"data": [{"id": 1,"name": "boyka","userType": {"code": "COMMON","type": "普通用户"},"registerTime": "2022-03-29 22:57:33"}],"msg": "用户列表查询成功"
}

OK,和非加密版的终于一致了,完了吗?感觉还是可能存在些什么问题,首先业务代码的时间序列化需求不一样,有"yyyy-MM-dd hh:mm:ss"的,也有"yyyy-MM-dd"的,还可能其他配置思考不到位的,导致和之前非加密版返回数据不一致的问题,到时候联调测出来了也麻烦,有没有一劳永逸的办法呢?哎,这个时候如果你看过 Spring 源码的话,就应该知道spring框架自身是怎么序列化的,照着配置应该就行嘛,好像有点道理,我这里不从0开始分析源码了

跟着执行链路,找到具体的响应序列化,重点就是RequestResponseBodyMethodProcessor

protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType, ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {// 获取响应的拦截器链并执行beforeBodyWrite方法,也就是执行了我们自定义的SecretResponseAdvice中的beforeBodyWrite啦body = this.getAdvice().beforeBodyWrite(body, returnType, selectedMediaType, converter.getClass(), inputMessage, outputMessage);if (body != null) {// 执行响应体序列化工作if (genericConverter != null) {genericConverter.write(body, (Type)targetType, selectedMediaType, outputMessage);} else {converter.write(body, selectedMediaType, outputMessage);}}

进而通过实例化的AbstractJackson2HttpMessageConverter对象找到执行序列化的核心方法

-> AbstractGenericHttpMessageConverter:public final void write(T t, @Nullable Type type, @Nullable MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {...this.writeInternal(t, type, outputMessage);outputMessage.getBody().flush();}-> 找到Jackson序列化 AbstractJackson2HttpMessageConverter:// 从spring容器中获取并设置的ObjectMapper实例protected ObjectMapper objectMapper;protected void writeInternal(Object object, @Nullable Type type, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {MediaType contentType = outputMessage.getHeaders().getContentType();JsonEncoding encoding = this.getJsonEncoding(contentType);JsonGenerator generator = this.objectMapper.getFactory().createGenerator(outputMessage.getBody(), encoding);this.writePrefix(generator, object);Object value = object;Class<?> serializationView = null;FilterProvider filters = null;JavaType javaType = null;if (object instanceof MappingJacksonValue) {MappingJacksonValue container = (MappingJacksonValue)object;value = container.getValue();serializationView = container.getSerializationView();filters = container.getFilters();}if (type != null && TypeUtils.isAssignable(type, value.getClass())) {javaType = this.getJavaType(type, (Class)null);}ObjectWriter objectWriter = serializationView != null ? this.objectMapper.writerWithView(serializationView) : this.objectMapper.writer();if (filters != null) {objectWriter = objectWriter.with(filters);}if (javaType != null && javaType.isContainerType()) {objectWriter = objectWriter.forType(javaType);}SerializationConfig config = objectWriter.getConfig();if (contentType != null && contentType.isCompatibleWith(MediaType.TEXT_EVENT_STREAM) && config.isEnabled(SerializationFeature.INDENT_OUTPUT)) {objectWriter = objectWriter.with(this.ssePrettyPrinter);}// 重点进行序列化objectWriter.writeValue(generator, value);this.writeSuffix(generator, object);generator.flush();}

看出SpringMVC在进行响应序列化的时候是从容器中获取的ObjectMapper实例对象,并会根据不同的默认配置条件进行序列化,那处理方法就简单了,我也可以从Spring容器拿数据进行序列化啊。SecretResponseAdvice进行如下进一步改造

@ControllerAdvice
public class SecretResponseAdvice implements ResponseBodyAdvice {@Autowiredprivate ObjectMapper objectMapper;@Overridepublic Object beforeBodyWrite(....) {.....String dataStr =objectMapper.writeValueAsString(o);String data = EncryptUtils.aesEncrypt(dataStr, secretKey);.....}}

相关文章:

Spring Boot 接口数据加解密

今天聊下接口安全问题&#xff0c;涉及到接口的加密和解密 经常和外部单位接口调用梳理了相关技术方案&#xff0c;主要的需求点如下&#xff1a; 1&#xff0c;尽量少改动&#xff0c;不影响之前的业务逻辑 2&#xff0c;考虑到时间紧迫性&#xff0c;可采用对称性加密方式&…...

2018年计算机网络408真题解析

第一题&#xff1a; 解析&#xff1a;TCP/IP体系结构应用层常用协议及其相应的运输层协议 TCP协议是面向连接可靠数据传输服务&#xff0c;UDP无连接不可靠的数据传输服务&#xff0c;IP无连接不可靠的数据连接服务。 FTP协议&#xff0c;SMTP协议和HTTP协议使用TCP协议提供的面…...

Javascript 脚本查找B站限时免费番剧

目录 前言 脚本编写 脚本 前言 B站的一些番剧时不时会“限时免费”&#xff0c;白嫖党最爱&#xff0c;主打一个又占到便宜的快乐。但是在番剧索引里却没有搜索选项可以直接检索“限时免费”的番剧&#xff0c;只能自己一页一页的翻去查看&#xff0c;非常麻烦。 自己找限…...

YoloV10改进策略:主干网络改进|DeBiFormer,可变形双级路由注意力|全网首发

摘要 在目标检测领域,YoloV10以其高效和准确的性能而闻名。然而,为了进一步提升其检测能力,我们引入了DeBiFormer作为YoloV10的主干网络。这个主干网络的计算量比较大,不过,上篇双级路由注意力的论文受到很大的关注,所以我也将这篇论文中的主干网络用来改进YoloV10,卡多…...

C#学习笔记(一)

C#学习笔记&#xff08;一&#xff09; 简介第一章 上位机开发环境之 VS 使用和.NET 平台基础一、安装软件二、创建项目三、第一个Hello world四、解决方案与项目五、Debug 和 Release 的区别六、代码的生产过程七、CLR的其它功能 简介 C# .NET工控上位机开发 在工控领域&…...

MATLAB边缘检测

一、目的&#xff1a; 熟悉边缘检测原理&#xff0c;并运用matlab软件实现图像的canny边缘检测&#xff0c;体会canny边缘检测的优缺点。 二、内容&#xff1a; 编写matlab程序&#xff0c;实现对lena图像的边缘检测&#xff0c;输出程序运行结果。 三、原理或步骤&#x…...

Tortoise SVN 安装汉化教程(乌龟SVN)

1.首先下载 去官网下载 如果下载比较慢的&#xff0c;链接自取 https://pan.quark.cn/s/cb6f2eee3f90 2. 安装Tortoise SVN 无脑next到完成 最后到桌面右键 你就发现svn出来了&#xff0c;但是是英文的&#xff01;&#xff01;&#xff01;&#xff01; 像我这种英文不好的…...

深入了解Spring重试组件spring-retry

在我们的项目中&#xff0c;为了提高程序的健壮性&#xff0c;很多时候都需要有重试机制进行兜底&#xff0c;最多就场景就比如调用远程的服务&#xff0c;调用中间件服务等&#xff0c;因为网络是不稳定的&#xff0c;所以在进行远程调用的时候偶尔会产生超时的异常&#xff0…...

海南聚广众达电子商务咨询有限公司靠谱吗怎么样?

在当今这个数字化浪潮席卷全球的时代&#xff0c;抖音电商以其独特的魅力成为了众多商家争相入驻的新蓝海。而在这片浩瀚的电商海洋中&#xff0c;如何找到一家既专业又可靠的合作伙伴&#xff0c;成为了众多商家心中的一大难题。今天&#xff0c;我们就来深入剖析一下海南聚广…...

Java的魔法世界:面向对象编程(OOP)是什么?

这个嘎嘎重要 面向对象编程&#xff08;OOP&#xff09;是让Java像玩具世界一样&#xff0c;把现实中的东西变成“对象”&#xff0c;然后让这些对象去互动。你可以想象OOP是Java的“魔法世界”&#xff0c;通过创建“对象”&#xff08;Object&#xff09;&#xff0c;让它们有…...

软件测试笔记——接口测试

文章目录 一、概念1.接口测试流程2.URL3.HTTP协议4.RESTful5.案例介绍 二、Postman1.Postman软件2.登录接口调试-获取验证码3.登录接口调试-自动关联数据4.合同上传接口-提交请求数据5.提交参数查询6.批量执行7.接口用例设计8.断言8.参数化三、案例1.项目2.课程添加3.课程列表查…...

东方通 TongRDS V2 配置与开机自启指南及 Spring Boot 集成

东方通 TongRDS V2 配置与开机自启指南及 Spring Boot 集成 文章目录 东方通 TongRDS V2 配置与开机自启指南及 Spring Boot 集成一 简述二 配置 cfg.xml1 启用密码访问2 Spring Boot 连接 TongRDS 三 配置 TongRDS 开机自启1 配置 RdsCenter1&#xff09;设置 RdsCenter.servi…...

在 VS Code 中调试 Tensor 形状不显示的问题及解决方案

文章目录 常见问题解决方案1. 定制类包装和 __repr__ 方法 解释如何应用总结 在使用 VS Code 调试 PyTorch 代码时&#xff0c;可能会遇到一个常见问题&#xff1a;调试时 variables 窗口中不显示 Tensor 的形状信息。这会使得调试时观察数据的结构变得不便&#xff0c;尤其是在…...

Linux 时间获取全面总结

1. 引言 在Linux操作系统中&#xff0c;获取时间是一个基本且重要的功能。本文旨在全面总结Linux系统中获取时间的方法&#xff0c;包括命令行工具和编程接口&#xff0c;帮助读者深入理解Linux时间管理的机制。 2. 命令行工具 2.1 date 命令 date 命令是Linux中最常用的命…...

SQL 自学:游标(Cursors)的理解与应用

在 SQL 中&#xff0c;游标&#xff08;Cursor&#xff09;是一种用于处理从数据库中检索出的多行数据的机制。它允许我们逐行地处理查询结果集&#xff0c;而不是一次性处理整个结果集。 一、游标是什么 游标可以看作是一个指向结果集的指针。通过游标&#xff0c;我们可以在…...

IO多路复用概述与epoll简介

一、引言 在网络编程中&#xff0c;高并发的场景下处理大量连接请求是一项挑战。传统的阻塞式IO模型会让线程在等待数据的过程中陷入停顿&#xff0c;导致系统效率低下。为了解决这个问题&#xff0c;IO多路复用应运而生。它允许一个线程同时监听多个文件描述符&#xff08;如…...

关于region_to_label算子的想法

1&#xff0c;定义&#xff1a;将区域进行编码 2&#xff0c;如何做到的&#xff1a;底层逻辑应该是paint_region。通过一个小的循环&#xff0c;按顺序将区域从灰度值1开始11的往上喷。 3&#xff0c;有什么作用&#xff1a;目前能用到的&#xff0c;是有字典的作用&#xff0…...

uni-app 实现好看易用的抽屉效果

在移动应用开发中&#xff0c;抽屉效果是一种常用的用户界面设计&#xff0c;它能有效地节省空间&#xff0c;同时提供导航和其他功能。本文将介绍如何在uni-app中实现一个好看且易用的抽屉效果&#xff0c;帮助你提升应用的用户体验。 一、什么是抽屉效果&#xff1f; 抽屉效…...

PowerShell 脚本 比较两文件差异(带粗狂进度条)并汇总输出

一上来就放代码 function Compare-FileHex {param ([Parameter(Mandatory$true)][string]$SourceFile,[Parameter(Mandatory$true)][string]$CompareFile,[Parameter(Mandatory$false)][string]$OutputFile,[Parameter(Mandatory$false)][int]$BufferSize 1MB)function Forma…...

学习 UE5 的一些前置操作总结

随着 Unity, Godot 这些引擎都玩抽象&#xff0c;主动捅自己一刀后&#xff0c;UE5 的风头不可谓不盛&#xff0c;本着多学一点免得失业的思路方针&#xff0c;咱也研究了一下 UE5 引擎&#xff0c;然后发现想要开始使用 UE5 &#xff0c;包含了很多前置操作&#xff0c;这里总…...

C#/.NET/.NET Core技术前沿周刊 | 第 10 期(2024年10.14-10.20)

前言 C#/.NET/.NET Core技术前沿周刊&#xff0c;你的每周技术指南针&#xff01;记录、追踪C#/.NET/.NET Core领域、生态的每周最新、最实用、最有价值的技术文章、社区动态、优质项目和学习资源等。让你时刻站在技术前沿&#xff0c;助力技术成长与视野拓宽。 欢迎投稿、推荐…...

Git 基本配置

目录 打开 Git Bash设置用户信息查看配置信息修改电脑名字为常用指令配置别名打开用户目录&#xff0c;创建 .bashrc 文件在 .bashrc 文件中输入如下内容&#xff1a;打开gitBash&#xff0c;执行 source ~/.bashrc 解决GitBash乱码问题打开GitBash执行下面命令${git_home}/etc…...

理工科考研想考计算机,湖南大学、重大、哈工大威海、山东大学,该如何选择?

C哥专业提供——计软考研院校选择分析专业课备考指南规划 计算机对理工科同学来说&#xff0c;还是性价比很高的&#xff0c;具有很大的优势&#xff01; 一、就业前景广阔 高需求行业 在当今数字化时代&#xff0c;计算机技术几乎渗透到了各个领域&#xff0c;无论是互联网…...

使用langchain和大模型API提取QA的实战教程

大家好,我是herosunly。985院校硕士毕业,现担任算法研究员一职,热衷于大模型算法的研究与应用。曾担任百度千帆大模型比赛、BPAA算法大赛评委,编写微软OpenAI考试认证指导手册。曾获得阿里云天池比赛第一名,CCF比赛第二名,科大讯飞比赛第三名。授权多项发明专利。对机器学…...

Java面试场景题(1)---如何使用redis记录上亿用户连续登陆天数

感谢uu们的观看&#xff0c;话不多说开始~ 对于这个问题&#xff0c;我们需要先来了解一下~ 海量数据都可以用bitmap来存储&#xff0c;因为占得内存小&#xff0c;速度也很快 我大概计算了一下~ 完全够&#xff1a;String类型512M 1byte 8个bit位 8个状态 512M1024byt…...

Element UI

Element ui 就是基于vue的一个ui框架,该框架基于vue开发了很多相关组件,方便我们快速开发页面。 官网: https://element.eleme.io/#/zh-CN 安装Element UI vue init webpack element(项目名)确认项目是否构建成功&#xff1a;进入到项目的根路径 执行 npm start 访问 h…...

②PROFINET转ModbusTCP, EtherCAT/Ethernet/IP/Profinet/ModbusTCP协议互转工业串口网关

EtherCAT/Ethernet/IP/Profinet/ModbusTCP协议互转工业串口网关https://item.taobao.com/item.htm?ftt&id822721028899 协议转换通信网关 PROFINET 转 Modbus TCP &#xff08;接上一章&#xff09; 配置使用 与 PROFINET 主站进行组态说明 这里介绍与西门子 PLC 的…...

python+Mosh网课笔记04

太久没写python代码了&#xff0c;学机器学习重新拾起python&#xff0c;笔记比较简陋。 参考&#xff1a;mosh python网课 一、导入同一文件夹下其他文件 first.py def swim():print("swim")def run():print("run")同一个文件夹下的second.py from f…...

【微服务】全面构建微服务监控体系:确保系统稳定与性能优化的关键

目录 引言一、微服务监控概述1.1 微服务监控的定义1.2 微服务监控的重要性1.3 监控的核心目标1.4 微服务监控的关键指标1.5 监控的策略 二、微服务监控的架构2.1 监控架构图2.2 架构组件2.3 监控架构示意图 三、微服务监控的工具3.1 工具概述3.2 Prometheus3.3 Grafana3.4 ELK …...

Gin框架操作指南08:日志与安全

官方文档地址&#xff08;中文&#xff09;&#xff1a;https://gin-gonic.com/zh-cn/docs/ 注&#xff1a;本教程采用工作区机制&#xff0c;所以一个项目下载了Gin框架&#xff0c;其余项目就无需重复下载&#xff0c;想了解的读者可阅读第一节&#xff1a;Gin操作指南&#…...