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提供了三个主要方法:beforeBodyRead、afterBodyRead和handleEmptyBody。
-
beforeBodyRead:这个方法在请求体被读取之前调用,主要用于预处理请求体。默认实现是直接返回传入的
HttpInputMessage对象。 -
afterBodyRead:在请求体被读取并转换为对象之后调用,用于对读取到的对象进行进一步处理。默认实现是直接返回转换后的对象。
-
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接口加密呢? 1.防止爬虫 2.防止数据被串改 3.确保数据安全 2.如何实现接口加密呢? 3.我们可以使用哪些加密算法来加密呢? AES 密码学中的高级加密标准(Advanced Encryption Standard,…...
iOS在项目中设置 Dev、Staging 和 Prod 三个不同的环境
在 Objective-C 项目中设置 Dev、Staging 和 Prod 三个不同的环境,并为每个环境使用不同的 Bundle ID,可以通过以下步骤实现: 步骤 1: 创建不同的 Build Configuration 打开项目: 启动 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的核心概…...
日常思考笔记
技术管理, 团队管理,人才培养,梯队建设 项目管理,项目全生命周期,项目进度 考核规范, AQS 是CountDownLatch,ReentrantLock,Semaphore,ReentrantReadWriteLock的基础 vo…...
【JAVA】后台管理系统密码复杂度和修改密码处理
一、后台管理系统密码要求 后台管理系统密码要求 口令有效期:90天 口令长度8位及8位以上 口令复杂度要求,至少包含以下四类字符中的三类字符: 英文大写字母(A 到 Z)、英文小写字母(a 到 z)、10个基本数字(0 到 9)、特殊字符(例如 !、$、#、%、、^、&a…...
微服务SpringCloud链路追踪之Micrometer+Zipkin
视频教程: https://www.bilibili.com/video/BV12LBFYjEvR 效果演示 当我们发送一个请求给 Gateway 的时候,由 Micrometer trace 进行链路追踪和数据收集,由 Zipkin 进行数据展示。可以清楚的看到微服务的调用过程,以及每个微服务…...
Quartz(2-Trigger)
相关文章链接 定时任务工具类(Cron Util)SpringBoot TaskQuartz(1-Job)Quartz(2-Trigger) Trigger 方法 优先级(priority) 如果你的 trigger 很多(或者 Quartz 线程…...
【微信小程序开发 - 3】:项目组成介绍
文章目录 项目组成介绍项目的基本组成结构小程序页面的组成部分JSON配置文件的作用app.json文件project.config.json文件sitemap.json文件页面的 .json 配置文件新建小程序页面修改项目首页 XWML模板XWML 和 HTML 的区别 WXSS样式WXSS 和 CSS 的区别 .js文件 项目组成介绍 项目…...
Leetcode 三角形最小路径和
算法思想与代码详解 这段代码采用的是**动态规划(Dynamic Programming)**的思想,用来解决“120. 三角形最小路径和”问题。动态规划通过将问题分解成更小的子问题,并通过保存子问题的解来避免重复计算,从而提高效率。…...
DataOps驱动数据集成创新:Apache DolphinScheduler SeaTunnel on Amazon Web Services
引言 在数字化转型的浪潮中,数据已成为企业最宝贵的资产之一。DataOps作为一种文化、流程和实践的集合,旨在提高数据管道的质量和效率,从而加速数据从源头到消费的过程。白鲸开源科技,作为DataOps领域的领先开源原生公司…...
Android Studio的笔记--BusyBox相关
BusyBox 相关 BusyBoxandroid上安装busybox和使用示例一、下载二、移动三、安装和设置环境变量四、使用 busybox源码下载和查看 BusyBox BUSYBOX BUSYBOX链接https://busybox.net/ 点击链接后如图 点击左边菜单栏的Get BusyBix中的Download Source 跳转到busybox 的下载源码…...
MySQL 存储过程与函数:增强数据库功能
一、MySQL 存储过程与函数概述 (一)存储过程的定义与特点 存储过程是一组预编译的 SQL 语句集合,它们被存储在数据库中,可根据需要被重复调用。例如,在一个电商系统中,经常需要查询某个时间段内的订单数据…...
网络安全(3)_安全套接字层SSL
4. 安全套接字层 4.1 安全套接字层(SSL)和传输层安全(TLS) (1)SSL/TLS提供的安全服务 ①SSL服务器鉴别,允许用户证实服务器的身份。支持SSL的客户端通过验证来自服务器的证书,来鉴别…...
Git 快速入门
Git 是什么? Git 是一个分布式版本控制系统四大区域: 工作区:项目文件的当前状态,即本地目录。暂存区:保存将要提交的文件快照,是一个中间层,使用git add将文件添加到暂存区。本地仓库…...
AI学习记录 - 依据 minimind 项目入门
想学习AI,还是需要从头到尾跑一边流程,最近看到这个项目 minimind, 我也记录下学习到的东西,需要结合项目的readme看。 1、github链接 https://github.com/jingyaogong/minimind?tabreadme-ov-file 2、硬件环境:英伟达4070ti …...
数据结构----链表头插中插尾插
一、链表的基本概念 链表是一种线性数据结构,它由一系列节点组成。每个节点包含两个主要部分: 数据域:用于存储数据元素,可以是任何类型的数据,如整数、字符、结构体等。指针域:用于存储下一个节点&#…...
设计模式-读书笔记
确认好: 模式名称 问题:在何时使用模式,包含设计中存在的问题以及问题存在的原因 解决方案:设计模式的组成部分,以及这些组成部分之间的相互关系,各自的职责和协作方式,用uml类图和核心代码描…...
c语言----选择结构
基本概念 选择结构是C语言中用于根据条件判断来执行不同代码块的结构。它允许程序在不同的条件下执行不同的操作,使程序具有决策能力。 if语句 单分支if语句 语法格式: if (条件表达式) { 执行语句块; } 功能: 当条件表达式的值为真&#…...
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…...
Python实战:利用SymPy与SciPy高效破解复杂非线性方程组
1. 为什么需要SymPy和SciPy解非线性方程组? 遇到工程计算或科研问题时,我们常需要解像这样的方程组:xy10且yz34。这种包含平方项、三角函数或指数函数的方程,传统手工计算不仅耗时还容易出错。我去年做机器人运动学分析时…...
DAMO-YOLO智能视觉系统作品集:多场景零售货架检测效果惊艳展示
DAMO-YOLO智能视觉系统作品集:多场景零售货架检测效果惊艳展示 1. 零售视觉检测的新标杆 走进现代零售空间,商品陈列的艺术背后隐藏着复杂的运营挑战。传统的人工巡检方式已经难以满足快节奏零售环境的需求,这正是DAMO-YOLO智能视觉系统大放…...
React Native Chart Kit 性能优化技巧:大数据量下的流畅图表渲染
React Native Chart Kit 性能优化技巧:大数据量下的流畅图表渲染 【免费下载链接】react-native-chart-kit 📊React Native Chart Kit: Line Chart, Bezier Line Chart, Progress Ring, Bar chart, Pie chart, Contribution graph (heatmap) 项目地址:…...
GLM-4.1V-9B-Base多场景落地:医疗影像辅助描述、零售货架识别、文旅导览图解
GLM-4.1V-9B-Base多场景落地:医疗影像辅助描述、零售货架识别、文旅导览图解 1. 模型介绍 GLM-4.1V-9B-Base是智谱开源的一款视觉多模态理解模型,专门针对图像内容识别、场景描述和目标问答等任务进行了优化。这个模型特别擅长处理中文视觉理解任务&…...
从取证到防御:实战解析BadUSB攻击与USB流量异常检测(Wireshark实战)
从取证到防御:实战解析BadUSB攻击与USB流量异常检测(Wireshark实战) 在企业内网安全防护中,USB设备带来的威胁往往被低估。去年某金融机构遭遇的供应链攻击事件中,攻击者通过伪装成键盘的BadUSB设备,在3分钟…...
vue基于springboot的目的地旅游预订网站
目录同行可拿货,招校园代理 ,本人源头供货商功能模块划分技术实现要点扩展功能建议性能优化方向项目技术支持源码获取详细视频演示 :文章底部获取博主联系方式!同行可合作同行可拿货,招校园代理 ,本人源头供货商 功能模块划分 用户模块 用户注册与登录…...
除了阿里云,还有哪些靠谱的身份证实名认证方案?SpringBoot整合横向评测
SpringBoot整合主流身份证实名认证API横向评测:从阿里云到多服务商技术选型指南 当你的应用需要接入身份证实名认证功能时,阿里云可能只是众多选项中的一个起点。作为技术决策者,如何在腾讯云、百度智能云、聚合数据等众多服务商中做出最优选…...
别再到处找了!这12个三维点云开源数据集,够你从入门到项目实战
三维点云实战指南:12个精选开源数据集与精准匹配策略 当你第一次打开三维点云处理软件,面对空白的项目界面,最迫切的问题往往是:"我该从哪里获取高质量的训练数据?"这个问题困扰过每一位初学者,…...
MiniCPM-o-4.5-nvidia-FlagOS部署运维:使用Docker Compose管理多服务依赖
MiniCPM-o-4.5-nvidia-FlagOS部署运维:使用Docker Compose管理多服务依赖 你是不是也遇到过这种情况?想部署一个AI模型,发现它依赖一堆东西:模型服务本身、数据库、缓存、可能还有别的辅助工具。一个个手动去装、去配置、去启动&…...
DynamicColor跨平台开发指南:iOS、macOS、watchOS的统一颜色解决方案
DynamicColor跨平台开发指南:iOS、macOS、watchOS的统一颜色解决方案 【免费下载链接】DynamicColor Yet another extension to manipulate colors easily in Swift and SwiftUI 项目地址: https://gitcode.com/gh_mirrors/dy/DynamicColor DynamicColor是一…...

