当前位置: 首页 > 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…...

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…...

Prompt Tuning、P-Tuning、Prefix Tuning的区别

一、Prompt Tuning、P-Tuning、Prefix Tuning的区别 1. Prompt Tuning(提示调优) 核心思想:固定预训练模型参数,仅学习额外的连续提示向量(通常是嵌入层的一部分)。实现方式:在输入文本前添加可训练的连续向量(软提示),模型只更新这些提示参数。优势:参数量少(仅提…...

线程同步:确保多线程程序的安全与高效!

全文目录&#xff1a; 开篇语前序前言第一部分&#xff1a;线程同步的概念与问题1.1 线程同步的概念1.2 线程同步的问题1.3 线程同步的解决方案 第二部分&#xff1a;synchronized关键字的使用2.1 使用 synchronized修饰方法2.2 使用 synchronized修饰代码块 第三部分&#xff…...

Golang dig框架与GraphQL的完美结合

将 Go 的 Dig 依赖注入框架与 GraphQL 结合使用&#xff0c;可以显著提升应用程序的可维护性、可测试性以及灵活性。 Dig 是一个强大的依赖注入容器&#xff0c;能够帮助开发者更好地管理复杂的依赖关系&#xff0c;而 GraphQL 则是一种用于 API 的查询语言&#xff0c;能够提…...

Nginx server_name 配置说明

Nginx 是一个高性能的反向代理和负载均衡服务器&#xff0c;其核心配置之一是 server 块中的 server_name 指令。server_name 决定了 Nginx 如何根据客户端请求的 Host 头匹配对应的虚拟主机&#xff08;Virtual Host&#xff09;。 1. 简介 Nginx 使用 server_name 指令来确定…...

Robots.txt 文件

什么是robots.txt&#xff1f; robots.txt 是一个位于网站根目录下的文本文件&#xff08;如&#xff1a;https://example.com/robots.txt&#xff09;&#xff0c;它用于指导网络爬虫&#xff08;如搜索引擎的蜘蛛程序&#xff09;如何抓取该网站的内容。这个文件遵循 Robots…...

12.找到字符串中所有字母异位词

&#x1f9e0; 题目解析 题目描述&#xff1a; 给定两个字符串 s 和 p&#xff0c;找出 s 中所有 p 的字母异位词的起始索引。 返回的答案以数组形式表示。 字母异位词定义&#xff1a; 若两个字符串包含的字符种类和出现次数完全相同&#xff0c;顺序无所谓&#xff0c;则互为…...

C++:多态机制详解

目录 一. 多态的概念 1.静态多态&#xff08;编译时多态&#xff09; 二.动态多态的定义及实现 1.多态的构成条件 2.虚函数 3.虚函数的重写/覆盖 4.虚函数重写的一些其他问题 1&#xff09;.协变 2&#xff09;.析构函数的重写 5.override 和 final关键字 1&#…...

SpringAI实战:ChatModel智能对话全解

一、引言&#xff1a;Spring AI 与 Chat Model 的核心价值 &#x1f680; 在 Java 生态中集成大模型能力&#xff0c;Spring AI 提供了高效的解决方案 &#x1f916;。其中 Chat Model 作为核心交互组件&#xff0c;通过标准化接口简化了与大语言模型&#xff08;LLM&#xff0…...

【FTP】ftp文件传输会丢包吗?批量几百个文件传输,有一些文件没有传输完整,如何解决?

FTP&#xff08;File Transfer Protocol&#xff09;本身是一个基于 TCP 的协议&#xff0c;理论上不会丢包。但 FTP 文件传输过程中仍可能出现文件不完整、丢失或损坏的情况&#xff0c;主要原因包括&#xff1a; ✅ 一、FTP传输可能“丢包”或文件不完整的原因 原因描述网络…...