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

springboot结合AES和国密SM4进行接口加密

api接口加密

1.为什么需要api接口加密呢?

1.防止爬虫

2.防止数据被串改

3.确保数据安全

2.如何实现接口加密呢?

3.我们可以使用哪些加密算法来加密呢?

AES

密码学中的高级加密标准(Advanced Encryption Standard,AES),又称Rijndael加密法,是美国联邦政府采用的一种区块加密标准。

SM4

SM4.0(原名SMS4.0)是中华人民共和国政府采用的一种分组密码标准,由国家密码管理局于2012年3月21日发布。相关标准为“GM/T 0002-2012《SM4分组密码算法》(原SMS4分组密码算法)”。在商用密码体系中,SM4主要用于数据加密,其算法公开,分组长度与密钥长度均为128bit,加密算法与密钥扩展算法都采用32轮非线性迭代结构,S盒为固定的8比特输入8比特输出。SM4.0中的指令长度被提升到大于64K(即64×1024)的水平,这是SM 3.0规格(渲染指令长度允许大于512)的128倍。

4.具体实现
4.1后端

在这之前我们先了解一个类RequestBodyAdviceAdapter,主要通过该类实现~

RequestBodyAdviceAdapter

官网解释:RequestBodyAdviceAdapter (Spring Framework 6.2.1 API)

RequestBodyAdviceAdapter‌是Spring MVC中用于增强请求体处理的一个工具类,它实现了RequestBodyAdvice接口。RequestBodyAdviceAdapter提供了三个主要方法:beforeBodyReadafterBodyReadhandleEmptyBody

  1. beforeBodyRead‌:这个方法在请求体被读取之前调用,主要用于预处理请求体。默认实现是直接返回传入的HttpInputMessage对象。

  2. afterBodyRead‌:在请求体被读取并转换为对象之后调用,用于对读取到的对象进行进一步处理。默认实现是直接返回转换后的对象。

  3. handleEmptyBody‌:当请求体为空时调用,用于处理这种情况。默认实现是直接返回null。

代码实现

