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

SpringBoot3集成TDengine自适应裂变存储

前言

        首先很遗憾的告诉大家,今天这篇分享要关注才可以看了。原因是穷啊,现在基本都是要人民币玩家了,就比如chatGPT、copilot,这些AI虽然都是可以很好的辅助编码,但是都是要钱。入驻CSDN有些年头了,中间有几年大学毕业,失恋了没有写,沉沦了几年。后面逐渐捡起来,我们之间应该说是互相成就吧,亦师亦友亦笔记。说实话,其实CSDN之前有出一些插件,我很欣慰,也一直在用,其实我一直希望CSDN能出个copilot采用AI辅助就好了。或者国内几大技术论坛能一起搞个也行,其实大家都是有这方面的优势的,至少代码、训练库是足够的。
        言归正传,今天要要分享的要是紧接之前的设计:物联网设备流水入库TDengine改造方案,这里是具体的实现过程。这个是TDengine可自动扩展列方案,这个方案实现代码绝对是目前独家,关注我,你值得拥有。


一、整体思路

        整体思路:消费信息 》》 数据转换 》》组织sql 》》orm框架自动配备数据源》》执行入库TDengine》》异常处理(扩展的核心)》》DDL执行扩列》》再次执行入库。。。。
        这里大家应该可以猜到具体做法了,其实要不是因为这个列不固定,实现起来可简单多了,也可以用超级表,而且性能也会好很多。更重要的是可以用ORM框架,基本不用写啥sql。而且查询结果用实体接受数据,不会出现VARCHAR字段不能正确显示字符串的问题(我就是被这个坑了下)。
        其实也可以用flink等消费信息,做入库处理,当然这样处理可就不能用ORM框架了,只能用经典的JDBC。
        核心思路:根据设备上报数据,做插入数据转换sql,执行入库处理异常,根据异常做DDL操作,实现自动扩列,最后入库。上报的数据:json串做数据转换,数据值做反射获取类型,转换为对应的扩列sql执行、组织入库sql。

二、实现流程图

在这里插入图片描述
我的整体环境:SpringBoot3 + mybatisPlus + 双数据源(mysql、TDengine)+ 集成kafka
消费上游平台放入kafka的信息,然后走以上流程,目标执行入库TDengine。

三、核心代码

这里的整体框架我之前的博文有写,并且是公开独家分享到csdn的gitCode:https://gitcode.net/zwrlj527/data-trans.git

1.引入库

<dependency><groupId>org.springframework.kafka</groupId><artifactId>spring-kafka</artifactId>
</dependency>

2.配置文件

spring:
#kafka配置kafka:#bootstrap-servers: 192.168.200.72:9092,192.168.200.73:9092#bootstrap-servers: 192.168.200.83:9092,192.168.200.84:9092bootstrap-servers: localhost:9092client-id: dc-device-flow-analyzeconsumer:group-id: dc-device-flow-analyze-consumer-groupmax-poll-records: 10#Kafka中没有初始偏移或如果当前偏移在服务器上不再存在时,默认区最新 ,有三个选项 【latest, earliest, none】auto-offset-reset: earliest#是否开启自动提交enable-auto-commit: false#自动提交的时间间隔auto-commit-interval: 1000listener:ack-mode: MANUAL_IMMEDIATEconcurrency: 1  #推荐设置为topic的分区数type: BATCH #开启批量监听#消费topic配置
xiaotian:analyze:device:flow:topic:consumer: device-flow

3.kafka消费监听

