传输加解密 RuoYi-Vue-PLus 4.x

这里写自定义目录标题

  • 前言
  • 代码修改
    • 前端
      • 引入步骤:
      • 代码解释:
      • 代码
    • 后端
      • 引入步骤:
      • 代码解释:
    • 代码

前言

RuoYi-VUE-Plus 5.x的版本 已经完成了 前端加密传输给后端的代码,本文章只是将代码迁移到4.x 并完成 后端加密后给前端的代码,具体内容和加密方式都是使用作者:疯狂的狮子Li,欢迎大家使用

代码修改

前端

一、前端相关代码

引入步骤:

  1. 文件替换:将Utils 下面的代码进行替换,根据原有模块的功能 需要加入crypto、jsencrypt、request几个文件
  2. 配置密钥:jsencrypt里面的公钥和私钥需要和后端的配置文件一致起来
  3. 引入依赖:package.json文件下 引入npm 依赖 “crypto-js”: “^4.1.1”,

代码解释:

**request**: 请求文件,里面包含了请求加密和返回参数解密,通过判断header头中是否存在固定值来确定
**crypto** :AES、Base64 加解密文件主要是对参数进行加解密的方式
**jsencrypt**: RSA加解密文件 主要是对密钥进行加解密

密钥加密流程
1. 生成32位AES密码
2. Base64 加密AES密码
3. 利用RSA将 加密后的AES密码进行加密
4. 传输给后端
数据加密流程
使用32位AES密码作为key,加密数据 encryptWithAes(JSON.stringify(config.data), aesKey)

代码

package.json

"crypto-js": "^4.1.1",

src/utils 下 crypto.js

import CryptoJS from 'crypto-js';/*** 随机生成32位的字符串* @returns {string}*/
const generateRandomString = () => {const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';let result = '';const charactersLength = characters.length;for (let i = 0; i < 32; i++) {result += characters.charAt(Math.floor(Math.random() * charactersLength));}return result;
};/*** 随机生成aes 密钥* @returns {string}*/
export const generateAesKey = () => {return CryptoJS.enc.Utf8.parse(generateRandomString());
};/*** 加密base64* @returns {string}*/
export const encryptBase64 = (str) => {return CryptoJS.enc.Base64.stringify(str);
};/*** 解密base64* @returns {string}*/
export const decryptBase64 = (str) => {var words = CryptoJS.enc.Base64.parse(str);return CryptoJS.enc.Utf8.stringify(words);
};/*** 使用密钥对数据进行加密* @param message* @param aesKey* @returns {string}*/
export const encryptWithAes = (message, aesKey) => {const encrypted = CryptoJS.AES.encrypt(message, aesKey, {mode: CryptoJS.mode.ECB,padding: CryptoJS.pad.Pkcs7});return encrypted.toString();
};/*** 使用密钥对数据进行加密* @param message* @param aesKey* @returns {string}*/
export const decryptWithAes = (message, aesKey) => {const keyBytes = CryptoJS.enc.Utf8.parse(aesKey);const decrypt = CryptoJS.AES.decrypt(message, keyBytes, {mode: CryptoJS.mode.ECB,padding: CryptoJS.pad.Pkcs7});return decrypt.toString(CryptoJS.enc.Utf8)
};

3.jsencrypt.js

import JSEncrypt from 'jsencrypt/bin/jsencrypt.min'// 密钥对生成 http://web.chacuo.net/netrsakeypairconst publicKey = 'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdH\n' +'nzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQdAQ==自己生成密钥对 这里的我删除了'const privateKey = 'MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAqhHyZfSsYourNxaY\n' +'7Nt+PrgrxkiA50efORdI5U5lsW79MmFnusUA355oaSXcLhu5xxB38SMSyP2KvuKN\n' +'DQIgIdhDTIqD2jfYjPTY8Jj3EDGPbH2HHuffvflECt3Ek60CIQCFRlCkHpi7hthh\n' +'YhovyloRYsM+IS9h/0BzlEAuO0ktMQIgSPT3aFAgJYwKpqRYKlLDVcflZFCKY7u3\n' +'UP8iWi1Qw0Y='// 加密
export function encrypt(txt) {const encryptor = new JSEncrypt()encryptor.setPublicKey(publicKey) // 设置公钥return encryptor.encrypt(txt) // 对数据进行加密
}// 解密
export function decrypt(txt) {const encryptor = new JSEncrypt()encryptor.setPrivateKey(privateKey) // 设置私钥return encryptor.decrypt(txt) // 对数据进行解密
}