这里我们使用类似Aop的思想实现,定义对应的注解!

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface ApiDecrypt { //解密
}
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface ApiEncrypt {//加密
}
​
@Slf4j
@ControllerAdvice
public class DecryptRequestAdvice extends RequestBodyAdviceAdapter {
​private static final String ENCODING = "UTF-8";
​@Resourceprivate ApiEncryptService apiEncryptService;
​@Overridepublic boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {return methodParameter.hasMethodAnnotation(ApiDecrypt.class) || methodParameter.hasParameterAnnotation(ApiDecrypt.class)|| methodParameter.getContainingClass().isAnnotationPresent(ApiDecrypt.class);}
​@Overridepublic HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter,Type targetType, Class<? extends HttpMessageConverter<?>> converterType)  {try {String bodyStr = IOUtils.toString(inputMessage.getBody(), ENCODING);ApiEncryptForm apiEncryptForm = JSONObject.parseObject(bodyStr, ApiEncryptForm.class);if (StrUtil.isEmpty(apiEncryptForm.getEncryptData())) {return inputMessage;}String decrypt = apiEncryptService.decrypt(apiEncryptForm.getEncryptData());return new DecryptHttpInputMessage(inputMessage.getHeaders(), IOUtils.toInputStream(decrypt, ENCODING));} catch (Exception e) {return inputMessage;}
​}
​@Overridepublic Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {return body;}
​@Overridepublic Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {return body;}
​static class DecryptHttpInputMessage implements HttpInputMessage {private final HttpHeaders headers;
​private final InputStream body;
​public DecryptHttpInputMessage(HttpHeaders headers, InputStream body) {this.headers = headers;this.body = body;}
​@Overridepublic InputStream getBody() {return body;}
​@Overridepublic HttpHeaders getHeaders() {return headers;}}
​
}
@Slf4j
@ControllerAdvice
public class EncryptResponseAdvice implements ResponseBodyAdvice<ResponseDTO> {
​@Resourceprivate ApiEncryptService apiEncryptService;
​@Resourceprivate ObjectMapper objectMapper;
​@Overridepublic boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {return returnType.hasMethodAnnotation(ApiEncrypt.class) || returnType.getContainingClass().isAnnotationPresent(ApiEncrypt.class);}
​@Overridepublic ResponseDTO beforeBodyWrite(ResponseDTO body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {if (body.getData() == null) {return body;}
​String encrypt = null;try {encrypt = apiEncryptService.encrypt(objectMapper.writeValueAsString(body.getData()));} catch (JsonProcessingException e) {throw new RuntimeException(e);}body.setData(encrypt);body.setDataType(DataTypeEnum.ENCRYPT.getValue());return body;}
}

实现类

/*** AES 加密和解密* 1、AES加密算法支持三种密钥长度:128位、192位和256位,这里选择128位* 2、AES 要求秘钥为 128bit,转化字节为 16个字节;* 3、js前端使用 UCS-2 或者 UTF-16 编码,字母、数字、特殊符号等 占用1个字节;* 4、所以:秘钥Key 组成为:字母、数字、特殊符号 一共16个即可*/
@Slf4j
public class ApiEncryptServiceAesImpl implements ApiEncryptService {
​private static final String CHARSET = "UTF-8";
​private static final String AES_KEY = "1024lab__1024lab";
​static {Security.addProvider(new BouncyCastleProvider());}
​@Overridepublic String encrypt(String data) {try {//  AES 加密 并转为 base64AES aes = new AES(hexToBytes(stringToHex(AES_KEY)));return aes.encryptBase64(data);} catch (Exception e) {log.error(e.getMessage(), e);return StringConst.EMPTY;}}
​@Overridepublic String decrypt(String data) {try {// 第一步: Base64 解码byte[] base64Decode = Base64.getDecoder().decode(data);// 第二步: AES 解密AES aes = new AES(hexToBytes(stringToHex(AES_KEY)));byte[] decryptedBytes = aes.decrypt(base64Decode);return new String(decryptedBytes, CHARSET);} catch (Exception e) {log.error(e.getMessage(), e);return StringConst.EMPTY;}}
​/*** 16 进制串转字节数组** @param hex 16进制字符串* @return byte数组*/public static byte[] hexToBytes(String hex) {int length = hex.length();byte[] result;if (length % 2 == 1) {length++;result = new byte[(length / 2)];hex = "0" + hex;} else {result = new byte[(length / 2)];}int j = 0;for (int i = 0; i < length; i += 2) {result[j] = hexToByte(hex.substring(i, i + 2));j++;}return result;}
​public static String stringToHex(String input) {char[] chars = input.toCharArray();StringBuilder hex = new StringBuilder();for (char c : chars) {hex.append(Integer.toHexString((int) c));}return hex.toString();}
​/*** 16 进制字符转字节** @param hex 16进制字符 0x00到0xFF* @return byte*/private static byte hexToByte(String hex) {return (byte) Integer.parseInt(hex, 16);}
​public static void main(String[] args) {
//        System.out.println(new ApiEncryptServiceAesImpl().encrypt("{\"name\":\"chen\",\"age\":18}"));System.out.println(new ApiEncryptServiceAesImpl().decrypt("MWFjZTI3MDA0ZDcxY2RlM2U4YTY5NDU3MjE3MmE0YzlhOTI0OTVhOTBiM2VmYzYwZjgxZTlmMjRmNDVkMWNlMg=="));}
}
​
​
@Slf4j
@Service
public class ApiEncryptServiceSmImpl implements ApiEncryptService {
​private static final String CHARSET = "UTF-8";private static final String SM4_KEY = "1024lab__1024lab";
​static {Security.addProvider(new BouncyCastleProvider());}
​
​@Overridepublic String encrypt(String data) {try {
​// 第一步: SM4 加密SM4 sm4 = new SM4(hexToBytes(stringToHex(SM4_KEY)));String encryptHex = sm4.encryptHex(data);
​// 第二步: Base64 编码return new String(Base64.getEncoder().encode(encryptHex.getBytes(CHARSET)), CHARSET);
​} catch (Exception e) {log.error(e.getMessage(), e);return StringConst.EMPTY;}}
​
​@Overridepublic String decrypt(String data) {try {
​// 第一步: Base64 解码byte[] base64Decode = Base64.getDecoder().decode(data);
​// 第二步: SM4 解密SM4 sm4 = new SM4(hexToBytes(stringToHex(SM4_KEY)));return sm4.decryptStr(new String(base64Decode));
​} catch (Exception e) {log.error(e.getMessage(), e);return StringConst.EMPTY;}}
​
​public static String stringToHex(String input) {char[] chars = input.toCharArray();StringBuilder hex = new StringBuilder();for (char c : chars) {hex.append(Integer.toHexString((int) c));}return hex.toString();}
​
​/*** 16 进制串转字节数组** @param hex 16进制字符串* @return byte数组*/public static byte[] hexToBytes(String hex) {int length = hex.length();byte[] result;if (length % 2 == 1) {length++;result = new byte[(length / 2)];hex = "0" + hex;} else {result = new byte[(length / 2)];}int j = 0;for (int i = 0; i < length; i += 2) {result[j] = hexToByte(hex.substring(i, i + 2));j++;}return result;}
​/*** 16 进制字符转字节** @param hex 16进制字符 0x00到0xFF* @return byte*/private static byte hexToByte(String hex) {return (byte) Integer.parseInt(hex, 16);}
​
}
4.2前端

axios.js

import axios from 'axios'
import { decryptData, encryptData } from './encrypt.js';
​
const requests = axios.create({//共同的接口前缀baseURL: 'http://localhost:8881',//超时时间timeout: 2000
});
​
//请求拦截器:再发请求之前,请求拦截器可以检查到,可以在请求发出去之前做一些事情
requests.interceptors.request.use(config => {return config
}), err => {return Promise.reject(err)
}
​
//响应拦截器
requests.interceptors.response.use(response => {// 如果是加密数据if (response.data.dataType === 10) {response.data.encryptData = response.data.data;let decryptStr = decryptData(response.data.data);if (decryptStr) {response.data.data = JSON.parse(decryptStr);}}return response.data
}, err => {return Promise.reject(err)
})
​
// ================================= 加密 =================================
/*** 加密请求参数的post请求*/
export const postEncryptRequest = (url, data) => {return requests({data: { encryptData: encryptData(data) },url,method: 'post',});
};
​
​
/*** post请求*/
export const postRequest = (url, data) => {return requests({data,url,method: 'post',});
};
​
/*** get请求  /api?encryptData=xxx*/
export const getEncryptRequest = (url, data) => {return requests({params: { encryptData: encryptData(data) },url,method: 'get',});
};

前端加密

import CryptoJS from 'crypto-js';
import CryptoSM from 'sm-crypto';
​
function object2string(data) {if (typeof data === 'object') {return JSON.stringify(data);}
​let str = JSON.stringify(data);if (str.startsWith("'") || str.startsWith('"')) {str = str.substring(1);}if (str.endsWith("'") || str.endsWith('"')) {str = str.substring(0, str.length - 1);}return str;
}
/*** 字符串转为数字*/
function stringToHex(str) {let hex = '';for (let i = 0; i < str.length; i++) {hex += str.charCodeAt(i).toString(16).padStart(2, '0');}return hex;
}
​
/** -------------------- ※ AES 加密、解密 begin ※ --------------------** 1、AES加密算法支持三种密钥长度:128位、192位和256位,这里选择128位* 2、AES 要求秘钥为 128bit,转化字节为 16个字节;* 3、js前端使用 UCS-2 或者 UTF-16 编码,字母、数字、特殊符号等 占用1个字节;* 4、所以:秘钥Key 组成为:字母、数字、特殊符号 一共16个即可** -------------------- ※ AES 加密、解密 end ※ --------------------*/
const AES_KEY = '1024lab__1024lab';
const AES = {encryptData: function (data) {// AES 加密 并转为 base64let utf8Data = CryptoJS.enc.Utf8.parse(object2string(data));const key = CryptoJS.enc.Utf8.parse(AES_KEY);const encrypted = CryptoJS.AES.encrypt(utf8Data, key, {mode: CryptoJS.mode.ECB,padding: CryptoJS.pad.Pkcs7,});return CryptoJS.enc.Base64.stringify(encrypted.ciphertext);},
​decryptData: function (data) {//  第一步:Base64 解码let words = CryptoJS.enc.Base64.parse(data);
​// 第二步:AES 解密const key = CryptoJS.enc.Utf8.parse(AES_KEY);return CryptoJS.AES.decrypt({ ciphertext: words }, key, {mode: CryptoJS.mode.ECB,padding: CryptoJS.pad.Pkcs7,}).toString(CryptoJS.enc.Utf8);},
};
​
/** -------------------- ※ 国密SM4算法 加密、解密 begin ※ --------------------** 1、国密SM4 要求秘钥为 128bit,转化字节为 16个字节;* 2、js前端使用 UCS-2 或者 UTF-16 编码,字母、数字、特殊符号等 占用1个字节;* 3、java中 每个 字母数字 也是占用1个字节;* 4、所以:前端和后端的 秘钥Key 组成为:字母、数字、特殊符号 一共16个即可** -------------------- ※ 国密SM4算法 加密、解密 end ※ --------------------*/
​
// 秘钥Key 组成为:字母、数字、特殊符号 一共16个即可
const SM4_KEY = '1024lab__1024lab';
​
const SM4 = {encryptData: function (data) {// 第一步:SM4 加密let encryptData = CryptoSM.sm4.encrypt(object2string(data), stringToHex(SM4_KEY));// 第二步: Base64 编码return CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(encryptData));},
​decryptData: function (data) {// 第一步:Base64 解码let words = CryptoJS.enc.Base64.parse(data);let decode64Str = CryptoJS.enc.Utf8.stringify(words);
​// 第二步:SM4 解密return CryptoSM.sm4.decrypt(decode64Str, stringToHex(SM4_KEY));},
};
​
​
​
// -----------------------  对外暴露: 加密、解密 -----------------------
​
// 默认使用SM4算法
const EncryptObject = SM4;
// const EncryptObject = AES;
​
/*** 加密*/
export const encryptData = function (data) {return !data ? null : EncryptObject.encryptData(data);
};
/*** 解密*/
export const decryptData = function (data) {return !data ? null : EncryptObject.decryptData(data);
};

api.js

import { postRequest, postEncryptRequest, getEncryptRequest } from '@/utils/axios.js';
// 测试获取列表 双向加密
export const getList = (data) => {return postEncryptRequest('/api/test/getList',data)
}
​
export const getListOfTetsGet = (data) => {return getEncryptRequest('/api/testget/getList',data)
}
​
// 请求加密paramsAes
export const getListParamsAes = (data) => {return postEncryptRequest('/api/paramsAes/getList',data)
}
​
// 响应加密  /resAes/getList
export const getListResAes = (data) => {return postRequest('/api/resAes/getList',data)
}

vue界面

<template><div><el-button type="primary" @click="aes">发送加密请求</el-button><el-button type="primary" @click="paramsAsc">请求体加密</el-button><el-button type="primary" @click="reqAsc">返回加密</el-button>
​<div><el-table :data="tableData" border style="width: 100%"><el-table-column prop="title" label="书标题"></el-table-column><el-table-column prop="author" label="作者"></el-table-column><el-table-column prop="price" label="价格"></el-table-column><el-table-column fixed="right" label="操作" width="100"><template slot-scope="scope"><el-button @click="handleClick(scope.row)" type="text" size="small">查看</el-button><el-button type="text" size="small">编辑</el-button></template></el-table-column></el-table></div></div>
</template>
​
<script>import { getList, getListParamsAes, getListResAes, getListOfTetsGet } from "@/api/test.js"export default {name: 'HelloWorld',props: {msg: String},data() {return {tableData: []}},mounted() {this.aes()},methods: {aes() {getList({ pageNum: 1, pageSize: 3 }).then(res => {if (res.code == 0) {this.tableData = res.data}})},paramsAsc() {getListParamsAes({ pageNum: 1, pageSize: 3 }).then(res => {console.log(res);})},reqAsc() {getListResAes({ pageNum: 1, pageSize: 3 }).then(res => {console.log(res);})}},}
</script>
5.测试结果

双向加密

 

请求体加密

 

响应体加密

 

6.声明

该案例参考于开源的smartadmin项目,该项目类似于若依,整体代码比较规范,推荐使用~

官网地址:SmartAdmin | 1024创新实验室

gitee地址:smart-admin(MIT协议-免费任意商用): 🔥SmartAdmin以「高质量代码」为核心,「简洁、高效、安全」的快速开发平台;基于SpringBoot2/3+Sa-Token+Mybatis-Plus和Vue3 +Ant Design Vue+UniApp (提供JavaScript和TypeScript双版本、Java8和java17双版本);满足三级等保、网络安全、数据安全等功能要求。并重磅开源千余家企业在使用的《高质量代码规范》等

相关文章:

springboot结合AES和国密SM4进行接口加密

api接口加密 1.为什么需要api接口加密呢&#xff1f; 1.防止爬虫 2.防止数据被串改 3.确保数据安全 2.如何实现接口加密呢&#xff1f; 3.我们可以使用哪些加密算法来加密呢&#xff1f; AES 密码学中的高级加密标准&#xff08;Advanced Encryption Standard&#xff0c;…...

iOS在项目中设置 Dev、Staging 和 Prod 三个不同的环境

在 Objective-C 项目中设置 Dev、Staging 和 Prod 三个不同的环境&#xff0c;并为每个环境使用不同的 Bundle ID&#xff0c;可以通过以下步骤实现&#xff1a; 步骤 1: 创建不同的 Build Configuration 打开项目&#xff1a; 启动 Xcode 并打开你的项目。 选择项目文件&…...

openeuler24.09 系统无需配置 docker 源即可安装 docker 和 docker-composer

准备工作 1、准备一台刚刚创建的 openeuler24.09 lxc 虚拟机 2、使用 dnf 更新到最新,安装常用 工具 dnf update -y dnf install vim net-tools wget3、设置 ssh 由于ssh 与通常网上教程大同小异,在此我们就略过。 从下图我们可以看到 openeuler24.09 已经远程连接上。 …...

Flask入门:打造简易投票系统

目录 准备工作 创建项目结构 编写HTML模板 编写Flask应用 代码解读 进一步优化 结语 Flask,这个轻量级的Python Web框架,因其简洁和易用性,成为很多开发者入门Web开发的首选。今天,我们就用Flask来做一个简单的投票系统,让你快速上手Web开发,同时理解Flask的核心概…...

日常思考笔记

技术管理&#xff0c; 团队管理&#xff0c;人才培养&#xff0c;梯队建设 项目管理&#xff0c;项目全生命周期&#xff0c;项目进度 考核规范&#xff0c; AQS 是CountDownLatch&#xff0c;ReentrantLock&#xff0c;Semaphore&#xff0c;ReentrantReadWriteLock的基础 vo…...

【JAVA】后台管理系统密码复杂度和修改密码处理

一、后台管理系统密码要求 后台管理系统密码要求 口令有效期&#xff1a;90天 口令长度8位及8位以上 口令复杂度要求&#xff0c;至少包含以下四类字符中的三类字符: 英文大写字母(A 到 Z)、英文小写字母(a 到 z)、10个基本数字(0 到 9)、特殊字符(例如 !、$、#、%、、^、&a…...

微服务SpringCloud链路追踪之Micrometer+Zipkin

视频教程&#xff1a; https://www.bilibili.com/video/BV12LBFYjEvR 效果演示 当我们发送一个请求给 Gateway 的时候&#xff0c;由 Micrometer trace 进行链路追踪和数据收集&#xff0c;由 Zipkin 进行数据展示。可以清楚的看到微服务的调用过程&#xff0c;以及每个微服务…...

Quartz(2-Trigger)

相关文章链接 定时任务工具类&#xff08;Cron Util&#xff09;SpringBoot TaskQuartz&#xff08;1-Job&#xff09;Quartz&#xff08;2-Trigger&#xff09; Trigger 方法 优先级&#xff08;priority&#xff09; 如果你的 trigger 很多&#xff08;或者 Quartz 线程…...

【微信小程序开发 - 3】:项目组成介绍

文章目录 项目组成介绍项目的基本组成结构小程序页面的组成部分JSON配置文件的作用app.json文件project.config.json文件sitemap.json文件页面的 .json 配置文件新建小程序页面修改项目首页 XWML模板XWML 和 HTML 的区别 WXSS样式WXSS 和 CSS 的区别 .js文件 项目组成介绍 项目…...

Leetcode 三角形最小路径和

算法思想与代码详解 这段代码采用的是**动态规划&#xff08;Dynamic Programming&#xff09;**的思想&#xff0c;用来解决“120. 三角形最小路径和”问题。动态规划通过将问题分解成更小的子问题&#xff0c;并通过保存子问题的解来避免重复计算&#xff0c;从而提高效率。…...

DataOps驱动数据集成创新:Apache DolphinScheduler SeaTunnel on Amazon Web Services

引言 在数字化转型的浪潮中&#xff0c;数据已成为企业最宝贵的资产之一。DataOps作为一种文化、流程和实践的集合&#xff0c;旨在提高数据管道的质量和效率&#xff0c;从而加速数据从源头到消费的过程。白鲸开源科技&#xff0c;作为DataOps领域的领先开源原生公司&#xf…...

Android Studio的笔记--BusyBox相关

BusyBox 相关 BusyBoxandroid上安装busybox和使用示例一、下载二、移动三、安装和设置环境变量四、使用 busybox源码下载和查看 BusyBox BUSYBOX BUSYBOX链接https://busybox.net/ 点击链接后如图 点击左边菜单栏的Get BusyBix中的Download Source 跳转到busybox 的下载源码…...

MySQL 存储过程与函数:增强数据库功能

一、MySQL 存储过程与函数概述 &#xff08;一&#xff09;存储过程的定义与特点 存储过程是一组预编译的 SQL 语句集合&#xff0c;它们被存储在数据库中&#xff0c;可根据需要被重复调用。例如&#xff0c;在一个电商系统中&#xff0c;经常需要查询某个时间段内的订单数据…...

网络安全(3)_安全套接字层SSL

4. 安全套接字层 4.1 安全套接字层&#xff08;SSL&#xff09;和传输层安全&#xff08;TLS&#xff09; &#xff08;1&#xff09;SSL/TLS提供的安全服务 ①SSL服务器鉴别&#xff0c;允许用户证实服务器的身份。支持SSL的客户端通过验证来自服务器的证书&#xff0c;来鉴别…...

Git 快速入门

Git 是什么&#xff1f; Git 是一个分布式版本控制系统四大区域&#xff1a; 工作区&#xff1a;项目文件的当前状态&#xff0c;即本地目录。暂存区&#xff1a;保存将要提交的文件快照&#xff0c;是一个中间层&#xff0c;使用git add将文件添加到暂存区。本地仓库&#xf…...

AI学习记录 - 依据 minimind 项目入门

想学习AI&#xff0c;还是需要从头到尾跑一边流程&#xff0c;最近看到这个项目 minimind, 我也记录下学习到的东西&#xff0c;需要结合项目的readme看。 1、github链接 https://github.com/jingyaogong/minimind?tabreadme-ov-file 2、硬件环境&#xff1a;英伟达4070ti …...

数据结构----链表头插中插尾插

一、链表的基本概念 链表是一种线性数据结构&#xff0c;它由一系列节点组成。每个节点包含两个主要部分&#xff1a; 数据域&#xff1a;用于存储数据元素&#xff0c;可以是任何类型的数据&#xff0c;如整数、字符、结构体等。指针域&#xff1a;用于存储下一个节点&#…...

设计模式-读书笔记

确认好&#xff1a; 模式名称 问题&#xff1a;在何时使用模式&#xff0c;包含设计中存在的问题以及问题存在的原因 解决方案&#xff1a;设计模式的组成部分&#xff0c;以及这些组成部分之间的相互关系&#xff0c;各自的职责和协作方式&#xff0c;用uml类图和核心代码描…...

c语言----选择结构

基本概念 选择结构是C语言中用于根据条件判断来执行不同代码块的结构。它允许程序在不同的条件下执行不同的操作&#xff0c;使程序具有决策能力。 if语句 单分支if语句 语法格式&#xff1a; if (条件表达式) { 执行语句块; } 功能&#xff1a; 当条件表达式的值为真&#…...

KS曲线python实现

目录 实战 实战 # 导入第三方模块 import pandas as pd import numpy as np import matplotlib.pyplot as plt# 自定义绘制ks曲线的函数 def plot_ks(y_test, y_score, positive_flag):# 对y_test重新设置索引y_test.index np.arange(len(y_test))# 构建目标数据集target_dat…...

5步掌握Beyond Compare 5逆向工程:RSA加密破解与密钥生成实战

5步掌握Beyond Compare 5逆向工程&#xff1a;RSA加密破解与密钥生成实战 【免费下载链接】BCompare_Keygen Keygen for BCompare 5 项目地址: https://gitcode.com/gh_mirrors/bc/BCompare_Keygen 软件授权逆向工程是信息安全领域的重要研究方向&#xff0c;通过分析Be…...

用Wireshark抓包实战,手把手教你读懂LwIP里的TCP/IP数据帧(附真实数据解析)

Wireshark与LwIP实战&#xff1a;从抓包数据到协议栈实现的深度解析 当你第一次在Wireshark中看到那些密密麻麻的十六进制数据时&#xff0c;是否感到无从下手&#xff1f;作为嵌入式开发者&#xff0c;理解网络数据包的底层结构不仅是调试网络问题的关键&#xff0c;更是优化L…...

ReID跨镜需人工复核,镜像视界无感定位实现全自动全链路闭环

ReID跨镜需人工复核&#xff0c;镜像视界无感定位实现全自动全链路闭环在全域视频感知与人员动态管控行业应用落地进程中&#xff0c;传统依托ReID行人重识别搭建的跨镜追踪体系&#xff0c;长期深陷算法识别偏差大、数据容错率低、最终必须依赖人工二次复核的运营困局&#xf…...

为什么预训练再好的VLA,在新任务上普通SFT 并不好用?CapVector给出了原因和方案

Vision-Language-Action&#xff08;VLA&#xff09;模型现在已经很强了。 但一个很现实的问题是&#xff1a; 预训练再充分的 VLA&#xff0c;到了新任务上&#xff0c;普通 SFT 往往并不好用。 很多工作发现&#xff1a; 训练收敛慢少量 demonstration 不够泛化能力并没有…...

开源科研操作系统OpenResearcher:一体化工作流与知识管理实践

1. 项目概述&#xff1a;当开源遇上学术研究如果你是一名研究生、博士生&#xff0c;或者任何需要长期进行文献调研、实验记录和论文撰写的科研工作者&#xff0c;那么你大概率经历过这样的场景&#xff1a;电脑桌面上散落着几十个PDF文件&#xff0c;文件名是“paper1.pdf”、…...

告别adb命令行:用C++和libusb手撸一个USB调试工具(附完整源码)

告别adb命令行&#xff1a;用C和libusb手撸一个USB调试工具&#xff08;附完整源码&#xff09; 你是否厌倦了反复敲击adb命令&#xff0c;却对背后的USB通信机制充满好奇&#xff1f;本文将带你深入Android调试桥&#xff08;ADB&#xff09;的底层世界&#xff0c;用C和libus…...

会议录播堆积如山?用这款AI工具3分钟自动生成会议纪要

一个很普遍的职场痛点&#xff1a;每周开3-4个会&#xff0c;录播存了一堆&#xff0c;但从来没有整理过。 不是不想整理&#xff0c;是整理一小时的会议录像至少要40分钟——要从头拉一遍、要标重点、要区分谁说了什么、要提炼行动项。忙的时候根本没时间干这个。 结果就是&…...

Rust构建的轻量级文件搜索工具fltr:高性能文本检索新选择

1. 项目概述&#xff1a;一个轻量级、高性能的本地文件搜索工具在开发或日常文件管理工作中&#xff0c;我们常常面临一个看似简单却极其恼人的问题&#xff1a;如何在成千上万的文件中&#xff0c;快速、精准地找到包含特定关键词或符合特定模式的那一个&#xff1f;无论是定位…...

基于CRICKIT与蓝牙的双足机器人:从机械原理到手机遥控实践

1. 项目概述&#xff1a;一个会“翻跟头”的蓝牙机器人如果你玩腻了循迹小车或者舵机云台&#xff0c;想做一个动作更“魔性”、互动性更强的机器人&#xff0c;那么这个基于CRICKIT和Feather M0 Bluefruit的双足机器人绝对能让你眼前一亮。它走起路来不是平稳前进&#xff0c;…...

告别 AI 失忆!基于 Harness 记忆模型,解密 SpreadContext 多实例同步引擎

在日常与企业级客户及前端开发者的交流中&#xff0c;我经常听到这样的痛点&#xff1a;“我们成功接入了大模型&#xff0c;但它总是‘睁眼瞎’。用户在表格里改了数据&#xff0c;AI 不知道&#xff1b;AI 修改了单元格&#xff0c;UI 没有同步。聊了几轮之后&#xff0c;大模…...