package com.xiaotian.datagenius.kafka;import com.xiaotian.datagenius.service.DataTransService;
import lombok.extern.slf4j.Slf4j;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.kafka.support.Acknowledgment;
import org.springframework.stereotype.Component;import java.util.List;/*** 消费者listener** @author zhengwen**/
@Slf4j
@Component
public class KafkaListenConsumer {@Autowiredprivate DataTransService dataTransService;/*** 设备流水listenner** @param records 消费信息* @param ack     Ack机制*/@KafkaListener(topics = "${easylinkin.analyze.device.flow.topic.consumer}")public void deviceFlowListen(List<ConsumerRecord> records, Acknowledgment ack) {log.debug("=====设备流水deviceFlowListen消费者接收信息====");try {for (ConsumerRecord record : records) {log.debug("---开启线程解析设备流水数据:{}", record.toString());dataTransService.deviceFlowTransSave(record);}} catch (Exception e) {log.error("----设备流水数据消费者解析数据异常:{}", e.getMessage(), e);} finally {//手动提交偏移量ack.acknowledge();}}}

4.消息具体处理方法(实现)

package com.xiaotian.datagenius.service.impl;import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.xiaotian.datagenius.mapper.tdengine.DeviceFlowRecordMapper;
import com.xiaotian.datagenius.mapper.tdengine.TableOperateMapper;
import com.xiaotian.datagenius.service.DataTransService;
import com.xiaotian.datagenius.utils.TDengineDbUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;import java.util.*;/*** @author zhengwen*/
@Slf4j
@Service
public class DataTransServiceImpl implements DataTransService {/*** 专门记录业务错误日志*/private final static Logger logger = LoggerFactory.getLogger("businessExp");@Autowiredprivate KafkaTemplate kafkaTemplate;@Autowiredprivate TableOperateMapper tableOperateMapper;@Autowiredprivate DeviceFlowRecordMapper deviceFlowRecordMapper;@Overridepublic void deviceFlowTransSave(ConsumerRecord record) {log.debug("----设备流水转换解析存储----");log.debug(String.format("offset = %d, key = %s, value = %s%n \n", record.offset(), record.key(), record.value()));//字段不可控,所以没有实体可言,只能直接sql//先直接执行插入,try异常 -> 如果是报字段不存在 -> 执行校验字段 -> dml创建字段//再执行插入String stableName = "device_flow_mater";String tableName = "device_flow_record";String recordStr = record.value().toString();if (JSONUtil.isTypeJSON(recordStr)) {JSONObject recordJson = JSONUtil.parseObj(recordStr);//初始化语句Map<String, Map<String, String>> columnData = new HashMap<>();String insertSql = initDataInsertSql(recordJson, tableName, columnData);//保存数据saveRecord(recordJson, insertSql, columnData, tableName);} else {logger.error("---设备上报数据推送信息格式异常,无法解析---");}}/*** 初始化数据插入语句** @param recordJson 记录json* @param tableName  表名* @param columnData 字段信息* @return 数据插入语句*/private String initDataInsertSql(JSONObject recordJson, String tableName, Map<String, Map<String, String>> columnData) {//这里先转换成sql的字段、valueStringJoiner columnSj = new StringJoiner(",");StringJoiner valueSj = new StringJoiner(",");String insertSql = transInitInsertSql(tableName, columnSj, valueSj, recordJson, columnData);if (StringUtils.isBlank(insertSql)) {logger.error("---上报数据转插入语句异常,上报数据:{}", JSONUtil.toJsonStr(recordJson));return null;}return insertSql;}/*** 保存记录** @param recordJson 记录json对象* @param insertSql  插入语句* @param columnData 字段信息* @param tableName  普通表或子表*/private void saveRecord(JSONObject recordJson, String insertSql, Map<String, Map<String, String>> columnData, String tableName) {try {//boolean insertRes = SqlRunner.db(DeviceFlowMaterRecord.class).insert(insertSql, '1');int num = deviceFlowRecordMapper.insert(insertSql);} catch (Exception e) {logger.error("Error inserting,{}", e.getMessage());Throwable throwable = e.getCause();String msg = throwable.getMessage();//报缺少字段、字段长度不够if (msg.contains("Invalid column name:") || msg.contains("Value too long for column/tag")) {transAddOrChangeColumnsSql(columnData, tableName, recordJson, insertSql);}}}/*** 转换扩展列** @param columnData 上报数据字段信息map* @param tableName  表名* @param recordJson 上报数据json* @param insertSql  插入语句*/private void transAddOrChangeColumnsSql(Map<String, Map<String, String>> columnData, String tableName, JSONObject recordJson, String insertSql) {String showColumnsSql = "desc " + tableName;List<Map<String, Object>> columnLs = tableOperateMapper.operateSql(showColumnsSql);if (CollectionUtil.isNotEmpty(columnLs)) {//StringBuffer sbf = new StringBuffer();//sbf.append("ALTER TABLE ").append(tableName).append(" ADD COLUMN ");Map<String, Map<String, Object>> tableColumns = new HashMap<>();columnLs.stream().forEach(c -> {Object byBufferObj = c.get("field");//获取字段String field = TDengineDbUtil.getColumnInfoBy(byBufferObj);tableColumns.put(field, c);});columnData.entrySet().forEach(c -> {String key = c.getKey();Map<String, String> columnMp = c.getValue();String length = columnMp.get("length");if (tableColumns.containsKey(key)) {//包含字段,比较数据类型长度Map<String, Object> tcMp = tableColumns.get(key);Object byBufferObj = tcMp.get("length");//获取字段长度String dbLength = TDengineDbUtil.getColumnInfoBy(byBufferObj);if (dbLength != null) {if (Integer.parseInt(length) > Integer.parseInt(dbLength)) {String changeColumnSql = TDengineDbUtil.getColumnChangeSql(tableName,length,key);tableOperateMapper.operateSql(changeColumnSql);}}} else {//不包含需要执行增加字段String addColumnSql = TDengineDbUtil.getColumnAddSql(tableName,length,key);tableOperateMapper.operateSql(addColumnSql);}});//复调存储saveRecord(recordJson, insertSql, columnData, tableName);}}/*** 转换初始化插入语句sql** @param tableName  表名* @param columnSj   字段字符串* @param valueSj    值字符串* @param recordJson 上报数据json* @param columnData 字段Map* @return 插入语句sql*/private String transInitInsertSql(String tableName, StringJoiner columnSj, StringJoinervalueSj, JSONObject recordJson, Map<String, Map<String, String>> columnData) {StringBuffer sb = new StringBuffer();//子表不能扩展列,所以超级表思路走不通sb.append("insert into ").append(tableName);if (!JSONUtil.isNull(recordJson)) {JSONObject tmpRecordJson = recordJson;JSONObject dataJson = tmpRecordJson.getJSONObject("data");Date collectTime = tmpRecordJson.getDate("collectTime");tmpRecordJson.remove("data");tmpRecordJson.entrySet().forEach(entry -> {//TODO 这里要设置调整下数据库区分大小写后去掉//String key = entry.getKey().toLowerCase();String key = entry.getKey();columnSj.add("`" + key + "`");Object val = entry.getValue();//TODO 校验字符串类型处理sqlint length = 5;if (val != null) {//TODO 几个时间字段传的是long,是转时间类型,还是改字段为字符串?String valStr = TDengineDbUtil.convertValByKey(val,key);valueSj.add(valStr);length = valStr.length() + 5;} else {valueSj.add(null).add(",");}//TODO 字段数据类型后面要优化处理Map<String, String> columnMp = TDengineDbUtil.checkColumnType(key, val, length);columnData.put(key, columnMp);});if (!JSONUtil.isNull(dataJson)) {dataJson.entrySet().forEach(entry -> {//TODO 这里要设置调整下数据库区分大小写后去掉String key = entry.getKey();columnSj.add("`" + key + "`");Object val = entry.getValue();int length = 3;if (val != null) {//TODO 几个时间字段传的是long,是转时间类型,还是改字段为字符串?String valStr = TDengineDbUtil.convertValByKey(val,key);valueSj.add(valStr);length = valStr.length() + 1;} else {valueSj.add(null).add(",");}//TODO 字段数据类型后面要优化处理Map<String, String> columnMp = TDengineDbUtil.checkColumnType(key, val, length);columnData.put(key, columnMp);});}//Tags//sb.append(" TAGS (").append(dataJson).append(",").append(deviceUnitCode).append(",").append(deviceCode).append(") ");//sb.append(" TAGS ('").append(JSONUtil.toJsonStr(dataJson)).append("') ");//ColumnscolumnSj.add("`data_ts`");sb.append("(").append(columnSj.toString()).append(") ");//Values//valueSj.add("'" + DateUtil.format(collectTime, DatePattern.NORM_DATETIME_MS_FORMAT) + "'");//主键应该是时间,不能是设备上报数据的时间,因为设备上报数据万一相同就更新了valueSj.add("NOW");sb.append(" VALUES (").append(valueSj.toString()).append(")");logger.debug("----插入语句sql:", sb.toString());return sb.toString();}return null;}
}

5.工具类

package com.xiaotian.datagenius.utils;import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import io.micrometer.core.instrument.util.TimeUtils;
import lombok.extern.slf4j.Slf4j;import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;/*** TDengine数据库工具类** @author zhengwen*/
@Slf4j
public class TDengineDbUtil {/*** orm框架执行ddl语句返回的字段是byte数组处理** @param byBufferObj byte数组object对象* @return*/public static String getColumnInfoBy(Object byBufferObj) {try {if (byBufferObj instanceof byte[]) {byte[] bytes = (byte[]) byBufferObj;ByteArrayOutputStream bos = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(bos);oos.write(bytes);oos.flush();String strRead = new String(bytes);oos.close();bos.close();return strRead;}} catch (IOException e) {log.error("----字段异常:{}", e.getMessage());}return null;}/*** 校验字段类型返回字段信息** @param key    字段* @param val    值* @param length 长度* @return 字段信息*/public static Map<String, String> checkColumnType(String key, Object val, int length) {Map<String, String> columnMp = new HashMap<>();columnMp.put("type", "String");columnMp.put("length", String.valueOf(length));return columnMp;}/*** @param tableName* @param length* @param key* @return*/public static String getColumnAddSql(String tableName, String length, String key) {String beforeSql = "ALTER TABLE " + tableName + " ADD COLUMN ";//TODO 处理字段类型String addColumnSql = beforeSql + "`" + key + "` NCHAR(" + Integer.parseInt(length) + ")";return addColumnSql;}/*** @param tableName* @param length* @param key* @return*/public static String getColumnChangeSql(String tableName, String length, String key) {String changeLengthSql = "ALTER TABLE " + tableName + " MODIFY COLUMN ";//TODO 处理字段类型String changeColumnSql = changeLengthSql + "`" + key + "` NCHAR(" + length + ")";return changeColumnSql;}/*** 根据字段、字段值对插入sql的字段值做处理** @param val 字段原始值* @param key 字段* @return 字段转换后的值*/public static String convertValByKey(Object val, String key) {//其他全部当字符串处理String valStr = "'" + val.toString() + "'";//TODO 根据字段处理转换后的字段值,这里暂时对几个时间字段做特殊处理if (key.equals("collectTime") || key.equals("createTime") || key.equals("storageTime")) {if (val instanceof Long){LocalDateTime localDateTime = LocalDateTimeUtil.of(Long.parseLong(val.toString()));valStr = "'" + DateUtil.format(localDateTime,DatePattern.NORM_DATETIME_MS_PATTERN) + "'";}if (val instanceof Integer){LocalDateTime localDateTime = LocalDateTimeUtil.of(Long.parseLong(val.toString() + 100));valStr = "'" + DateUtil.format(localDateTime,DatePattern.NORM_DATETIME_MS_PATTERN) + "'";}}return valStr;}
}

6.Mapper的重要方法

TableOperateMapper

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.xiaotian.datagenius.mapper.tdengine.TableOperateMapper"><select id="operateSql" resultType="java.util.Map">${sql}</select>
</mapper>

DeviceFlowRecordMapper

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xiaotian.datagenius.mapper.tdengine.DeviceFlowRecordMapper"><!-- 通用查询映射结果 --><resultMap id="BaseResultMap" type="com.xiaotian.datagenius.entity.DeviceFlowRecord"><id column="data_ts" property="dataTs" /><result column="deviceUnitCode" property="deviceUnitCode" /><result column="deviceUnitName" property="deviceUnitName" /><result column="deviceCode" property="deviceCode" /><result column="deviceName" property="deviceName" /><result column="deviceTypeName" property="deviceTypeName" /><result column="collectTime" property="collectTime" /><result column="createTime" property="createTime" /><result column="storageTime" property="storageTime" /><result column="projectId" property="projectId" /><result column="companyId" property="companyId" /><result column="data" property="data" /><result column="showMessage" property="showMessage" /></resultMap><insert id="insert">${sql}</insert><select id="selectPageMap" resultType="java.util.Map">${sql}</select><select id="selectPageBy" parameterType="com.easylinkin.datagenius.vo.DeviceFlowRecordVo" resultType="java.util.Map">select r.*from device_flow_record rwhere 1 = 1<if test="param2 != null"><if test="param2.deviceCode != null and param2.deviceCode != ''">and r.`deviceCode` = #{param2.deviceCode}</if><if test="param2.startTime != null"><![CDATA[ and r.`collectTime` >= #{param2.startTime} ]]></if><if test="param2.endTime != null"><![CDATA[ and r.`collectTime` <= #{param2.endTime} ]]></if></if>order by r.`data_ts` desc</select>
</mapper>