4.request.js

import axios from 'axios'
import {Loading, Message, MessageBox, Notification} from 'element-ui'
import store from '@/store'
import {getToken} from '@/utils/auth'
import errorCode from '@/utils/errorCode'
import {blobValidate, tansParams} from "@/utils/ruoyi";
import cache from '@/plugins/cache'
import {saveAs} from 'file-saver'
import {decrypt, encrypt} from "@/utils/jsencrypt";
import {decryptBase64, decryptWithAes, encryptBase64, encryptWithAes, generateAesKey} from '@/utils/crypto';let downloadLoadingInstance;
// 是否显示重新登录
export let isRelogin = {show: false};axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'
// 对应国际化资源文件后缀
axios.defaults.headers['Content-Language'] = 'zh_CN'
// 创建axios实例
const service = axios.create({// axios中请求配置有baseURL选项,表示请求URL公共部分baseURL: process.env.VUE_APP_BASE_API, // 超时timeout: 10000
})// request拦截器
service.interceptors.request.use(config => {// 是否需要设置 tokenconst isToken = (config.headers || {}).isToken === false// 是否需要防止数据重复提交const isRepeatSubmit = (config.headers || {}).repeatSubmit === false// 是否需要加密const isEncrypt = (config.headers || {}).isEncrypt === true;if (getToken() && !isToken) {config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改}// get请求映射params参数if (config.method === 'get' && config.params) {let url = config.url + '?' + tansParams(config.params);url = url.slice(0, -1);config.params = {};config.url = url;}if (!isRepeatSubmit && (config.method === 'post' || config.method === 'put')) {const requestObj = {url: config.url, data: typeof config.data === 'object' ? JSON.stringify(config.data) : config.data, time: new Date().getTime()}const sessionObj = cache.session.getJSON('sessionObj')if (sessionObj === undefined || sessionObj === null || sessionObj === '') {cache.session.setJSON('sessionObj', requestObj)} else {const s_url = sessionObj.url;                  // 请求地址const s_data = sessionObj.data;                // 请求数据const s_time = sessionObj.time;                // 请求时间const interval = 1000;                         // 间隔时间(ms),小于此时间视为重复提交if (s_data === requestObj.data && requestObj.time - s_time < interval && s_url === requestObj.url) {const message = '数据正在处理,请勿重复提交';console.warn(`[${s_url}]: ` + message)return Promise.reject(new Error(message))} else {cache.session.setJSON('sessionObj', requestObj)}}}// 当开启参数加密if (isEncrypt && (config.method === 'post' || config.method === 'put')) {// 生成一个 AES 密钥const aesKey = generateAesKey();console.log(aesKey)config.headers['encrypt-key'] = encrypt(encryptBase64(aesKey));config.data = typeof config.data === 'object' ? encryptWithAes(JSON.stringify(config.data), aesKey) : encryptWithAes(config.data, aesKey);}// FormData数据去请求头Content-Typeif (config.data instanceof FormData) {delete config.headers['Content-Type'];}return config;
}, error => {console.log(error)Promise.reject(error)
})// 响应拦截器
service.interceptors.response.use(res => {if (res.headers && res.headers['encrypt-key']) {const encryptByRsa = res.headers['encrypt-key']//取出aes 加密后的key 然后解密const encryptAesByBase64 = decrypt(encryptByRsa)const aesPassword = decryptBase64(encryptAesByBase64)  //eDBLQjEyQlBtZ1Y0ZjBLam9MZnJqUXNDd0I3dkJpenA=res.data = JSON.parse(decryptWithAes(res.data, aesPassword));}//解密出现错误转json 提示错误if (typeof res.data === 'string') {res.data = JSON.parse(res.data)}// 未设置状态码则默认成功状态const code = res.data.code || 200;// 获取错误信息const msg = errorCode[code] || res.data.msg || errorCode['default']// 二进制数据则直接返回if (res.request.responseType === 'blob' || res.request.responseType === 'arraybuffer') {return res.data}if (code === 401) {if (!isRelogin.show) {isRelogin.show = true;MessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', {confirmButtonText: '重新登录', cancelButtonText: '取消', type: 'warning'}).then(() => {isRelogin.show = false;store.dispatch('LogOut').then(() => {location.href = process.env.VUE_APP_CONTEXT_PATH + "index";})}).catch(() => {isRelogin.show = false;});}return Promise.reject('无效的会话,或者会话已过期,请重新登录。')} else if (code === 500) {Message({message: msg, type: 'error'})return Promise.reject(new Error(msg))} else if (code === 601) {Message({message: msg, type: 'warning'})return Promise.reject('error')} else if (code !== 200) {Notification.error({title: msg})return Promise.reject('error')} else {return res.data}
}, error => {console.log('err' + error)let {message} = error;if (message == "Network Error") {message = "后端接口连接异常";} else if (message.includes("timeout")) {message = "系统接口请求超时";} else if (message.includes("Request failed with status code")) {message = "系统接口" + message.substr(message.length - 3) + "异常";}Message({message: message, type: 'error', duration: 5 * 1000})return Promise.reject(error)
})// 通用下载方法
export function download(url, params, filename, config) {downloadLoadingInstance = Loading.service({text: "正在下载数据,请稍候", spinner: "el-icon-loading", background: "rgba(0, 0, 0, 0.7)",})return service.post(url, params, {transformRequest: [(params) => {return tansParams(params)}], headers: {'Content-Type': 'application/x-www-form-urlencoded'}, responseType: 'blob', ...config}).then(async (data) => {const isBlob = blobValidate(data);if (isBlob) {const blob = new Blob([data])saveAs(blob, filename)} else {const resText = await data.text();const rspObj = JSON.parse(resText);const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default']Message.error(errMsg);}downloadLoadingInstance.close();}).catch((r) => {console.error(r)Message.error('下载文件出现错误,请联系管理员!')downloadLoadingInstance.close();})
}export default service

上面的代码基本是作者自己的可以参考5.X的代码


后端

二、后端相关代码

引入步骤:

  1. 文件替换:后端文件 包括:加解密方法、拦截器、配置文件,按照文件位置替换进去,配置文件中加了api-decrypt: 配置参数

  2. 配置密钥:可以通过互联网生成的key 需要与前端密钥相同,否则会导致无法解密

代码解释:

EncryptFilter :判断是否是排除加密路由,进行加密 具体看代码

密钥加密流程

  1. 生成32位AES密码
  2. Base64 加密AES密码
  3. 利用RSA将 加密后的AES密码进行加密
  4. 传输给后端

数据加密流程

使用32位AES密码作为key,加密数据 EncryptUtils.encryptByAes(responseBody, aesPassword);

代码

1.application.yml

# api接口加密
api-decrypt:# 是否开启全局接口加密enabled: true# AES 加密头标识headerFlag: encrypt-key# 公私钥 非对称算法的公私钥 如:SM2,RSA 使用者请自行更换publicKey: MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ==privateKey: MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAqhHyZfSsYourNxaY7Nt+PrgrxkiA50efORdI5U5lsWqoTt2UZwA5E2MzS4EI2gjfQhz5X28uqxAiEA3wNFxfrCZlSZHb0gn2zDpWowcSxQAgiCstxGUoOqlW8CIQDDOerGKH5OmCJ4Z21v+F25WaHYPxCFMvwxpcw99EcvDQIgIdhDTIqD2jfYjPTY8Jj3EDGPbH2HHuffvflECt3Ek60CIQCFRlCkHpi7hthhYhovyloRYsM+IS9h/0BzlEAuO0ktMQIgSPT3aFAgJYwKpqRYKlLDVcflZFCKY7u3UP8iWi1Qw0Y=# 不需要加密路径encryptExcludes: /monitor/*,/tool/*,/captchaImage

2.过滤器

package com.ruoyi.framework.encrypt.filter;//package com.yawei.gateway.filter;/*** 描述** @author :suiquantong* @date : 2023/3/15 9:22*/import cn.hutool.core.util.RandomUtil;
import com.ruoyi.common.constant.HttpStatus;
import com.ruoyi.common.utils.EncryptUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.framework.config.properties.ApiDecryptProperties;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;/*** 解密拦过滤器** @author yawei*/
public class EncryptFilter implements Filter {private final ApiDecryptProperties properties;/*** 排除链接*/public List<String> excludes = new ArrayList<>();public EncryptFilter(ApiDecryptProperties properties) {this.properties = properties;}@Overridepublic void init(FilterConfig filterConfig) throws ServletException {String tempExcludes = filterConfig.getInitParameter("excludes");if (StringUtils.isNotEmpty(tempExcludes)) {String[] url = tempExcludes.split(StringUtils.SEPARATOR);for (int i = 0; url != null && i < url.length; i++) {excludes.add(url[i]);}}}@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {HttpServletRequest servletRequest = (HttpServletRequest) request;String url = servletRequest.getServletPath();/** 1.过滤不需要加密url (可选)* 2.过滤非Json 格式数据*/if (!StringUtils.matches(url, excludes)) {
//        if (false) {EncryptResponseWrapper responseWrapper = new EncryptResponseWrapper((HttpServletResponse) response);//执行业务逻辑 交给下一个过滤器或servlet处理chain.doFilter(request, responseWrapper);System.out.println("responseWrapper.getContentType() = " + responseWrapper.getContentType());if (!isJsonResponse(response)) {System.out.println("response.getContentType() = " + response.getContentType());//不需要加密chain.doFilter(request, response);}else {try {//获取返回体byte[] responseData = responseWrapper.getResponseData();//设置响应内容格式,防止解析响应内容时出错
//                responseWrapper.setContentType("text/plain;charset=UTF-8");String responseBody = new String(responseData, StandardCharsets.UTF_8);//生成aes密码 采用res加密  与前端相同逻辑//生成aes 32位随机码,String aesPassword = RandomUtil.randomString("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 32);//加密aes 密钥String encryptAesByBase64 = EncryptUtils.encryptByBase64(aesPassword);String encryptByRsa = EncryptUtils.encryptByRsa(encryptAesByBase64, properties.getPublicKey());//绑定返回头标识responseWrapper.setHeader(properties.getHeaderFlag(), encryptByRsa);//加密内容String encryptBody = EncryptUtils.encryptByAes(responseBody, aesPassword);PrintWriter out = response.getWriter();out.print(encryptBody);out.flush();out.close();} catch (Exception e) {try {getFailResponse(responseWrapper);} catch (IOException ioException) {ioException.printStackTrace();}e.printStackTrace();}}} else {//不需要加密chain.doFilter(request, response);}}/*** 有错误相应返回-44** @param response* @throws IOException*/private void getFailResponse(HttpServletResponse response) throws IOException {response.setCharacterEncoding("UTF-8");response.setContentType("application/json; charset=utf-8");PrintWriter out = null;out = response.getWriter();out.write("{\n" + "    \"status\":" + HttpStatus.ENCRYPT_FAIL + ",\n" + "    \"message\": 解密出现异常!,\n" + "    \"data\": []\n" + "}");//加密后的错误消息
//        out.write("+D+JO8tuwkrNbxnTTLdqStifmQceT+LlYETnIG/JZKrbAn+gIiqIp3VbzBV1y6R8B7aY53VM2xHa7cY3Osbnqw==");out.flush();out.close();}/*** 是否是Json请求*/public boolean isJsonResponse(ServletResponse response) {String contentType = response.getContentType();return StringUtils.startsWithIgnoreCase(contentType, MediaType.APPLICATION_JSON_VALUE);}
}
package com.ruoyi.framework.encrypt.filter;import cn.hutool.core.io.IoUtil;
import com.ruoyi.common.constant.HttpStatus;
import com.ruoyi.common.utils.EncryptUtils;import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import java.io.*;
import java.nio.charset.StandardCharsets;/*** @Description: 响应包装类* @Date: 2020/5/26 16:29*/
public class EncryptResponseWrapper extends HttpServletResponseWrapper {private ByteArrayOutputStream buffer = null;private ServletOutputStream out = null;private PrintWriter writer = null;public EncryptResponseWrapper(HttpServletResponse response) throws IOException {super(response);buffer = new ByteArrayOutputStream();// 真正存储数据的流out = new WapperedOutputStream(buffer);writer = new PrintWriter(new OutputStreamWriter(buffer,this.getCharacterEncoding()));}/** 重载父类获取outputstream的方法 */@Overridepublic ServletOutputStream getOutputStream() throws IOException {return out;}/** 重载父类获取writer的方法 */@Overridepublic PrintWriter getWriter() throws UnsupportedEncodingException {return writer;}/** 重载父类获取flushBuffer的方法 */@Overridepublic void flushBuffer() throws IOException {if (out != null) {out.flush();}if (writer != null) {writer.flush();}}@Overridepublic void reset() {buffer.reset();}/** 将out、writer中的数据强制输出到WapperedResponse的buffer里面,否则取不到数据 */public byte[] getResponseData() throws IOException {flushBuffer();return buffer.toByteArray();}/** 内部类,对ServletOutputStream进行包装 */private class WapperedOutputStream extends ServletOutputStream {private ByteArrayOutputStream bos = null;public WapperedOutputStream(ByteArrayOutputStream stream)throws IOException {bos = stream;}@Overridepublic void write(int b) throws IOException {bos.write(b);}@Overridepublic void write(byte[] b) throws IOException {bos.write(b, 0, b.length);}@Overridepublic boolean isReady() {return false;}@Overridepublic void setWriteListener(WriteListener writeListener) {}}public static   ByteArrayOutputStream toByteArray(InputStream input) throws IOException {ByteArrayOutputStream output = new ByteArrayOutputStream();byte[] buffer = new byte[1024*4];int n = 0;while (-1 != (n = input.read(buffer))) {output.write(buffer, 0, n);}return output;}
}

3.具体内容太多了 直接上附件吧

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/109313.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

HTML渲染过程

整个渲染过程&#xff1a; 将 URL 对应的各种资源&#xff0c;通过浏览器渲染引擎的解析&#xff0c;输出可视化的图像。 基本概念&#xff1a; HTML 解释器&#xff1a;解析html语言、将html文本翻译成dom树&#xff1b; CSS 解释器&#xff1a;解析css语言&#xff0c;给…

【JVM面试】从JDK7 到 JDK8, JVM为啥用元空间替换永久代?

系列文章目录 【JVM系列】第一章 运行时数据区 【面试】第二章 从JDK7 到 JDK8, JVM为啥用元空间替换永久代&#xff1f; 大家好&#xff0c;我是青花。拥有多项发明专利&#xff08;都是关于商品、广告等推荐产品&#xff09;。对广告、Web全栈以及Java生态微服务拥有自己独到…

Linux SVN 命令详解

1、将文件 checkout 到本地目录 svn checkout path&#xff08;path是服务器上的目录&#xff09; 例如&#xff1a;svn checkout svn://192.168.1.1/pro/domain 简写&#xff1a;svn co 2、往版本库中添加新的文件 svn add file 例如&#xff1a;svn add test.php(添加test.…

MySQL [基础] 学习笔记

MySQL 学习 文章目录 MySQL 学习1. 数据库三层结构2. 数据在数据库中的存储方式3. SQL 语句分类3.1 备份恢复数据库的表 4. Mysql 常用数据类型(列类型)4.1 数值型(整数)的基本使用4.2 数值型(bit)的使用4.3 数值型(小数)的基本使用4.4 字符串的基本使用(面试题)4.5 字符串使用…

2020年下半年~2022下半年下午题易错总结

2020年下半年 试题一&#xff1a; 1.组播报文对无线网络空口的影响主要有(14) &#xff0c;随着业务数据转发的方式不同, 组播报文的抑制分别在 (15)、(16) 配置。 答案&#xff1a; &#xff08;14&#xff09;无线空口拥塞 &#xff08;15&#xff09;直连AP的交换…

软件测试需要学习什么?好学吗?需要学多久?到底是报班好还是自学好?

前言&#xff1a; 上篇文章看到很多小伙伴在讨论做测试到底怎么样&#xff0c; 其中很有很多的小伙伴还踩不少的坑&#xff0c;花费了大量的精力和时间去探索&#xff0c;结果还是一无所获。这里给大家出一期关于软件测试萌新的疑惑&#xff0c;看完这篇文章你就知道软件测试…

AR智能眼镜主板设计方案_AR眼镜PCB板设计

AR智能眼镜是一种采用先进技术的创新产品&#xff0c;具备强大的功能和性能。它采用了MTK8788八核 12nm低功耗硬件平台&#xff0c;搭载IMG GE830063OMhz或以上的GPU&#xff0c;并运行Android 11.0或以上的操作系统。该眼镜支持光波导1080P显示和LVDS接口自由曲面显示&#xf…

Linux命令(101)之nl

linux命令之nl 1.nl 介绍 linux命令nl用来将输出的文件内容自动加上行号 2.nl用法 nl [参数] filename nl常用参数 参数说明-b a输出内容都加上行号-b t只有非空行的输出加上行号&#xff0c;默认选项-n ln在最左方加上行号-n rn在最右方加上行号&#xff0c;且不加0-n rz在…

信钰证券:新增融券交易明显降温 业内称新规将平衡多类型投资者利益

10月14日&#xff0c;中国证监会发布调整优化融券相关准则的通知&#xff0c;沪深北买卖所齐发具体安排&#xff0c;阶段性收紧融券和战略出资者配售股份出借。其间&#xff0c;融券保证金比例进步自10月30日起实施&#xff0c;战略出资者配售股份出借收紧等其他条款自10月16日…

Java面试题-Java核心基础-第五天(面向对象基础)

目录 一、面向对象与面向过程的区别 二、创建一个对象用什么运算符&#xff0c;对象实体与对象引用有什么区别&#xff1f; 三、对象的相等和引用的相等有什么区别 四、如果一个类没有构造方法&#xff0c;程序能正常执行吗&#xff1f; 五、构造方法有什么特点&#xff0c…

爆肝整理,性能测试-非GUI模式执行Jemter压测,看这篇就够了...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 1、上传脚本 把在…

智慧公厕:探索未来城市环境卫生设施建设新标杆

智慧公厕是当代城市建设的一项重要举措&#xff0c;它集先进技术、人性化设计和智能管理于一体&#xff0c;为人们提供更为舒适、便捷和卫生的厕所环境。现代智慧公厕的功能异常丰富&#xff0c;从厕位监测到多媒体信息交互&#xff0c;从自动化清洁到环境调控&#xff0c;每一…

chatgpt图片识别、生成图片、语音对话多模态深度试玩

大模型替代人的工作的能力&#xff0c;越来越明显了。最近chatgpt支持多模态了&#xff0c;看这大佬们玩的不易乐乎&#xff0c;手痒也想试一试&#xff0c;因此有给openai上供了20刀。 另外我是gpt的拥护者&#xff0c;但是周围的同事有对此担忧&#xff0c;因为他们长期积累的…

Tengine 边缘AI计算框架移植RV1126(包括opencv的交叉编译)

目录 1.编译opencv 2.拷贝SDK源码到虚拟机 3. 拉取TIM-VX代码 4.拉取Tengine源码并配置 1.编译opencv 编译opencv是为了&#xff0c;在编译Tengine时指定OpenCVConfig.cmake,以便寻找特定的opencv动态库 01.从github拉取opencv源代码 git clone -b 4.5.5 https://github.co…

【AIFEM案例操作】水轮机转轮强度和模态分析

AIFEM是由天洑自主研发的一款通用的智能结构仿真软件&#xff0c;助力用户解决固体结构相关的静力学、动力学、振动、热力学等实际工程问题&#xff0c;软件提供高效的前后处理工具和高精度的有限元求解器&#xff0c;帮助用户快速、深入地评估结构的力学性能&#xff0c;加速产…

DICOM图像像素值、灰度值与CT值的解释及关系

​DICOM图像像素值、灰度值与CT值之间的关系可以通过以下方式进行解释&#xff1a; DICOM图像像素值&#xff1a;在DICOM图像中&#xff0c;像素值是用来表示图像信息的基本单位。这些值通常在0-4096的范围内&#xff0c;反映了图像的像素亮度。 DICOM图像灰度值&#xff1a;灰…

H264短期参考帧和长期参考帧

问题引出 最近在做短期参考帧的marking操作时发现一个问题,我的预期是用两个短期参考帧,其中一个短期参考帧为当前帧的前一帧,另一个为距离当前位置较远的一个帧。 在大多数的帧都没有问题,但是在个别帧的位置会发生参考帧列表中帧的顺序和预期不一致的情况,我在第28帧的…

SLAM算法中状态估计的算法有哪些?

状态估计 在SLAM(Simultaneous Localization and Mapping)中,状态估计是核心组件之一,其主要目的是估计机器人的轨迹(或姿态)和地图特征的位置。针对不同的传感器、场景和应用,已经发展出了多种状态估计方法。以下是一些主要的状态估计方法: 扩展卡尔曼滤波(Extended…

多测师肖sir_高级金牌讲师___python之json模块

python之json模块 python标准模块之json 定义&#xff1a;json &#xff08;java script object notation&#xff09;是轻量级的文本数据交换格式 案例json&#xff1a; json和字典 一样 一、json模块可以实现json数据的序列化和反序列化 &#xff08;1&#xff09;序列化…

Linux常用命令——comm命令

在线Linux命令查询工具 comm 两个文件之间的比较 补充说明 comm命令可以用于两个文件之间的比较&#xff0c;它有一些选项可以用来调整输出&#xff0c;以便执行交集、求差、以及差集操作。 交集&#xff1a;打印出两个文件所共有的行。求差&#xff1a;打印出指定文件所包…