        核心点就以上这些位置了,大家自行体会。


总结

  • TDengine还不错,官方有交流群,群里也有技术支持,不过肯定不是每一个问题都有回复
  • 各方面都在支持它,它的优化空间还很多,我们用开源实际也是在帮忙测试。就从开年到现在我就整这玩意,刚开始是3.0.2.3现在都迭代到3.0.2.5了
  • ORM框架也在逐步支持,但是官方支持明确跟我说了可能ORM框架会拖慢,影响性能。
  • 里面坑还是很多的,我就踩了乱码、返回字节码的问题
            就写到这里,希望能帮到大家!!有需要帮助的可以在CSDN发消息我。

相关文章:

SpringBoot3集成TDengine自适应裂变存储

前言 首先很遗憾的告诉大家&#xff0c;今天这篇分享要关注才可以看了。原因是穷啊&#xff0c;现在基本都是要人民币玩家了&#xff0c;就比如chatGPT、copilot&#xff0c;这些AI虽然都是可以很好的辅助编码&#xff0c;但是都是要钱。入驻CSDN有些年头了&#xff0c;中间有几…...

golang alpine 配置gstreamer开发环境

启动容器 sudo docker run -it --name golang -v $PWD:/home/leon -d golang:1.18-alpine3.17tar zxvf x86_64-linux-musl-cross.tgz mv x86_64-linux-musl-cross /usr/local/musl export PATH$PATH:/usr/local/musl/bin/:/usr/local/musl/x86_64-linux-musl/bin 下载gstre…...

SAP ABAP GUI_DOWNLOAD中下载乱码的问题

1 GUI_DOWNLOAD 1.1 问题表现 GUI_DOWNLOAD在应用当中有时会导致输出的文件在某些电脑正常显示&#xff0c;在某些电脑乱码显示。这个固然是由于各个电脑系统配置有差异&#xff0c;但是我们可以在应用该函数时就排除该差异来保证任意台电脑正常显示输出的文件。 如下…...

接口和抽象类

接口(Interface)和抽象类(Abstract Class)是支持抽象类定义的两种机制。 1.抽象类 (1)说明 在Java中被abstract关键字修饰的类称为抽象类&#xff0c;被abstract关键字修饰的方法称为抽象方法&#xff0c;抽象方法只有方法的声明&#xff0c;没有方法体。抽象类是用来捕捉子…...

ES7新特性

1. ES7 新特性 1.1. Array.prototype.includes includes 方法用来检测是否包含某个数组&#xff0c;返回布尔类型值 其他检测包含字符串的方法&#xff1a; indexOf&#xff08;&#xff09;&#xff0c;返回的是下标值&#xff0c;如果没有则返回-1 1.2 指数操作符 指数…...

【软件测试】资深测试总结的几个自动化测试点,提升跨越一大步......

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

GEE:时间序列分析1——认识arraySlice()

本文是记录时间序列分析系列教程的开篇之作,教程由浅入深介绍在GEE平台上进行时间序列分析的方法和代码。本教程会从操作时间序列的基本函数开始讲解,到后续更新会加入一些成熟的时间序列分析方法。随着本教程文章数量增加到一定数量,本专栏会适当涨价。欢迎乐多们订阅。 文…...

【react实战小项目:笔记】用React 16写了个订单页面

视频地址 React 16 实现订单列表及评价功能 简介&#xff1a;React 以其组件化的思想在前端领域大放异彩&#xff0c;但其革命化的前端开发理念对很多 React 初学者来说&#xff0c; 却很难真正理解和应用到真实项目中。本课程面向掌握了 React 基础知识但缺乏实战经验的开发…...

30岁+的人如何进行自我提升和职场规划

今天非常忙一天开了N个会&#xff0c;一堆头疼的事情要解决&#xff0c;一晃就加班到现在&#xff0c;刚打到了的士开始想今天分享点什么。 实在不知道写什么了&#xff0c;突然想起下午部门茶话会小伙伴问的问题&#xff1a;“30岁的人如何进行自我提升和职场规划”。 这是个…...

创建基于Vue2.0开发项目的两种方式

前天开始接触基于Vue2.0的前端项目&#xff0c;实际操作中肯定会遇到一些问题&#xff0c;慢慢摸索和总结。   其实&#xff0c;作为开发一般企事业单位应用的小项目&#xff0c;前端的懂一点HTMLCSSJavaScroptJQueryJson&#xff08;或者Xml&#xff09;&#xff0c;后端懂一…...

[测试]性能测试

最近遇到一个性能测试的问题&#xff0c;虽然最后确定是一个乌龙问题。这里还是总结一下&#xff0c;看是否有可以从中学到什么。 场景&#xff1a; 月底要出一个新版本。测试人员发现这个新版本在相同的负载的情况下&#xff0c;会有队列使用负荷过高的警告。之前的版本没有。…...

剑指 Offer 21. 调整数组顺序使奇数位于偶数前面

摘要 剑指 Offer 21. 调整数组顺序使奇数位于偶数前面 一、双指针解析 考虑定义双指针 i , j分列数组左右两端&#xff0c;循环执行&#xff1a; 指针 i从左向右寻找偶数&#xff1b;指针 j从右向左寻找奇数&#xff1b;将偶数nums[i]和奇数 nums[j]交换。 可始终保证&…...

实用版ChatBing论文阅读助手教程+新测评

实用版ChatBing论文阅读助手新测评 AI进化&#xff08;更新&#xff09;的速度太快了&#xff01;距离我上次的【Chat嘴硬&#xff01;基于NewBing的论文调研评测报告】&#xff0c;才四天&#xff0c;它已经进化到快能用的地步了&#xff01; 这次是我刷B站看到热门推荐&…...

Linux生产者消费模型

1.生产者消费者模型 1.1 为何要使用生产者消费者模型 生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯&#xff0c;而通过阻塞队列来进行通讯&#xff0c;所以生产者生产完数据之后不用等待消费者处理&#xff0c;直接…...

动态网站开发讲课笔记01:网页开发基础

文章目录零、本讲学习目标一、HTML基础&#xff08;一&#xff09;HTML简介1、HTML2、HTML语言的基本格式3、<!DOCTYPE>声明4、html标签5、head标签6、body标签7、编写第一个网页8、关于编写HTML文件的工具9、HTML标签概述&#xff08;1&#xff09;单标签&#xff08;2&…...

互联网新时代要到来了(三)什么是ChatGPT?

什么是ChatGPT? tips&#xff1a;资料来自百度百科、openAi、CSDN博主「琦在江湖飘」、Info写作社区、CSDN博主「夕小瑶」等网页资料。 1.什么是ChatGPT&#xff1f; ChatGPT&#xff08;全名&#xff1a;Chat Generative Pre-trained Transformer&#xff09;&#xff0c;…...

华为OD机试 - 环中最长子串(Python)

环中最长子串 题目 给你一个字符串s,首尾相连成一个环形, 请你在环中找出o字符出现了偶数次最长子字符串的长度. 备注: 1 <= s.lenth <= 5x10^5 s只包含小写英文字母 输入 输入是一个小写字母组成的字符串 输出描述 输出是一个整数 示例一 输入 alolobo输出 6说…...

安全—08day

ApabilitiesapabilitiesLinux Capabilities线程的 capabilitiesPermitted 允许Effective 有效InheritableBoundingAmbient文件的 capabilitiesPermittedInheritableEffective运行 execve() 后 capabilities 的变化案例分析方法一、依次执行如下命令方法二、iptables端口转发方案…...

【看表情包学Linux】进程地址空间 | 区域和页表 | 虚拟地址空间 | 初识写时拷贝

&#x1f923; 爆笑教程 &#x1f449; 《看表情包学Linux》&#x1f448; 猛戳订阅 &#x1f525; &#x1f4ad; 写在前面&#xff1a;本章核心主题为 "进程地址空间"&#xff0c;会通过验证 Linux 进程的地址空间来开头&#xff0c;抛出 "同一个值能有不同内…...

响应式编程(Reactive Programming)介绍

什么是响应式编程? 在互联网上有着一大堆糟糕的解释与定义。Wikipedia 一如既往的空泛与理论化。Stackoverflow 的权威答案明显不适合初学者。Reactive Manifesto 看起来是你展示给你公司的项目经理或者老板们看的东西。微软的 Rx terminology"Rx Observables LINQ S…...

循环冗余码校验CRC码 算法步骤+详细实例计算

通信过程&#xff1a;&#xff08;白话解释&#xff09; 我们将原始待发送的消息称为 M M M&#xff0c;依据发送接收消息双方约定的生成多项式 G ( x ) G(x) G(x)&#xff08;意思就是 G &#xff08; x ) G&#xff08;x) G&#xff08;x) 是已知的&#xff09;&#xff0…...

mongodb源码分析session执行handleRequest命令find过程

mongo/transport/service_state_machine.cpp已经分析startSession创建ASIOSession过程&#xff0c;并且验证connection是否超过限制ASIOSession和connection是循环接受客户端命令&#xff0c;把数据流转换成Message&#xff0c;状态转变流程是&#xff1a;State::Created 》 St…...

【服务器压力测试】本地PC电脑作为服务器运行时出现卡顿和资源紧张(Windows/Linux)

要让本地PC电脑作为服务器运行时出现卡顿和资源紧张的情况&#xff0c;可以通过以下几种方式模拟或触发&#xff1a; 1. 增加CPU负载 运行大量计算密集型任务&#xff0c;例如&#xff1a; 使用多线程循环执行复杂计算&#xff08;如数学运算、加密解密等&#xff09;。运行图…...

什么?连接服务器也能可视化显示界面?:基于X11 Forwarding + CentOS + MobaXterm实战指南

文章目录 什么是X11?环境准备实战步骤1️⃣ 服务器端配置(CentOS)2️⃣ 客户端配置(MobaXterm)3️⃣ 验证X11 Forwarding4️⃣ 运行自定义GUI程序(Python示例)5️⃣ 成功效果![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/55aefaea8a9f477e86d065227851fe3d.pn…...

【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的“no matching...“系列算法协商失败问题

【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的"no matching..."系列算法协商失败问题 摘要&#xff1a; 近期&#xff0c;在使用较新版本的OpenSSH客户端连接老旧SSH服务器时&#xff0c;会遇到 "no matching key exchange method found"​, "n…...

站群服务器的应用场景都有哪些?

站群服务器主要是为了多个网站的托管和管理所设计的&#xff0c;可以通过集中管理和高效资源的分配&#xff0c;来支持多个独立的网站同时运行&#xff0c;让每一个网站都可以分配到独立的IP地址&#xff0c;避免出现IP关联的风险&#xff0c;用户还可以通过控制面板进行管理功…...

深入理解Optional:处理空指针异常

1. 使用Optional处理可能为空的集合 在Java开发中&#xff0c;集合判空是一个常见但容易出错的场景。传统方式虽然可行&#xff0c;但存在一些潜在问题&#xff1a; // 传统判空方式 if (!CollectionUtils.isEmpty(userInfoList)) {for (UserInfo userInfo : userInfoList) {…...

前端中slice和splic的区别

1. slice slice 用于从数组中提取一部分元素&#xff0c;返回一个新的数组。 特点&#xff1a; 不修改原数组&#xff1a;slice 不会改变原数组&#xff0c;而是返回一个新的数组。提取数组的部分&#xff1a;slice 会根据指定的开始索引和结束索引提取数组的一部分。不包含…...

comfyui 工作流中 图生视频 如何增加视频的长度到5秒

comfyUI 工作流怎么可以生成更长的视频。除了硬件显存要求之外还有别的方法吗&#xff1f; 在ComfyUI中实现图生视频并延长到5秒&#xff0c;需要结合多个扩展和技巧。以下是完整解决方案&#xff1a; 核心工作流配置&#xff08;24fps下5秒120帧&#xff09; #mermaid-svg-yP…...

Python实现简单音频数据压缩与解压算法

Python实现简单音频数据压缩与解压算法 引言 在音频数据处理中&#xff0c;压缩算法是降低存储成本和传输效率的关键技术。Python作为一门灵活且功能强大的编程语言&#xff0c;提供了丰富的库和工具来实现音频数据的压缩与解压。本文将通过一个简单的音频数据压缩与解压算法…...