目录
数据库基础
代码建库
数据完整性
代码建表
数据库基础 系统数据库:master、model、tempdb、madb数据库文件的组成:【数据文件可以放在不同的文件组里】 主数据文件:*.mdf 主数据文件只能有一个次要数据文件:*.ndf日志文件:*.ldf 日志文件不属于任何文件组 数据库的拷贝
数据库分离和附加:右击——>任务——>分离【分离后数据库是与服务器分离,SQL界面的数据库就会消失,此时就可以剪切文件到U盘】 下次要用:又剪切到原文件夹,然后右击数据库——>附加——>选主数据文件所在文件夹。单机确定就会显示出来。
数据库的脱机与联机
右击此数据库——>任务——>脱机。脱机后数据库还显示在SQL界面。
【脱机后可完成数据库文件的剪切和复制】
下次要用:重新剪切到原文件夹——>右击该脱机数据库——>任务——>联机。即可
【不建议收缩数据库】
代码建库 create database XK--数据库名称 on primary--主文件组 ( name=XK_data,--主数据文件 filename='D:\project\XK_data.mdf',--主数据文件路径 size=10mb,--文件初始大小 maxsize=500mb,--文件增长最大容量限制 filegrowth=10mb--文件的增量 ), ( name=XK_data1,--辅数据文件 filename='D:\project\XK_data1.ndf',--辅数据文件路径 size=5mb, maxsize=100mb, filegrowth=1mb ), filegroup client--新建的文件组 ( name=XK_data2, filename='C:\project\XK_data2.ndf', size=4mb, maxsize=10mb, filegrowth=1mb ) log on--日志文件 ( name=XK_log, filename='D:\project\XK_log.ldf', size=5mb, maxsize=unlimited, filegrowth=1mb ), ( name=XK_log1, filename='D:\project\XK_log1.ldf', size=5mb, maxsize=unlimited, filegrowth=10% ) go exec sp_detach_db 'XK'--分离XK数据库 exec sp_attach_db 'XK' , 'D:\project\XK_data.
vivado版本是2019.1
modelsim版本是10.7
下为modelsim链接
链接:https://pan.baidu.com/s/1IzQIb7578P9aEfU7Xux5IA?pwd=1117
提取码:1117
默认已经安装好vivado和modelsim
目录
一、生成库文件
二、modelsim加载库文件
三、每个新工程的关联modelsim的步骤
一、生成库文件 1、找一个比较方便找的位置建立一个文件夹(后面仿真时需要用到),可以随意命名(建议和我的一样)。我是在modelsim安装文件夹下创建的。
2、随便打开一个创建好的工程
3、在弹出的窗口中按照标红的地方选择,②是刚才1步骤创建文件夹的位置,③是modelsim安装位置下的win64。
设置好后点击compile。
4、等待大约25分钟,看电脑性能。
二、modelsim加载库文件 5、完成后找到刚才的文件夹,打开modelsim.ini
从47行这个单词开始到vcom这一行上面结束,全部复制,
打开modelsim目录下的modelsim.ini,右击属性把只读勾选去掉,再打开
把刚才复制的内容粘贴到红线下面,保存
6、打开modelsim就可以看到库已经被识别了
三、每个新工程的关联modelsim的步骤 7、再在刚才vivado打开的工程里按照下图操作
按照下图选择,库地址还是刚才自己设置的那个地址,点击ok
8、这样vivado就可以联合modelsim仿真了
PS:每个新的工程需要重复7、8步骤,每个工程设置一次即可。
一、搭建hostapd 安装hostapd
sudo apt install hostapd配置hostapd
新建/etc/hostapd/hostapd.conf配置 AP信息 interface=p2p0 #wifi的网卡名称 driver=nl80211 ssid=TP-LINK_TEST #热点名称 channel=10 hw_mode=g wpa=3 wpa_passphrase=12345678 #无线密码 wpa_key_mgmt=WPA-PSK wpa_pairwise=TKIP ignore_broadcast_ssid=0 #0:显示热点名称 1:隐藏热点名称 修改/etc/init.d/hostapd或者/etc/default/hostapd中的DAEMON_CONF,修改内容如下:
DAEMON_CONF="/etc/hostapd/hostapd.conf" 启动hostapd
默认情况下上面配置完后后,重启设备将可以通过手机搜索到名为"TP-LINK_TEST"的wifi热点。如果hostapd服务为启动的情况下可里面下面的命令操作hostapd的服务。 sudo systemctl start hostapd.service #启动hostapd服务,仅本次有效。 sudo systemctl restart hostapd.service #重启hostapd服务,仅本次有效。 sudo systemctl stop hostapd.service #停止hostapd服务,仅本次有效。 sudo systemctl status hostapd.service #查看hostapd状态、log sudo systemctl enable hostapd.service #使能hostapd服务,下次重启后自动start sudo systemctl disable hostapd.service #失能hostapd服务,下次重启后不在启动hostapd 问题排查
如果无法搜素到热点可以利用下面命令手动启动hostapd。命令hostapd /etc/hostapd/hostapd.conf。如果有提示如下错误,请检查配置的wifi网卡释放已经被占用,下面就是wlan0已经被wpa_supplicant占用而报的错误信息。 // 一般都会出现以下错误 Configuration file: hostapd.conf nl80211: Could not configure driver mode nl80211 driver initialization failed.
1、一般情况下重新clean项目即可,不行在清除下缓存,或者关掉as重新打开;
2、查看res下面的资源文件编写是否有问题;
3、查看清单文件中,是否缺少package信息,或者package包名错误;(一般确定上面两点没问题的,大概率是此原因)
idea添加左右箭头
在eclipse中左右箭头本来就有的,但是在idea中需要配置,大佬可以通过快捷键Ctrl + Alt + 左箭头和Ctrl + Alt + 右箭头来后退前进,配置步骤如下:
第3步旁边有个上下三角形可以调整箭头位置,最终效果图:
(重要:方向反了可以调整位置)
1. 在spring-web中配置视图解析器
<!--3:配置JSP 显示ViewResolver--> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/> <property name="prefix" value="/WEB-INF/view/"/> <property name="suffix" value=".jsp"/> </bean> 2. 在WEB-INF下创建view目录
3. controller代码
@Controller @RequestMapping("path") public class PathCheckController { @RequestMapping("/test") public String pathTest(ModelMap model){ model.addAttribute("message","I am zhangxing"); return "index"; } } 这里不能用@RestController注解,否则controller中的方法无法返回jsp页面,配置的视图解析器InternalResourceViewResolver也将不起作用,所以 只能用@Controller
4. index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <html> <head> <title>登录</title> </head> <body> <h1>${message}</h1> </body> </html> RestController和Controller的区别
RestController的作用相当于Controller加ResponseBody共同作用的结果,但采用RestController请求方式一般会采用Restful风格的形式。
Controller的作用:声明该类是Controller层的Bean,将该类声明进入Spring容器中进行管理
ResponseBody的作用:表明该类的所有方法的返回值都直接进行提交而不经过视图解析器,且返回值的数据自动封装为json的数据格式
RestController的作用:包含上面两个的作用,且支持Restful风格的数据提交方式
Restful风格:
get:获取数据时用的请求方式
post:增加数据时的请求方式
put:更新数据时的请求方式
delete:删除数据时的请求方式
这是一段在 PyTorch 中实现 ResNet(残差网络)并使用 CIFAR-10 数据集进行训练和测试的代码。ResNet 是一种深度学习模型,由于其独特的“跳跃连接”设计,可以有效地解决深度神经网络中的梯度消失问题。CIFAR-10 是一个常用的图像分类数据集,包含10个类别的60000张32x32彩色图像。
下面我们会分步解析这段代码。
首先,我们看到导入了必要的 PyTorch 库和模块,包括神经网络(nn)、优化器(optim)、学习率调度器(lr_scheduler)、数据集(datasets)、数据转换(transforms)、数据加载器(DataLoader)等。
import torch import torch.nn as nn import torch.optim as optim from torch.optim.lr_scheduler import StepLR, ReduceLROnPlateau from torchvision import datasets, transforms from torch.utils.data import DataLoader import os import torch.backends.cudnn as cudnn import torch.nn.functional as F 之后定义了一个名为`progress_bar`的函数,这个函数用于在控制台上显示训练或测试的进度。
def progress_bar(current, total, msg=None): progress = current / total bar_length = 20 # Length of progress bar to display filled_length = int(round(bar_length * progress)) bar = '=' * filled_length + '-' * (bar_length - filled_length) if msg: print(f'\r[{bar}] {progress * 100:.
文章目录 SQL 简介SQL 语法SQL SELECT 语句SQL SELECT DISTINCT 语句SQL WHERE 子句SQL AND & OR 运算符SQL ORDER BY 关键字SQL INSERT INTO 语句SQL UPDATE 语句SQL DELETE 语句 SQL 简介 SQL(Structured Query Language)是一种用于管理和操作关系型数据库的标准化语言。它允许用户通过简单的语句来定义、操作和查询数据库中的数据。
SQL 语法 SQL 语法由一系列的关键字、函数、运算符和表达式组成。下面是一些常用的 SQL 语法元素:
关键字:SQL 语句中的保留字,用于表示特定的操作或条件。例如:SELECT、INSERT、UPDATE、DELETE 等。
表达式:由常量、列名、函数和运算符组成的语句片段,用于计算结果或生成新的数据。
运算符:用于执行比较、逻辑和算术操作。例如:等于(=)、大于(>)、小于(<)、逻辑与(AND)、逻辑或(OR)等。
函数:用于执行特定的操作,例如计算、格式化或处理数据。例如:SUM、COUNT、MAX、MIN 等。
SQL SELECT 语句 SELECT 语句用于从数据库中检索数据。它的基本语法如下:
SELECT 列名1, 列名2, ... FROM 表名; 这将返回指定表中的所有行,并且只包含指定的列。
SQL SELECT DISTINCT 语句 SELECT DISTINCT 列名 FROM 表名; 这将返回指定列中的所有不重复的值。
SQL WHERE 子句 WHERE 子句用于筛选符合指定条件的行。它的语法如下:
前言 这个系列的文章有四篇,其目的是为了搞清楚:
进程,shell,shell进程,终端,控制终端,前台进程,后台进程,控制进程,前台进程组,后台进程组,会话,守护进程,init进程,用户进程,系统进程 它们之间的联系与区别系列文章第一篇传送门:全面理解shell进程、终端、控制终端的概念,以及它们之间有什么区别与联系?(系列文章第一篇)系列文章第二篇传送门:全面理解进程组,会话的基础概念,以及进程组,会话,控制终端,前台进程组与后台进程组之间的联系(系列文章第二篇)系列文章第四篇传送门:全面理解前台进程,后台进程的概念,以及之间如何切换,init进程与系列文章大总结(系列文章第四篇) 什么是守护进程 守护进程(daemon)是在 Unix 和类 Unix(如 Linux)操作系统中运行的一种特殊的后台进程,它们独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。
守护进程通常在系统引导装载时启动,并且在系统关闭之前一直运行。
守护进程的名称通常以 "d" 结尾,以便于区分。例如,sshd 是 Secure Shell 守护进程,httpd 是 HTTP 守护进程。
这些进程在后台运行,提供各种服务,例如处理网络请求(如 web 服务器)、处理系统日志、处理电子邮件和其他各种任务。
守护进程通常不直接与用户交互,但它们工作起来非常重要,为其他程序和用户提供关键服务。
守护进程在创建时通常会进行“孤儿化”操作,使其成为 init 进程(进程ID为1)的子进程。这样,它们就可以在后台运行,不受任何特定用户或会话的影响。此外,守护进程通常会更改其工作目录到根目录 (“/”),关闭所有已打开的文件描述符(包括输入、输出和错误输出),并重新打开标准输入、标准输出和标准错误到/dev/null,这样就可以防止它们不小心读取或写入任何用户文件或终端。
总的来说,守护进程是一种在后台运行,为系统或其他程序提供服务的进程。
如何创建一个守护进程 创建守护进程需要一定的步骤,这些步骤确保了守护进程能够在后台独立运行,并且不受任何特定用户或会话的影响。
下面我来解释一下每个步骤的含义:
执行一个 fork(),之后父进程退出,子进程继续执行。
这是创建守护进程的第一步,目的是让守护进程在后台运行。在fork()之后,父进程退出,子进程成为孤儿进程,并被 init 进程(进程ID为1)接管。 子进程调用 setsid() 开启一个新会话。
setsid()函数会创建一个新的会话,并且让子进程成为这个新会话的首进程。这样子进程就与其原来的会话、进程组和控制终端脱离,成为一个新的会话的首进程,可以独立运行。 清除进程的 umask 以确保当守护进程创建文件和目录时拥有所需的权限。
umask是一个权限掩码,它决定了新建文件或目录的默认权限。清除umask是为了让守护进程能够有更大的权限控制它创建的文件或目录。 修改进程的当前工作目录,通常会改为根目录(/)。
这是为了防止守护进程阻止文件系统被卸载。如果守护进程的工作目录在一个挂载的文件系统中,那么这个文件系统就不能被卸载。 关闭守护进程从其父进程继承而来的所有打开着的文件描述符。
这是为了避免守护进程继续使用这些文件描述符,可能会导致不可预料的问题。 在关闭了文件描述符0、1、2之后,守护进程通常会打开/dev/null 并使用dup2() 使所有这些描述符指向这个设备。
这是为了让守护进程的标准输入、输出和错误输出被重定向到/dev/null,避免它们产生任何输出,因为守护进程通常不需要和用户交互。 核心业务逻辑 在完成了以上所有的步骤后,守护进程开始执行其核心的业务逻辑,为系统或其他程序提供服务。
示例:创建一个守护进程 下面是一个简单的守护进程的例子,这个程序会每2秒获取一次系统时间,并写入一个叫做time.txt的文件中
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <stdio.h> #include <time.
目录
一、程序的组织结构
1、前言
二、顺序结构
1、介绍
三、对象的布尔值
1、介绍
2、规定
四、分支结构
1、单分支if结构
1、语法语义
2、语法结构
3、案例
2、双分支if...else结构
1、语法语义
2、语法结构
3、案例 3、多分支if...elif...else结构
1、语法语义
2、语法结构
3、案例
4、if语句嵌套
1、语法语义
2、语法结构
3、案例
5、条件表达式
1、语法语义
2、语法结构
3、案例
五、pass语句
1、含义
2、pass什么时候用
3、pass运用场景
4、pass作用 5、案例
一、程序的组织结构 1、前言 1997年,计算机科学家证明了这样的事实:任何简单或复杂的算法都可以由顺序结构、选择结构(i结构)、和循环结构(while语句,for...in语句)这三种基本结构组合
二、顺序结构 1、介绍 程序自上而下顺序的执行代码,中间没有任何的判断和跳转,直到程序结束
三、对象的布尔值 1、介绍 Python一切事物皆对象,所有对象都有一个布尔值,获得对象的布尔值,使用内置函数bool()
2、规定 以下对象的布尔值皆为False
FALSE数值()None空字符串空列表空元组空字典空集合 print(bool(False)) print(bool(None)) print(bool(19())) print(bool("")) print(bool('')) print(bool([]))#空列表 print(bool(list()))#空列表 print(bool(()))#空元组 print(bool(tuple)) print(bool({}))#空字典 print(bool(dict())) print(bool(set()))#空集合 四、分支结构 1、单分支if结构 1、语法语义 如果条件表达式成立(True),则将执行条件执行体
2、语法结构 if 条件表达式:
条件执行体
3、案例 场景:银行取钱:
1、ROS集成开发环境搭建 1.1 安装终端 1. 安装终端 sudo apt install terminator 2. 终端常用快捷键 同标签内操作:
Alt+Up //移动到上面的终端 Alt+Down //移动到下面的终端 Alt+Left //移动到左边的终端 Alt+Right //移动到右边的终端 Ctrl+Shift+O //水平分割终端 Ctrl+Shift+E //垂直分割终端 Ctrl+Shift+Right //在垂直分割的终端中将分割条向右移动 Ctrl+Shift+Left //在垂直分割的终端中将分割条向左移动 Ctrl+Shift+Up //在水平分割的终端中将分割条向上移动 Ctrl+Shift+Down //在水平分割的终端中将分割条向下移动 Ctrl+Shift+S //隐藏/显示滚动条 Ctrl+Shift+F //搜索 Ctrl+Shift+C //复制选中的内容到剪贴板 Ctrl+Shift+V //粘贴剪贴板的内容到此处 Ctrl+Shift+W //关闭当前终端 Ctrl+Shift+Q //退出当前窗口,当前窗口的所有终端都将被关闭 Ctrl+Shift+X //最大化显示当前终端 Ctrl+Shift+Z //最大化显示当前终端并使字体放大 Ctrl+Shift+N or Ctrl+Tab //移动到下一个终端 Ctrl+Shift+P or Ctrl+Shift+Tab //Crtl+Shift+Tab 移动到之前的一个终端 各个标签之间的操作
F11 //全屏开关 Ctrl+Shift+T //打开一个新的标签 Ctrl+PageDown //移动到下一个标签 Ctrl+PageUp //移动到上一个标签 Ctrl+Shift+PageDown //将当前标签与其后一个标签交换位置 Ctrl+Shift+PageUp //将当前标签与其前一个标签交换位置 Ctrl+Plus (+) //增大字体 Ctrl+Minus (-) //减小字体 Ctrl+Zero (0) //恢复字体到原始大小 Ctrl+Shift+R //重置终端状态 Ctrl+Shift+G //重置终端状态并clear屏幕 Super+g //绑定所有的终端,以便向一个输入能够输入到所有的终端 Super+Shift+G //解除绑定 Super+t //绑定当前标签的所有终端,向一个终端输入的内容会自动输入到其他终端 Super+Shift+T //解除绑定 Ctrl+Shift+I //打开一个窗口,新窗口与原来的窗口使用同一个进程 Super+i //打开一个新窗口,新窗口与原来的窗口使用不同的进程 1.
在JavaScript中,有多种方式可以向数组中追加数据,包括:
push()方法:将一个或多个元素添加到数组的末尾,并返回新数组的长度。 javascript复制代码 var arr = [1, 2, 3]; arr.push(4); console.log(arr); // [1, 2, 3, 4] unshift()方法:将一个或多个元素添加到数组的开头,并返回新数组的长度。 javascript复制代码 var arr = [2, 3, 4]; arr.unshift(1); console.log(arr); // [1, 2, 3, 4] splice()方法:可以在数组的任意位置添加或删除元素。 javascript复制代码 var arr = [1, 2, 4]; arr.splice(2, 0, 3); console.log(arr); // [1, 2, 3, 4] 其中,splice()方法的第一个参数表示要添加或删除元素的起始位置,第二个参数表示要删除的元素个数,第三个参数及以后的参数表示要添加到数组中的元素。
concat()方法:将两个或多个数组合并成一个新数组。 javascript复制代码 var arr1 = [1, 2]; var arr2 = [3, 4]; var arr3 = arr1.concat(arr2); console.log(arr3); // [1, 2, 3, 4]
最近在使用兆易创新的GD32E230C8T6的FMC时(即Flash读写),出现一个非常奇怪的异常现像。本身这个MCU的Flash还算比较大,64K的空间,而程序代码空间使用后还有剩余,就想把配置信息保存到Flash中,这样可以省掉一个外部存储的费用,毕竟大部分的配置其实并不多,可能就只有个几十字节,如果额外添置一个外置Flash,真的是有些没必要。
但在使用中出现一些奇怪的问题,就是项目中使用到了ADC,在使用Flash写入后大概率会出现ADC采样混乱,就是采样不到正确的数值。就非常奇怪,又无耐,总不能非要添加一个外置吧!
现像说完了,下面说下我所使用的软硬件情况:
系统 Win11 软件 Keil5.36 MCU GD32E230C8T6 (淘宝老五家的二手拆机) 大家看到了,我使用的是二手的MCU,以下是实物图片:
当时买当然为图便宜,因为当时淘了几十块,所以暂时是够用了,也就没有买新的必要,所以我以上所描述的问题,不知道新的芯片是不是存在。
本文中会涉及到以下几个内容:
1、使用DMA持续采样ADC 2、内置Flash读取及写入 哪么,我们就先来实现以上功能,我们将使用DMA来持续采样ADC,这样速度很快,而且不占用MCU时间,非常的省时省力。我这里需要采样三路ADC信息,分别是输入电压、输出电压和温度信息,使用的引脚对应信息如下:
PA1 --- 输入电压 --- 通道 ADC_CH1 PA2 --- 输出电压 --- 通道 ADC_CH2 PA3 --- 温度信息 --- 通道 ADC_CH3 为了保障采样精度、降低波动,我做了16倍采样后取平均值,所以我们申明了两个参数,一个是用于DMA采样后的数据存储,一个是存储取平均值后的值:
// ADC原始数据缓存区 4通道16倍采样 __IO uint16_t ADCRawValue[16][3]; // ADC实际值 __IO uint16_t ADCValue[3]; ADC 初始化完整代码如下: // ADC初始化例程,使用DMA转换 #include "bsp_adc.h" // ADC原始数据缓存区 4通道16倍采样 __IO uint16_t ADCRawValue[16][3]; // ADC实际值 __IO uint16_t ADCValue[3]; // DMA进行ADC转换配置 4通道采样 void adc_config(void) { // 配置GPIO rcu_periph_clock_enable(RCU_GPIOA); gpio_mode_set(GPIOA, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, VBAT_PIN | VBUS_PIN | TEMP_PIN ); // 配置DMA中断 nvic_irq_enable(DMA_Channel0_IRQn, 1); // 配置DMA rcu_periph_clock_enable(RCU_DMA); dma_deinit(DMA_CH0); dma_parameter_struct dma_data_parameter; dma_data_parameter.
No matching function for call 表示当我们调用某些函数时,该函数的参数与函数定义的参数不匹配。因此,我们得到一个错误“No matching function for call”
所以当我们继续向函数传递不正确类型的指定方法或不合适的参数集时。函数定义向编译器指定方法的名称以及如何调用它。
1.效果图 2.完整代码: 码云链接:susu-mini-tem: 微信小程序封装组件,覆盖常用需求:包括轮播组件(堆叠轮播等)、canvas(圆环、海报、裁图,绘画等)、map、echarts各类图表、css3动画,步骤条、日历组件、自定义tabbar+导航栏、loading加载动画、css渐变、雪碧图、瀑布流、关键词高亮、搜索历史、图片懒加载、节流防抖、索引选择(如城市)、微信拆红包、红包雨、菜单弹出动画、3d云、按钮拖拽、营销组件(九宫格等)
3.部分代码 <!-- 物流区 --> <view class="con1 flex-row"> <view> <image src="/img/order_icon5.png" class="order_icon"></image> </view> <view class="w_text"> <view class="item">订单编号:{{expresslist.order_no}}</view> <view class="item">物流公司:{{expresslist.de_company}}</view> <view class="item">物流单号:{{expresslist.wuliu_no}}</view> </view> </view> <view class="con4" wx:if="{{expresslist.list.length>0}}"> <view class="con4_top1">物流信息</view> <!-- 物流列表 --> <view class="con4_top3 flex" wx:for="{{expresslist.list}}"> <view class="con4_text"wx:key="key" wx:for-item='item'> <text class="con4_time {{index==0?'':'no'}}">{{item.status}} {{item.time}}</text> <view class="line_ellipsis text ">{{item.text}}</view> <view class="con4_icon" wx:if="{{index==0}}"> <image wx:if="{{item.display==0}}" src="/img/wuliu_icon2.png" class="chek_img"></image> <image wx:if="{{item.display==1}}" src="/img/wuliu_icon3.png" class="chek_img"></image> <image wx:if="{{item.display==2}}" src="/img/wuliu_icon4.png" class="chek_img"></image> <image wx:if="{{item.display==3}}" src="/img/wuliu_icon5.png" class="chek_img"></image> </view> <!
以ASP.NET Core WebAPI 作后端 API ,用 Vue 构建前端页面,用 Axios 从前端访问后端 API ,包括文件的上传和下载。
准备文件上传的API
#region 文件上传 可以带参数
[HttpPost("upload")]
public JsonResult uploadProject(IFormFile file, string userId)
{
if (file != null)
{
var fileDir = "D:\\aaa";
if (!Directory.Exists(fileDir))
{
Directory.CreateDirectory(fileDir);
}
//文件名称
string projectFileName = file.FileName;
//上传的文件的路径
string filePath = fileDir + $@"\{projectFileName}";
using (FileStream fs = System.IO.File.Create(filePath))
{
file.CopyTo(fs);
fs.Flush();
}
return Json("ok");
}else{
return Json("no");
}
}
#endregion
前端vue上传组件 ( 利用Form表单上传 )
1.实现效果 2.input标签的属性 multiple:是否多选 <input type="file" multiple="multiple" accept="image/*" /> accept:文件格式,这个可以控制文件格式,比如jpeg和gif还有Png之类的 <input type="file" multiple="multiple" accept="image/png,image/gif,image/jpeg" /> capture表示,可以捕获到系统默认的设备,比如:camera照相机;camcorder摄像机;microphone录音 <input type="file" multiple="multiple" accept="image/png,image/gif,image/jpeg" capture="camera" /> 3.实现代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>上传文件</title> </head> <style> body,*{ -webkit-tap-highlight-color:transparent; } .add_box{ width: 100px; height: 100px; border: 1px solid #ccc; border-radius: 10px; position: relative; font-size: 8px; text-align: center; margin: 40px 20px; padding: 5px; } .flex{ display: flex; align-items: center; } .
一.什么是权限管理 在web应用中权限管理,一般指根据系统设置和分配给某个角色的应用权限,用户可以访问而且只能访问自己被分配的资源。权限管理几乎出现在任何系统里面,在web后台管理系统里面尤为常见。
二.权限管理的分类 后端权限 权限管理主要还是围绕着数据进行的,核心还是服务器中数据的变化,所以后端一般才是权限的关键,后端告诉前端改用户拥有什么权限,然后前端在进行分配给用户,因此在很长的一段时间内,权限一直都只是后端程序要考虑的话题。但是随看前后端分离开发模式的流行,越来越多的项目也在前端进权限控制。
前端权限 前端权限的控制从本质上来说,就是控制前端的页面的展示和前端所发送的请求。但是只有前端权限控制必要要依靠后端的支持才能进行。前端权限控制只可以说是达到锦上添花的效果,可以优化界面逻辑,简化项目复杂度,提升项目的运行效率,减轻服务器的压力等等,所以,权限控制在前端中也比较重要的知识点了。
三.前端权限的意义 降低用户非法操作的可能性
提高用户体验,无权限的菜单按钮不在展示
拦截不必要的请求,减轻服务器的压力
四.前端权限管理的实现思路 菜单控制:在登录请求成功之,会得到权限数据当然,这个需要和后端商量返回数据的格式,前端根据权限数据,展示对应的菜单点击菜单,才能查看相关的界面。
界面控制:界面控制有两种,第一种就是用户没有登录前,在地址栏中输入项目中非登录页的项目地址,这时应该将其访问拦截重定向到登录页。 第二种就是针对不同的用户,有些特定用户所拥有的特定页面就不应该呈现给用户,即使他非法敲入看到的地址也不行,输入非法地址,应该给他重定向到404页面。
按钮控制:不同的用户对按钮的操作权限不同,第一种用户只能查看数据,不能更改数据,有的用户则拥有对数据增删改查的功能,所以同一个按钮当用户没有权限的时候我们应该给他隐藏或者禁用。第二种则是,一个页面存在多个tabs标签页面,我们也应该根据权限的不同做不同的展示。
请求和响应的控制:
对于超出用户权限以外的请求和响应对系统来说都是不必要的,会造成不必要的服务器开销和时间成本,这种请求和响应都是需要控制的,让其根本无法发送,比如一个编辑按钮,由于没有权限,在页面上是把当前按钮禁用了的,但是如果用户打开控制台,强行将此按钮的disabled属性置为true,这个时候没有权限的用户还是可以操作这个按钮,虽然可能后台最终会做拦截但是对用户来说体验不是很好,所以我们对当前发出的非法请求还是需要做出拦截。
五.动态路由(菜单相关) 动态路由介绍:动态即不是写死的,是可变的。我们可以根据存在的权限加载对应的路由,没有权限的路由我们就不加载,避免造成资源浪费,也算对项目的一个优化点了,动态路由的使用一般结合角色权限控制一起使用。
动态路由的优点:
安全性,当用户手动输入没有权限的地址进入某一个页面的时候,会自动重定向到404,无需我们单独在路由守卫里面进行控制
灵活,可以配置菜单的增加、减少,这样不用每次修改再去处理。后续的菜单增加,路由统一处理,方便快捷。
六.功能实现 这里我将使用一个demo进行举例,demo里面会对菜单,界面,按钮,请求四个方面进行实现,demo技术栈使用vue2+vue-router+element-ui来实现,后台数据使用mock.js模拟实现。
方法1(不使用动态路由) 这里整体实现思路是给每个menu菜单,按钮都分配一个独有的code,然后后台返回对应角色用户的code权限表集合,前端拿到code权限表在做筛选处理
demo里面存在两个用户角色,一个是admin(超级管理员),拥有所有的权限,还有一个是test(普通管理员),拥有一部分的权限
用户登录之后服务端返回一个数据,这个数据有角色对应的权限code表(permission)和token
我们在登录成功之后拿到数据先存储在vuex中,之后我们在进行使用,但是我们会发现一个问题我们刷新一下页面之后vuex的数据会消失
原因:vuex里的数据是保存在运行内存中的,当页面刷新时,页面会重新加载vue实例,vuex里面的数据就会被重新赋值为初始值。
解决方式:将数据保存在localstorage,sessionstorage或cookie中(因为这些是存储在浏览器的,相当于存储在硬盘中,如果不主动清除,会一直存在),并且让它和vuex保持同步。
菜单控制:根据返回的code表去与router对象进行对比,每个router对象都有一个roleCode属性,代表当前的menu菜单 这里我们使用了一个方法来进行筛选,该方法传入当前code表与所有的router对象,最后会返回符合code权限的全新路由对象,左侧的menu初始化是整个router对象,也就是具有所有menu菜单,现在经过筛选之后,只返回了当前角色具有权限的menu菜单
登录成功按照上面的方法筛选得到以下菜单
这样之后菜单模块看起来好像就是大功告成了,但其实如果我们重新刷新的话会发现刚刚筛选好的menu菜单就会复原成所有菜单,筛选菜单方法失效了。
原因:路由筛选路由是在登录成功之后才会调用的,刷新的时候并没有调用,所以刷新之后路由没有添加上。
解决方式:可以在app.vue中的created中调用添加筛选菜单路由的方法
界面控制:由于没有使用动态路由的方式,在项目里面实际上所有的路由都是进行了注册的,所以哪怕在页面上没有显示对应的menu菜单,但是我们在地址栏上输入不存在该权限的地址我们还是可以进入到该页面,所以我们接下来应该对界面进行权限控制 admin用户
test用户
test用户是不存在角色管理整个模块的
所以我们这里要多加一个步骤,就是在路由前置守卫里面进行拦截,如果判断没有该权限应该重定向到404页面。
解决方式:在路由前置守卫里面给他进行旁段,如果有权限放行,无权限直接404
问题:我想着这样就可以了,但是问题又来了,我尝试访问test用户不存在的角色模块时候,在地址栏上输入地址,结果却陷入了死循环。
于是我查阅资料发现这个问题其实也是对 vue-router 的router.beforEach运行机制不了解导致
解决方式:基于整个机制,我们对它进行改造一下
友情链接:vue-router死循环这个问题可以参考这个博客https://blog.csdn.net/weixin_45306532/article/details/114434748
上述操作完成之后,菜单部分就真的真的真的完工大吉了,输入非法地址,进入404
按钮权限:现在用户可以看到某些具有权限的界面了,但是这个界面的一些按钮和tabs页该用户可能是没有权限的。 按钮处理:用户不具备权限的按钮就隐藏或者禁用,而在这块的实现中,可以把该逻辑放到自定义指令中 a:新建permission.js文件,并且注册自定义指令
b.main.js引入permission.js
c.按钮上面加上自定义指令,并且把当前按钮对应的code传入自定义指令中
2.tabs处理:在当前页面组件的computed里面进行tabs过滤
a.tabs的配置数据里面有一个role属性,对应当前权限
b.用户管理/用户等级组件的computed进行处理
上述操作完成之后,test用户的效果就是如下
test用户
admin用户 接口请求控制:接口权限目前一般采用jwt的形式来验证,没有通过的话一般返回401,跳转到登录页面重新进行登录,登录完拿到token,将token存起来,通过axios请求拦截器进行拦截,每次请求的时候头部携带token。 到这里按钮级别的控制就结束了 。
方法2(使用动态路由) 此方法在第一种方法的菜单权限基础上加入了动态路由
我的router里面只保留了首页/home,登录页/login,以及404页面,因为这几个页面什么用户登录都是有权限查看的。
这里写目录标题 1.拉取es镜像2.配置配置文件3.启动容器4.启动过程中遇到的问题5.查看容器启动情况 1.拉取es镜像 docker pull docker.elastic.co/elasticsearch/elasticsearch:7.17.0 版本根据自己需求进行拉取,我这边选择的是7.17.0,不同版本配置可能稍有差别!
2.配置配置文件 采用文件挂载的方式,采用宿主机配置文件,本文采用的三台主机搭建集群,每一台主机的配置稍有区别!
主机一:
# es1 # 主master配置样例子 # 集群的名称 cluster.name: "docker-cluster" # 节点的名称 node.name: node-1 # 此节点是否可以用作master节点 node.master: true # 此节点是否是存储节点 node.data: false # 此节点是否是预处理节点 如果是master节点的话 建议这里是true node.ingest: true # network.host: 0.0.0.0 # 配置端口 http.port: 9200 # 集群通信端口 transport.port: 9300 # 集群内节点信息 每个节点会共享自己的此参数 # 这里我配置自己的dockerIP discovery.seed_hosts: ["172.17.0.2:9300","172.17.0.3:9300","172.17.0.4:9300"] # 集群的master候选节点目录。只有在初始化的时候才生效。 # 这里只写node-1 并且配置这个参数 是用于快速搭建集群。集群已启动自动node-1 是master cluster.initial_master_nodes: ["node-1"] # cross 跨域访问 配置这个之后 head就可以用了 http.cors.enabled: true http.
近期,国外Anthropic公司发布了Claude聊天机器人,堪比ChatGPT的最大竞争对手。一经推出,市场上就经常拿它俩来对比,因为推出Claude产品的Anthropic 公司是由多位前OpenAI前员工组成,两家公司,以及他们推出的产品确实也是有较深的源缘。
据说,Claude的创始人原本也是OpenAI实验室的核心研发人员,后来因为发展理念和路线的冲突,自己出来单干,做出来了Claude。
今天给大家手把手介绍一下注册流程,并简单体验一下。如果遇到什么问题,或者对AIGC感兴趣的朋友,加我:keeepdance,备注:chatgpt,我拉你进群,获得最新最全攻略。
注册 1. 访问Claude官网地址:Anthropic | Claude, now in Slack
点击Add to Slack,就会跳转到Slack官网,我们将在那添加对话机器人。这种操作跟Midjourney和Discord的操作类似。
2. 注册Slack账号
如果没有注册登录Slack,我们需要进行注册。
使用谷歌账号、苹果账号可以直接绑定,也可以使用新的电子邮箱进行注册。据网友反馈,不能使用国内邮箱如QQ邮箱、163邮箱等。否则将收不到验证码。我们这里使用Google邮箱。
3. 在slack中创建一个工作区
完成注册操作之后,我们再次回到Cluade应用管理界面,提示添加一个工作区。
接下来按提示,一步一步执行
完成之后,就进入聊天界面了
4. 再次回到添加Cluade应用界面,添加到自己工作区
按提示,允许,下一步
5. 再回到slack聊天界面,就看到应用处多了Claude,点击该应用就可以体验了
体验 中文能力
代码能力
速度还不错,手机下载Slack APP,使用更方便,快去体验吧!
一、 Java Date 对象 Java Date 类表示日期和时间,可以存储从1970年1月1日零时(格林威治标准时间)起累计的毫秒数。Date 对象支持日期的计算和显示,并且可以与其他系统进行时间数据的交换。
创建 Date 对象 Java 中创建 Date 对象的方式有多种,例如:
(1) 使用空构造函数创建一个当前日期和时间的对象。
Date date = new Date(); (2) 使用指定毫秒数的构造函数创建一个指定时刻的对象。
Date date = new Date(1618977192000L); 常用方法 Date 类提供了以下常用方法:
(1) getTime():返回自 1970 年 1 月 1 日零时开始累计的毫秒数。
(2) setTime(long time):设置自 1970 年 1 月 1 日零时开始累计的毫秒数。
(3) equals(Object obj):比较当前日期和时间是否等于另一个日期和时间。
(4) compareTo(Date anotherDate):将当前日期和时间与另一个日期和时间比较,如果当前日期和时间更早则返回负数,如果相等则返回 0,如果当前日期和时间更晚则返回正数。
(5) before(Date when) 和 after(Date when):分别表示当前日期和时间是否在另一个日期和时间之前或之后。
问题 Date 类存在以下问题:
(1) 可变性问题:Date 对象是可变的,因此在多线程环境下,可能会发生并发问题。
(2) 易混淆问题:Date 类提供的 getYear() 方法返回的是从 1900 年开始的年份,而且月份从 0 开始计算。
可以考虑通过ansible实现k8s的安装,通过ansible的角色复用减轻后续工作!!
操作系统:Centos 7
第一步:kubelet不支持交换分区,需要将交换分区关掉
临时关闭:swapoff -a
永久关闭:vim fstab,将swap那行挂载去掉
第二步:关闭防火墙和SELinux,安全做法开防火墙上边的白名单端口。
但生产环境一般按照网络区域设置防火墙而不是主机层面,所以建议关掉
1、systemctl disable --now firewalld.service
2、vim /etc/selinux/config
将SELINUX禁用,可通过getenforce查看selinux状态
第三步:配置时间同步服务
建议通过chrony来配置成同一台时间同步服务器实现
第四步、配置yum源
1、http://mirrors.aliyun.com/repo/Centos-7.repo
2、http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
3、kukenetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/
enabled=1
gpgcheck=0
repo_gpgcheck=0
gpgkey=https://mirrors.aliyun.com/kubernetes/yum/doc/yum-key.gpg https://mirrors.aliyun.com/kubernetes/yum/doc/rpm-package-key.gpg
master节点安装docker、dockercri、kubeadm、kubectl、kubelet
node节点上安装docker、docker-cri、kubeadm、kubelet
docker-cri的安装会比较难,被墙了
wget https://github.com/Mirantis/cri-dockerd/releases/download/v0.2.6/cri-dockerd-0.2.6-3.el7.x86_64.rpm
yum -y install cri-dockerd-0.2.6-3.el7.x86_64.rpm
systemctl enabled --now docker.service kubelet.service
第五步:配置docker-ce的镜像仓库为国内的开源镜像仓库,docker官方镜像仓库网速较差,我们需要设置国内镜像服务
/etc/docker/daemon.json
{
“registry-mirrors”: [“https://p1xjy9ro.mirror.aliyuncs.com”]
}
注意:需要重启docker服务生效
sudo systemctl daemon-reload
sudo systemctl restart docker
第六步:配置主机名
hostnamectl set-hostname k8s-master01
并配置相应的映射关系/etc/hosts
第七步:master节点上初始化kubernetes集群
kubeadm --v=6 init --kubernetes-version=v1.25.0 --image-repository registry.
医疗器械行业标准 并列标准 电磁兼容YY0505-2012----最新生效2023.05.01 YY9706.102-2021
医疗器械安规三项是什么?GB9706.1 2007最新生效GB9706.1 2020
1、漏电流测试 IEC60950-1 2、电介质强度测试=耐压测试?GB9706.1 2020
3、保护接地电阻测试=保护接地 ?GB9706.1 2020
安规检测项目和EMC项目需要在整机出厂前测试吗?抽测还是全检?全部项目都做吗?可以不做吗?是否一定要定期抽检?还有测过安规(耐压)的能销售吗,有人的说全测而且可以销售,有的人... 安规检测项目和EMC项目需要在整机出厂前测试吗?抽测还是全检?全部项目都做吗?
可以不做吗?是否一定要定期抽检?
还有测过安规(耐压)的能销售吗,有人的说全测而且可以销售,有的人反对销售因为怕有潜在电气损伤,请问你们怎么做的?依据是什么?
1、不需要全检安和EMC。原因:
- 根据《总局关于发布医疗器械生产企业质量控制与成品放行指南的通告(2016年第173号)》规定:成品检验规程的内容原则上应当覆盖已注册或者备案的产品技术要求中需要常规控制的检验项目和检验方法。不能覆盖的,应当在成品检验规程中予以说明。必要时,应当给出经过确认的替代解决方案。
出于成本原因,一般企业没有全检安规和EMC的条件,且耐压测试之类都是破坏性测试,所以基本是抽检。
2、通常做法:
每批出厂抽检安规三项:漏电流测试、耐压测试、保护接地。
耐压是破坏性测试,但是安规测试结束、经过性能检测依然能合格的,是可以出厂继续使用的。
1,漏电流测试 漏电流_百度百科
漏电流形成和测试方法
漏电流测试方法 - 百度文库
漏电流如何测试
漏电流如何测试-电子发烧友网
2,电介质强度测试流程 电介质强度测试流程 - 百度文库
电介质强度测试方法 - 百度文库
耐压测试方法,为什么要测漏电流,耐压,保护接地阻抗 耐压测试方法 - 百度文库
3,保护接地阻抗测试操作规范 百度文库
1.在控制台输入命 npm config get registry 输出默认地址为:https://registry.npmjs.org/
2.修改为阿里镜像地址命令: npm config set registry https://registry.npmmirror.com/
1.进行靶场搭建 准备两台虚拟机 靶机:win7 攻击机:kali linux win7IP 172.26.0.130kali linuxIP 172.26.0.129 虚拟机搭建好后,相互ping能ping同就行 安装xampp XAMPP Installers and Downloads for Apache FriendsXAMPP is an easy to install Apache distribution containing MariaDB, PHP and Perl.https://www.apachefriends.org/zh_cn/index.html
xampp内包含以下三种功能模块,xampp可用于快速开发搭建自己的php网站 PHP动态网页设计语言Apache网络服务器Mysql关系型数据库管理系统 安装好后的页面 2.搭建dvwa靶场 下载dvwa https://github.com/digininja/DVWA
下载后解压将解压的dvwa文件放到xampp安装目录的htdocs文件夹里 完成后启动xampp,并启动xampp里的apache和mysql后面按钮start进行启动,当apache与mysql变成下图绿色后就行了 3.靶场搭建完成 验证靶场是否搭建成功,用攻击机kali的浏览器访问172.26.0.130/dvwa(172.26.0.130为靶机ip地址,dvwa是解压后dvwa的文件名) 进入后输入账号:admin密码:password点击login即可登录,并进入靶场主界面,然后选择sql注入 4.对靶场账号密码进行破解 打开 kali终端输入sqlmap(kali系统自带sqlmap工具无需下载) sqlmapsqlmap是一个自动化的SQL注入工具,其主要功能是扫描,发现并利用给定的URL和SQL注入漏洞,其广泛的功能和选项包括数据库指纹,枚举,数据库提权,访问目标文件系统,并在获取操作权限时执行任意命令。 通过反射型xss拿到cookie值(需要登陆所以需要cookie) 通过sqlmap得到当前服务器所有数据库
sqlmap -u "http://172.26.0.130/dvwa/vulnerabilities/sqli_blind/?id=1&Submit=Submit#" --cookie 'security=low; PHPSESSID=md26gc5jbodavaqr6pu119v4kg' --dbs
--dbs显示所有数据库 通过sqlmap继续查看dvwa库的所有表(可得guestbook表与users表)
sqlmap -u "http://172.26.0.133/dvwa/vulnerabilities/sqli_blind/?id=1&Submit=Submit#" --cookie 'security=low; PHPSESSID=md26gc5jbodavaqr6pu119v4kg' -D dvwa --tables
-D选择数据库--tables查看所有表 据此我们可猜测一下账号密码会在哪个表里(首先盲猜是在users表里,查看字段是找到账号密码的手段之一,一般账号字段为user,密码字段为password)
sqlmap -u "
环境:spring + springMvc + mybatis + maven
关于在springMVC环境访问web-inf目录下文件,其一有在springMVC xml文件下加`
<!-- 对静态资源文件的访问 不支持访问WEB-INF目录 --> <mvc:default-servlet-handler /> ` ```<!-- 启动注解驱动--> <mvc:annotation-driven/> 如果还是不能访问,继续在配置文件中追加 <mvc:resources mapping="/res/**" location="/WEB-INF/res/" /> mapping:映射 location:本地资源路径,注意必须是webapp根目录下的路径。 两个*,它表示映射resources/下所有的URL,包括子路径(即接多个/) 这样我们就可以直接访问该文件夹下的静态内容了。 如在jsp页面中映入jquery文件 ```java 第一种: <script type="text/javascript" src="${pageContext.request.contextPath}/res/js/jquery/jquery-1.8.3.js"></script> <base href="${pageContext.request.scheme }://${pageContext.request.serverName }:${pageContext.request.serverPort }${pageContext.request.contextPath }/"> <script src="resource/jquery/jquery-2.1.1.min.js"></script> 第二种: <script src=" ${pageContext.request.contextPath}/resource/jquery/jquery-2.1.1.min.js"></script> 第三种: <% String path = request.getContextPath(); String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path + "/"; %> <script src="
reCAPTCHA简介 数字时代带来了许多便利和机会,消除了隔阂,并以以前难以想象的方式连接了世界。然而,随着这些先进技术的发展,网络世界也见证了Spam(广告信息)和数据滥用等恶意活动的激增。在这个不断发展的数字环境中,像reCAPTCHA这样的工具已经成为网络防御的关键组成部分,提供强大的安全措施来保护网站,并保持用户友好的体验。
CAPTCHA(Completely Automated Public Turing test to tell Computers and Humans Apart,通常翻译为人机识别)的概念被引入为验证用户是否为人类而不是机器人的系统。这些测试通常涉及人类相对简单但机器人难以解决的任务,例如解密扭曲的文本或从网格中选择特定的图像。谷歌的reCAPTCHA是这一概念的更为精细的版本,它更进一步,提供一个既更安全又不会打扰用户的系统。
reCAPTCHA的核心,在于其双重作用:它通过区分人类用户和自动机器人,帮助保护网站免受垃圾邮件和滥用,同时通过使这一过程尽可能平稳,增强了用户体验。这一双重作用一直是reCAPTCHA多年演进的推动力量,每个版本在安全性和用户友好性方面都有所改进。
最初的reCAPTCHA是网络安全领域的一大进步,利用了用户必须解析并输入的扭曲文字。虽然当时很有效,但它也存在某些挑战,特别是对具有视觉缺陷以及发现扭曲文本难以解释的用户来说。
为了应对这些挑战,reCAPTCHA v2引入了“I'm not a robot”复选框。这个版本是为了更加用户友好而设计的,同时保持强大的安全措施。它利用了高级风险分析技术,使大多数人可以一次点击通过测试,而机器人则会被提出更难解决的基于图像的任务。
随着网络威胁的不断发展,reCAPTCHA也在不断发展。reCAPTCHA v3的引入标志着该系统识别人类和机器人的方法发生了重大变化。与提出挑战的方法不同,reCAPTCHA v3根据用户与网站的交互评估,并根据用户是否为机器人进行评分。这种方法提供了更加顺畅的用户体验,因为它在不需要与用户直接交互的情况下在后台运行。
最新版本,reCAPTCHA v3企业版,提供了更先进的安全功能,可以提供更详细的网站流量信息,并能够对可疑活动作出更细致的反应。它保留了reCAPTCHA v3的用户友好功能,在后台无缝运行,不会干扰用户的体验。
总之,reCAPTCHA作为防范网络垃圾邮件和滥用的第一道防线,在为网站提供必要的安全保障的同时,维护了网络友好的用户体验。它多年来的演变说明了必须在保持强大的安全措施和确保平稳的用户体验之间取得平衡。通过了解reCAPTCHA的作用和重要性,网站所有人可以更有目的地保护其网站和用户免受潜在的网络威胁。随着我们在这个数字时代的前进,像reCAPTCHA这样的工具将继续在维护网络空间的完整性和安全性方面发挥关键作用。
reCAPTCHA类型识别 学习识别不同类型的reCAPTCHA是一项必不可少的技能,不仅适用于Web开发人员和网络安全专业人员,也适用于普通用户在互联网上浏览。每个reCAPTCHA版本都有其独特的特征,用户互动模式和代码片段。本节将指导您识别这些独特的特征,以准确识别网站上使用的reCAPTCHA类型。
reCAPTCHA v1: 这是reCAPTCHA的初始版本。用户需要输入两个扭曲的单词。其中一个是已知的单词,用于验证用户是否为人类,而另一个是未知单词,用于帮助数字化书籍和其他来源的文本。如果您在网站上看到这种CAPTCHA样式,则可以明确表示正在使用reCAPTCHA v1。
reCAPTCHA v2(标准版): 此版本引入了著名的“我不是机器人”复选框。一旦用户勾选此框,reCAPTCHA根据用户的行为判断其是否为人类。如果reCAPTCHA怀疑用户可能是机器人,则会提出第二个挑战,通常是基于图像的,以进一步验证用户是否为人类。
您可以通过查找“I'm not a robot”复选框来确定reCAPTCHA v2。在页面源代码中,查找包含“recaptcha / api.js”的script标记。存在此标记,表示使用reCAPTCHA v2。
reCAPTCHA v2(隐形版): 隐形版本的reCAPTCHA v2提供与标准版本相同的安全级别,但体验更加流畅。与要求用户勾选的标准reCAPTCHA v2不同,隐形reCAPTCHA v2仅在检测到可疑活动时触发CAPTCHA挑战。
确定reCAPTCHA v2 Invisible可能有些棘手,因为没有明显的复选框。您需要检查网站的源代码。查找包含'recaptcha / api.js'和设置为'invisible'的'data-size'属性的script标记。通常很容易识别,只需检查是否在您执行可疑操作后出现了reCaptcha v2框,看起来像这样:
reCAPTCHA v2企业版: 这是reCAPTCHA v2的更高级版本。它提供了更复杂的抵御机器人的防御,并提供了详细的风险分析。
确定reCAPTCHA v2企业版需要检查源代码以查找“recaptcha / enterprise.js”script标记。此标记特定于reCAPTCHA v2企业版。
reCAPTCHA v3: 此版本在后台运行,评估用户与网站的交互并分配一个得分,指示用户可能是机器人的可能性。reCAPTCHA v3不会通过挑战来打断用户的体验。
pycharm 允许并行运行 运行多个文件 运行多个py文件
1. 拉项目到本地 git init // 初始化 git remote add origin http://XXX.git // 连接仓库地址 git pull origin master // 拉取项目 2.项目推远程仓库 git add . // 全添加 git status // 查看(可省略) git commit -m "提交说明" git push origin master // 推送到远程master分支 3.报错 解决方法 git branch -m master main // 把本地master仓库名称 修改为远端的main git push origin main // 推送到远程main分支
接上文:Go-Benchmark入门-基础篇(上)
引言 本篇是进阶篇,围绕最佳实践,介绍项目中可能会用得上的一些技巧和科普更多的 benchmark 知识,也是对本人半个多月实践的一次总结和备忘。
go版本:
go1.19.4 darwin/arm64 最佳实践 提升准确度:-benchtime 默认情况下,benchmark只运行1秒钟,不过我们可以通过如下2个命令控制运行时长和测试轮次,以提高准确度:
-benchtime t Run enough iterations of each benchmark to take t, specified as a time.Duration (for example, -benchtime 1h30s). The default is 1 second (1s). The special syntax Nx means to run the benchmark N times (for example, -benchtime 100x). -count n Run each test, benchmark, and fuzz seed n times (default 1). If -cpu is set, run n times for each GOMAXPROCS value.
第1关 数据介绍 ########## Begin ########## happiness2015 = pd.read_csv('World_Happiness_2015.csv') #把前五行存储在变量first_5中 first_5 = happiness2015.head() #使用DataFrame.info()方法打印该dataframe的详细信息 happiness2015.info() ########## End ########## print(first_5) 第2关 用循环进行数据聚合 ########## Begin ########## for reg in Region: region_group = happiness2015[happiness2015['Region'] == reg] region_mean = region_group['Happiness Score'].mean() mean_happiness[reg] = region_mean ########## End ########## # 打印结果 第3关 GroupBy的使用 ########## Begin ########## grouped=happiness2015.groupby('Region') aus_nz = grouped.get_group('Australia and New Zealand') ########## End ########## print(aus_nz) 第4关 探索GroupBy对象 ########## Begin ########## # 1.按照Region列对happiness进行分组 # 2.取出第四行,第14行两行的数据 # 3.
第1关 读CSV文件 第2关 清洗列名 第3关 清洗列名(续) #********** Begin **********# def clean_column_name(col_name): # 删除开始和结束处的空格 col_name = col_name.strip() # 用缩写os替换子字符串Operating System col_name = col_name.replace('Operating System', 'OS') # 用下划线替换所有空格 col_name = col_name.replace(' ', '_') # 移除括号 col_name = col_name.replace('(', '').replace(')', '') # 转换为小写 col_name = col_name.lower() return col_name col_name_list = [] for col_name in laptops.columns : col = clean_column_name(col_name) col_name_list.append(col) laptops.columns = col_name_list print(laptops.columns) #********** End **********# 第4关 将字符串列转换为数值列 #********** Begin **********# unique_ram = laptops['ram'].
数字化浪潮构成了新世界跳动的脉搏,在医药行业转型的大环境下,实现数字化升级已经成为医药企业走向未来、拓展全球市场的必由之路。
近日,济南同科医药物流有限公司(简称“同科医药”,系同科股份全资子公司,股票代码:837833.NQ)和实在智能达成合作,借助实在RPA自动化技术,共建互联网+医药流通新生态,让更多更优质的药品和医疗资源惠及每一位消费者。
同科医药是经山东省食品药品监督管理局批准的集医药销售、储存及运输为一体的现代医药物流企业,先后服务于哈药集团、修正药业、康缘、云南白药、东阿阿胶、碧生源、九芝堂、康恩贝等200余家知名品牌,在供应链、医药电商B2C、B2B及新媒体等领域开展了多维度战略合作,同时协同京东、阿里、美团、百度、快手、抖音等各大互联网平台共同发展互联网大健康产业。
数字员工上岗同科医药,助力品牌企业搭建医药电商新生态
打通链路,助力服务最后一公里
在疫情的催化和数字化的浪潮下,医药电商与医药流通行业呈现出不同的发展态势。作为医药流通领域全案解决服务商,同科医药将二者融合,辅以数字化运营能力,构筑起B2B、B2C以及新媒体的全渠道“医药电商供应链+数字化运营”服务模式,为工业品牌提供从供应链到电商分销,到运营服务,到线上维价等的全链路解决方案。
在医药电商供应链方面,同科医药作为医药电商首批入局的企业,对工业品牌线上业务的全面拓展有7年的操盘经验,可以有效地协助工业品牌企业梳理业务体系,制定线上运营策略,提升品牌影响力。此外,同科医药还打造了仓储占地面积110亩的现代化医药物流中心,可多仓联动,日均峰值吞吐量达4万箱,支持100亿元/年仓储分拨能力,为医药行业提供标准化仓储+配送+山东全境无盲区的最后一公里配送。
数字赋能,高效聚合数字沉淀
在数字化运营方面,同科医药基于传统医药流通企业在货流、物流、资金流、信息流、商流方面的能力积淀,已完成现代化信息管理系统搭建,包括采用温湿度自动监控系统、自动分拣系统、WPS仓储管理系统、TMS物流管理系统、ERP等现代化信息系统,实现药品在库内自动储存、自动分拣、自动补货、自动传输。
同时,借数字化运营优势,同科医药已覆盖京东大药房、阿里健康等TOP20的电商主流渠道,实现线上业务的全域布局。在实在智能未来电商千人大会上,同科医药VP雷小磊表示:“多系统、多平台的建设,也给同科医药带来诸如数据源繁杂、取数繁琐、跨平台数据处理困难等现实问题。在部署了实在RPA·数字员工后,各个电商平台的品牌经营数据自动汇总到内部BI系统,帮助同科医药快速实现销售数据把控,弯道超车建设数字化。”
提效增质,携手医药数字化升级
有了数字员工和大数据支持,同科医药实现了流程重复、规则明确的业务自动化,不仅提高了电商平台运营效率和员工幸福感,还根据不同平台用户的历史数据和偏好,为工业品牌打磨出一套从“从无到有、从有到优”的全维度规划打法,能快速根据工业品牌特性量身定制切实可行的落地计划,提升工业品牌影响力,助力实现业绩倍增。
相信同科医药和实在智能的强强联手,在未来,将为医药行业发展注入新动力,向广大用户提供更好、更放心的健康保障。实在智能也将延续全国产全自研的RPA技术优势,秉承“为全社会贡献100万个数字员工”的愿景,帮助千行百业搭建“实在好用”的超级自动化平台。
问题描述 在vsCode中,项目里面只要格式化就会属性自动换行,看上去很麻烦,问题如图:
解决方案: 注意: 一定要在根目录创建.prettierrc文件
在项目的根目录中新创建一个配置文件.prettierrc文件, 注意一定要是根目录, 加入以下代码:
{ "semi": false, "singleQuote": true, "printWidth": 150 }
下载:
https://github.com/BtbN/FFmpeg-Builds/releases/tag/autobuild-2021-01-26-12-37
解压,然后配置环境变量,在Path下配置,目录结构如下(解压后的/bin处)
然后再cmd中使用ffmpeg查看是否配置成功
2.查看视频关键帧
首先把MP4文件转成FLV文件
ffmpeg -i test.mp4 -c copy -f flv -flvflags add_keyframe_index output.flv
然后查看output.flv文件
ffprobe -v trace -i output.flv
其中keyframe就是关键帧的位置了了,我的这个视频总共时27.7s,然后关键帧可得分别是0s、3s、7s、11s、15s、19s、23s、27s
或者可以下载Elecard StreamEye Tools查看MP4文件格式(图中红色线条代表关键帧)
3.给视频添加关键帧
然后我们执行如下命令
ffmpeg -i 原视频名称 -force_key_frames expr:gte(t,n_forced*5) 新视频名称 #每0.5s插入一个关键帧 4.x264的安装
首先需要安装Mingw环境
下载:MinGW - Minimalist GNU for Windows - Browse Files at SourceForge.net
下载好后双击执行mingw-get-setup.exe文件,选择一个保存位置
安装完成后我们点击Continue(实际会在桌面生成一个MinGW Installer文件,点击Continue时会自动打开此文件),双击打开后,如图,我们右键勾选这7项进行下载安装
安装完成后如图
然后我们会在之前设置的保存路径下得到如图内容
我们将..../mingw/mingw/bin配置到系统环境变量(根据你自己的位置路径去配置)
然后我们安装x264
下载:x264, the best H.264/AVC encoder - VideoLAN
然后得到一个x264-master.tar.bz2文件,我们选择一个位置保存并解压
然后我们找到mysys.bat,双击打开得到一个命令窗口
然后我们在命令窗口中找到x264中confugure所在路径,然后执行configure命令
发现提示我们使用--disable-asm命令,于是我们更新命令语句后再次执行
然后我们执行make语句如图
然后再执行make install,此时会在mysys/1.0目录下生成一个local文件夹
什么是WebSocket? WebSocket是一种在单个TCP连接上进行全双工通信的协议。它使得浏览器和服务器之间的实时通信变得更加容易。与HTTP请求不同,WebSocket连接是持久的,这意味着一旦建立连接,客户端和服务器之间的通信将一直保持打开状态,直到其中一方关闭连接。
Laravel中的WebSocket Laravel是一个流行的PHP框架,它提供了许多工具和库,使得开发Web应用程序变得更加容易。Laravel也提供了一种简单的方法来实现WebSocket,这使得在Laravel应用程序中实现实时通信变得更加容易。
Laravel中的WebSocket使用了Ratchet库,这是一个PHP实现的WebSocket库。Ratchet提供了一个简单的API,使得在Laravel应用程序中实现WebSocket变得更加容易。
实现WebSocket 下面是在Laravel中实现WebSocket的步骤:
步骤1:安装Ratchet 要在Laravel中使用WebSocket,首先需要安装Ratchet。可以使用Composer来安装Ratchet。在终端中运行以下命令:
composer require cboden/ratchet 步骤2:创建WebSocket服务 在Laravel应用程序中,可以使用Artisan命令来创建WebSocket服务。在终端中运行以下命令:
php artisan make:command WebSocketServer 这将创建一个名为WebSocketServer的Artisan命令。在app/Console/Commands目录中可以找到该文件。
打开WebSocketServer.php文件,并将以下代码添加到handle方法中:
use Ratchet\Server\IoServer; use Ratchet\Http\HttpServer; use Ratchet\WebSocket\WsServer; use App\WebSocket\Chat; class WebSocketServer extends Command { protected $signature = 'websocket:serve'; protected $description = 'Start the WebSocket server'; public function handle() { $server = IoServer::factory( new HttpServer( new WsServer( new Chat() ) ), 8080 ); $server->run(); } } 这将创建一个WebSocket服务器,并将其绑定到8080端口。Chat类是WebSocket服务器的实现,我们将在下一步中创建它。
步骤3:创建WebSocket处理程序Chat类 接下来,我们需要创建Chat类。在app/WebSocket目录下创建Chat.php文件,并将以下代码添加到其中:
<?php namespace App\WebSocket; use Ratchet\MessageComponentInterface; use Ratchet\ConnectionInterface; class Chat implements MessageComponentInterface { protected $clients; public function __construct() { $this->clients = new \SplObjectStorage; } public function onOpen(ConnectionInterface $conn) { $this->clients->attach($conn); echo "
在我执行vue项目时,执行npm install 命令后,出现cannot read properties of null的问题,之后执行npm run dev就一直报错
解决方案:
百度上上搜,一直说删除了node-model包后再下载,可我重复了好多次,还是出现了一样的错误,后面才发现要清理缓存,再执行npm install 才可以安转成功
清理缓存命令:npm cache clear --force
之后再执行npm install 就成功了
运行npm run serve
问题就解决了
目录
1、什么是布隆过滤器
2、布隆过滤器的原理
2.1 布隆过滤器的数据结构
2.2 布隆过滤器的检索和插入原理
2.3 布隆过滤器元素的修改和删除
3、布隆过滤器的使用场景
3.1 Redis通过布隆过滤器防止缓存穿透
3.2 RocketMQ通过布隆过滤器防止消息重复消费
4、布隆过滤器优缺点
4.1 优点:
4.2 缺点:
1、什么是布隆过滤器 以下定义来自百度百科:
布隆过滤器(Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都比一般的算法要好的多,缺点是有一定的误识别率和删除困难。
从上述定义我们可以得到以下关键信息:
布隆过滤器是由很长的二进制向量(即可以理解成很长的0、1数组)与一系列随机映射函数(Hash函数)构成。布隆过滤器的作用是检索一个元素是否存在我们的集合之中。优点是空间效率和查询时间都比一般的算法要好的多;缺点是有一定的误识别率和删除困难。 (*^▽^*)OK,那我们就根据这个定义得到的信息来详细的讲解我们的布隆过滤器。请大家耐心观看~
注意!接下来的原理我们都会基于定义来逐句解释和讲解布隆过滤器,请大家记住上述三点关键信息。
2、布隆过滤器的原理 2.1 布隆过滤器的数据结构 我们从定义总结的关键信息可知:布隆过滤器是由很长的二进制向量(即可以理解成很长的0、1数组)与一系列随机映射函数(Hash函数)构成。因此我们可以将布隆过滤器理解成下图这种很长的一个二进制数组:
正是由于布隆过滤器的数据结构仅需要存储“0”或“1”,因此所占用内存极少,这也是布隆过滤器的一大优点。 2.2 布隆过滤器的检索和插入原理 从上图布隆过滤器的数据结构图和定义的关键信息我们可以知道:布隆过滤器实际上是个很长的二进制数组,作用是检索一个元素是否存在我们的集合之中。那么布隆过滤器是如何通过上述数据结构判断一个元素是否存在我们的集合之中的呢?
这里就需要用到我们定义中提到的“一系列随机映射函数(Hash函数)”了。
首先我们需要知道什么是Hash函数?这里我们给出Hash函数的一个简要的说明:
简单来说Hash函数就是把输入值通过特定方式(hash函数) 处理后 生成一个值,这个值等同于存放数据的地址。
比如我们当前的Hash函数是 y=x²&(len-1),这里y是指最终在布隆过滤器的数据结构(二进制数组)中存放的下标位置,x指我们传入的值,len指数组的长度。那么如果当数组长度为100(举个例子,实际上数组长度是很长的),传入的值为5,则我们通过Hash函数得到的下标为25。那么此时我们便将下标25的值从0标为1。这就是插入(增加)数据的原理。
插入(增加)数据流程图如下:
那么,当我们下次再输入这个值的时候,我们会得到当前数组对应下标的值为1,说明我们有这个数字!这是不是就是检索的原理了!
检索流程图如下:
当然,这里有基础的同学肯定会发现我们上述说的过程虽然很简单,但是存在很大的问题:
①存在Hash冲突导致误判:
首先我们先对Hash冲突作一个简要说明:
根据key(键值)即经过一个Hash函数F(key)得到的结果的作为地址去存放当前的value值,但是却发现算出来的地址上已经被占用了,这就是所谓的hash冲突。
我们基于上述例子继续讲,在经过上述流程后,我们数组下标为25的值是1。此时我们传入一个值25,那么通过我们上述举例的Hash算法计算后得到的数组下标值也为25,那么此时布隆过滤器是不是就会认为值25是存在的!但是实际上是因为5和25经过Hash映射后得到同一个地址,导致了误判!
当然,这么简单的问题伟大的“布隆先生”肯定不会犯如此“低级”的错误,因此解决方法就和我们上述定义中的关键字“一系列Hash函数”有关了。
我们通过“一系列的Hash函数”,比如Hash函数①y=x²&(len-1)②y=(2*x)&(len-1) ③y=(x+x²)&(len-1) 这三个Hash函数一起来决定某个元素是否存在我们的集合中。
也就是检索流程变为:将key值传入一系列Hash函数得到对应的一系列数组地址(索引下标),注意这里一般来说有几个Hash函数就会得到几个地址,然后去判断这几个索引下标对应的值是否均为1,是的话则说明存在,否则不存在。
上述才是布隆过滤器检索元素是否存在的真正流程,检索元素流程图因此对应变化如下: 插入元素流程变为:根据一系列Hash函数得到一系列地址,将对应地址下标值改为1,流程图如下:
当然,我们从布隆过滤器定义中提到的缺点可以知道:布隆过滤器会有一定误判率。说明即使是在一系列Hash函数下,依然会有巧合:“一个不存在的元素,对应的一系列映射后的地址的值为1,即出现误判”。这也是无法避免的事情,毕竟如果数据量很大的话,很难防止有一定量的、不存在的“幸运儿”能通过布隆过滤器的筛选。
当然,我们在使用布隆过滤器的时候能通过设置两个参数:①预期数据量 ②误判率期望值。我们可以通过设置“误判率期望值”来达到我们能接受的误判率。
当然!大家别异想天开:“哎呀,那我设置0不就行了吗?”这肯定是不可能的,而且设置的误判率越低,数据量越大,占用内存则越大,运行时间则越慢!这也很好理解:数据量越大肯定占用越多内存空间,误判率越低则说明要越多的Hash函数来进行运算,则运行时间越慢,一个key对应的地址也多了,肯定占用越多内存空间。
2.3 布隆过滤器元素的修改和删除 我们从定义可以知道:我们想要修改或删除一个元素时,同时去保证布隆过滤器不受影响是几乎不可能的。
为什么这样说呢,由于我们在插入元素时,不同的值可能经过一系列Hash函数后得到的一系列地址,总有可能两个或多个值经过某个Hash函数映射后得到其中一个地址会一样,此时数组中对应的下标肯定为1,当我们删除或修改某个元素后,我们如果想将其原来对应地址的值从1改为0后,无法确定这个地址是否也对应其他值,如果贸然修改,可能会导致其他原本存在的值在检索时返回不存在的情况!这种情况是极其危险的,可能会导致数据的“逻辑丢失”。
因此我们这里不讨论修改和删除的情况。因为布隆过滤器对元素的删除不太支持,目前有一些变形的特定布隆过滤器支持元素的删除。
3、布隆过滤器的使用场景 3.1 Redis通过布隆过滤器防止缓存穿透 首先我们需要知道什么是缓存穿透,这里我们给出缓存穿透的定义。当然大家也可以去我主页看我关于Redis缓存写的文章:点我!!!传送门!!!
环境:Nordic NRF24L01+发送数据,无需自动应答;NRF52832接收收据。
问题一:NRF24L01+发送地址与NRF52832接收地址 一般情况下可以如下设置:
NRF24L01+发送地址设置:
void SPI1_Write_Buf(uint8_t address, uint8_t *pBuf, uint8_t datalen) { uint8_t tmp,i; CSN_L; SPI1_DR = address;//写入需要操作的寄存器地址, while(!(SPI1_SR_RXNE)); tmp = SPI1_DR; //读取数据,仅仅是为了清除标志位 while(!(SPI1_SR_TXE));//等待发送寄存器为空 for(i=0;i<datalen;i++) { SPI1_DR = pBuf[i]; while(!(SPI1_SR_TXE)); } CSN_H; } #define TX_ADR_WIDTH 5; //发射地址长度 const uint8_t TX_ADDRESS[TX_ADR_WIDTH] = {0x11, 0x22, 0x33, 0x44, 0x55}; //发射地址 SPI1_Write_Buf(SPI_WRITE_REG+TX_ADDR, (uint8_t*)TX_ADDRESS, TX_ADR_WIDTH); NRF52832接收地址设置:
#define RX_ADR_LENGTH 5; //接收地址长度 const uint8_t addr_prefix[1] = {0x11}; const uint8_t base_addr_0[RX_ADR_LENGTH -1] = {0x22, 0x33, 0x44, 0x55}; nrf_esb_set_address_length(RX_ADR_LENGTH); nrf_esb_set_prefixes(addr_prefix, 1); nrf_esb_set_base_address_0(base_addr_0); 但是当把地址长度改为4时(如下),就会出现问题,NRF52832接收不到数据,这是因为地址设置错误。
题目传送门
前言 说实话这题根本用不到什么折半……,今天看机房大佬写了半天加了一堆剪枝还以为很难,其实是你们想复杂了
20分钟不到从看题到代码实现
这题其实只需要可行性剪枝加排序 哦还有个后缀和
进入正题 小木棍子都听说过吧 没错就是小波上课打挂那道
跟这题没多大关系,不过如果你切了小木棍,就会觉得这道题很简单
讲讲我一开始的思路 一开始因为机房大佬在各种卡常,玄学剪枝,大叫折半是个好东西,还以为是个和小木棍一样的毒瘤
讲真我不喜欢打折半
第一眼看,排序,然后和埃及分数一样根据后续的瓜全买能不能满足剪枝,然后搜索的时候加个二分寻找当前第一个切开比剩下小的值
后面发现因为数据水所以加不加二分没差多少
最后清晰的讲述一下我的思路 第一步,先将所有的元素从大到小进行排序,然后做一下后缀和(后面可行性剪枝用)
第二步,开始搜索。 搜索的时候注意顺序要从前往后搜,也就是说后面被搜到的元素不能大于前面的(这里感性理解一下,如果大的搜了搜小的,然后搜完小的又去搜大的就重复了,排序就没有意义了)
关于可行性剪枝自然就是用第一步求出的后缀和直接判断一下后面所有的瓜加起来有没有剩下需要的瓜多
然后就结束了
关于一些小技巧 可以在读入的时候就把数据乘 2 ,这样就可以用 l o n g l o n g long long longlong 存下了(机房大佬说double常数很大)
然后就是把题目看清楚,求的是 需要切开的瓜,还有如果不行要输出 − 1 -1 −1 不然你会因此 W A WA WA 一个点
Code #include <bits/stdc++.h> #define int long long//记得开 long long #define ull unsigned long long const int N = 1e6+10; const int M = 1e4+10; const int mod = 1e9+7; const int INF = 0x3f3f3f3f; using namespace std; int a[40],n,m,b[40]; bool esmite(int pos,int res){ return b[pos+1] >= res; } int ans = INF; int find(int x){// STL熟练的可以使用 upper_bound 或者 lower_bound 本蒟蒻这两玩意用法分不清故手写 int l = 1, r = n; while(l < r){ int mid = (l+r) >> 1; if(a[mid] / 2 <= x){ r = mid; }else{ l = mid+1; } } return l; } void dfs(int num,int rest,int pos){//num 当前切开了几个瓜,rest 还剩下需要多少瓜,pos当前搜到哪个位置了,防止往前搜 if(rest == 0){//统计答案 ans = min(ans,num); return; } if(!
目录
一、前言
二、RAM_STYLE
2.1 工程代码
2.2 参考资料
一、前言 RAM英文全称为Random Access Memory,随机存取存储器,可以实现数据的快速随机读写,RAM可直接verilog代码编写,也可调用IP核。
二、RAM_STYLE RAM_STYLE属性使用格式:(* ram_style=value *) reg [len-1:0] reg_name [num-1:0];
len为存储器的宽度,num为深度,value为生成方式。
XDC使用格式:set_property RAM_STYLE value [get_cells reg_name]
Vivado可以通过属性RAM_STYLE指定RAM的生成方式,也即指定value的值,value可为block,distributed,registers,ultra,mixed,auto
block:使用块状RAM来实现
distributed:使用LUT来实现
registers:使用寄存器来是实现
ultra:只针对ultrascale系列的器件才有效,即使用ultrascale器件中的URAM实现
mixed:根据使用最小的面积原则灵活确定RAM的实现方式
auto:综合工具来决定实现方式
属性如果设置在一个信号上,属性的作用范围限定为设置的信号上,如果是设置到一个模块层级上,该模块的RAM都按设置方式实现,但该属性对于模块中的子模块不生效。
2.1 工程代码 module RAM_STYLE( d,address,ce,we,clk,out );
parameter len=12,depth=64;
input [len-1:0] d;
input [5:0] address;
input ce,we,clk; //we为写入的使能信号,为1时对RAM进行写入
output reg [len-1:0] out;
(* RAM_STYLE="block"*) reg [len-1:0] ram [depth-1:0]; always@(posedge clk)
写论文的时候遇到这个问题,由于正文最后一行使用了分节符导致删除后面内容后有一页空白页,如果直接删除分节符的话会导致前面的格式变乱,最终在导师的帮助下找到了解决办法,方法如下:
1.点击此处,显示编辑标记
2.将光标移至分节符之前
3.打开页面设置
4.选择插入点之后
问题就解决了
函数
是一个可反复使用的程序段
从其他的程序段中使用调用语句来执行这段程序
好处
减少编程的工作量,提高开发效率
提高代码的可重用性
SQL Server的内部函数
可以和SQL Server的SELECT语句联合使用
可以与UPDATE和INSERT语句一起使用
进行类型转换、日期处理、数学计算,实现系统功能
系统函数
获取有关SQL Server中对象和设置的系统信息
字符串函数
控制返回给用户的字符串
字符串函数
几乎所有信息都需要转换成字符串才能正确显示
使用“+”运算符拼接两个字符串
日期函数
可以提取日期值中日、月以及年,并进行操作
日期函数
标准的日期字符串写法格式
“YYYY-MM-DD HH:MM:SS”或“YYYY-MM-DD”
聚合函数
用于对一组值执行计算,并返回单个值
如:求总和、平均值、最大或最小值等
SUM:求总和
AVG:求平均数
MAX:返回最大值
MIN:返回最小值
XOUNT:返回非空值的计数
分组查询
指将查询结果按条件分组,再使用聚合函数返回每一个组的汇总信息
SELECT select_list FROM table_name
GROUP BY column_name 对查询结果进行分组
HAVING search_conditions指定分组查询条件
比较HAVING子句和WHERE子句
过滤数据的时间
条件
WHERE子句
分组之前
不能包含聚合函数
HAVING子句
分组之后
经常包含聚合函数
同在一个SELECT语句中,执行顺序
WHEREàGROUP BYàHAVING
数学函数
用于对数值进行代数运算
如果一个查询需要对多个表进行操作,就称为联接查询
联接查询的结果集或结果称为表之间的联接
关系数据库查询的特征
通过各个表之间共同列的关联性查询数据
表的联接类型
内联接
只返回两个数据集合之间匹配关系的行(INNER JOIN)
外联接
目录
一、string基本概念
1、本质
2、string和char * 区别:
3、特点:
二、string构造函数
1、构造函数原型
2、示例
三、string赋值操作
1、赋值的函数原型
2、示例
四、string字符串拼接
1、函数原型
2、示例
五、string查找和替换
1、功能描述
2、函数原型
3、示例
六、string字符串比较
1、功能描述
2、比较方式
3、函数原型
4、示例
七、string字符存取
1、存取方式
2、示例
八、string插入和删除
1、函数原型
2、示例
九、string子串
1、函数原型
2、示例
一、string基本概念 1、本质 string是C++风格的字符串,而string本质上是一个类
2、string和char * 区别: 1、char * 是一个指针
2、string是一个类,类内部封装了char*,管理这个字符串,是一个char*型的容器。
3、特点: string 类内部封装了很多成员方法
例如:查找find,拷贝copy,删除delete 替换replace,插入insert
string管理char*所分配的内存,不用担心复制越界和取值越界等,由类内部进行负责
二、string构造函数 1、构造函数原型 1、string();
//创建一个空的字符串 例如: string str;
2、string(const char* s); //使用字符串s初始化
3、string(const string& str); //使用一个string对象初始化另一个string对象
4、string(int n, char c);
目录
一、awk概述
1.工作原理
2.语法格式
3.awk常用的内建变量
二、按行输出文本
1.输出所有内容
2.输出指定行号的内容
3.输出奇偶行内容
4.输出匹配行的行内容
二、按字段输出文本
1.输出指定字段
2.输出结果指定分隔符
2.1OFS指定输出结果分隔符
3.输出匹配字段的行内容
4.三元运算符
三、通过管道符、双引号调用shell命令
1.统计行数
2.查看当前内存使用百分比
3.查看当前cpu空闲率
4.显示系统上次重启时间
5.getline的使用
四、BEGIN,END模式
1.awk数组
2.过滤文本中重复行数
3.过滤访问本机密码输入失败的命令
一、awk概述 AWK是一种处理文本文件的语言,是一个强大的文件分析工具。
它是专门为文本处理设计的编程语言,也是行处理软件,通常用于扫描,过滤,统计汇总等工作,数据可以来自标准输入也可以是管道或文件。
1.工作原理 awk逐行读取文本,默认以空格或Tab键为分隔符进行分隔,将分隔所得的各个字段保存到内建变量中,并按模式或条件执行编辑命令。
与sed命令相比:
sed命令常用于一整行的处理。awk命令倾向于将一行分成多个“字段” 然后再进行处理。awk信息的读入是逐行读取的,执行结果可以通过 print 的功能将字段数据打印显示。在使用 awk 命令的过程中,可以使用逻辑操作符“&&”表示“与”,“||”表示“或”,“!”表示“非”;还可以进行简单的数学运算,如+、-、*、/、%、^分别表示加、减、乘、除、取余和乘方。 2.语法格式 awk 选项 '模式或条件 {操作}' 文件1 文件2 awk -f 脚本文件 文件1 文件2 3.awk常用的内建变量 FS:列分隔符。指定每行文本的字段分隔符,默认为空格或制表位,与“ -F ”作用相同 OFS:输出分隔符。指定输出字段间的分隔符。 RS:行分隔符。awk从文件读取资料时,将根据RS的定义把资料切割为多条记录, awk一次仅读取一条记录,以进行处理,预设值为 \n (换行符) NF:当前处理行的字段个数 NR:当前处理行的行号 FNR:awk当前读取的记录数,其变量值小于等于NR (比如当读取第二个文件时,FNR是从0开始重新计数,而NR不会)。 NR==FNR:用于在读取两个或两个以上的文件时,判断是不是在读取第一个文件 $0:当前处理行的整行内容 $n:当前处理行的第 n 个字段(第 n 列) FILENAME:被处理的文件名 二、按行输出文本 1.
目录
一、switch
1、定义:通过比较值来决定执行哪条分支
2、流程
3、与if相比哪个好
4、注意事项
二:for
1、定义:控制一段代码重复执行多次
2、作用:减少代码的重复编写,灵活控制程序的运行
3、格式
4、流程
5、debug来查看流程
6、使用场景
三:while
1、格式
2、流程
3、练习
四:do_while
1、格式
2、特点
3、流程
循环小结:
五:死循环
1、定义:可以一直执行下去的一种循环,如果没有干预不会停下来
2、演示
六:break、continue关键字
1、break
①:作用:跳出并结束当前所在循环
②:示例
③:练习
2、continue
①:作用 结束本次循环,继续下次循环
②:示例
总结:
注意事项:
一、switch 1、定义:通过比较值来决定执行哪条分支 2、流程 Ⅰ:先执行switch括号中表达式的值
Ⅱ:再执行与这个值与case后面值匹配的代码
Ⅲ:如果与case后面的值都不匹配则执行default后面的代码
3、与if相比哪个好 相对来说,if的功能强大于switch,但是因为需求的关系可以选择适合的方法。
比如: if适合做区间的判断。 例:0~100之间怎么怎么样
switch适合做比较值的判断。例:a == 1要干什么,a == 2要干什么就可以使用 switch
4、注意事项 ①:表达式类型支持 1、基本数据类型:byte、short、int、char
2、引用类型:String
不支持boolean|、float、double、long
②:case的值不能重复,也不能是变量,只能是常量
③:使用switch时记得加break,不然执行完这条语句不管下面的值和case匹不匹配都会执行,也就是穿透
④:对于case穿透的特性其实可以使用在相对应的需求中,例:周一到周五上班
周六周天休息
⑤:表达式里面是什么类型,case就要是什么类型
二:for 1、定义:控制一段代码重复执行多次 2、作用:减少代码的重复编写,灵活控制程序的运行 3、格式 快捷键(fori)
4、流程 注意:初始化语句只会执行一次
本文介绍了C++ override关键字使用详解以及与重载的区别。
C++ override关键字使用详解 一、override作用二、override在基类与派生类的应用2.1. 纯虚函数2.2. 普通虚函数2.3.Override重写 三、Override实例四、C++中重载(overload)与覆盖(override)4.1. 重载(overload)4.2. 重写/覆盖(override)4.3. 重载和覆盖的区别 五、疑惑解答5.1. 如果是虚析构函数, 就算子类析构函数和父类的析构函数不同, 也可以override 是为什么?5.2. 基类的析构函数和派生类的析构函数的函数签名是相同的吗 一、override作用 override关键字作用:
如果派生类在虚函数声明时使用了override描述符, 那么该函数必须重载其基类中的同名函数,否则代码将无法通过编译。 C++ override从字面意思上,是覆盖的意思,实际上在C++中它是覆盖了一个方法并且对其重写,从而达到不同的作用。override是C++11中的一个继承控制关键字。override确保在派生类中声明的重载函数跟基类的虚函数有相同的声明。
override明确地表示一个函数是对基类中一个虚函数的重载。更重要的是,它会检查基类虚函数和派生类中重载函数的签名不匹配问题。如果签名不匹配,编译器会发出错误信息。
override表示函数应当重写基类中的虚函数(用于派生类的虚函数中)。
在我们C++编程过程中,最熟悉的就是对接口方法的实现,在接口中一般只是对方法进行了声明,而我们在实现时,就需要实现接口声明的所有方法。还有一个典型应用就是在继承中也可能会在子类覆盖父类的方法。
公有继承包含两部分:一是“接口”(interface),二是 “实现” (implementation)。
二、override在基类与派生类的应用 例如Person类的几种成员函数的继承方式:
class Person { public: virtual void eat() const = 0; // 1.纯虚函数 virtual void say(const std::string& msg); // 2.普通虚函数 int name() const; // 3.非虚函数 }; class Student : public Person { public: protected: private: }; class Teacher : public Person { public: protected: private: }; 2.
微信小程序实现图片上传 最近做了个小程序,涉及到了图片上传的功能,今天给大家详细介绍下如何实现小程序图片上传,话不多说先上代码
首先是静态布局和样式部分 .wxml代码部分
<view class='load-img'> <view class='load-box'> <view class='img-item' wx:for="{{fileList}}" wx:key="index" > <image src="{{item.path}}" data-src="{{item}}" mode="aspectFill" data-list="{{fileList}}" bindtap=""></image> <icon class='icon' type="clear" size="20" color='#EF4444' catchtap='_onDelTab' data-idx="{{index}}" wx:if="{{!prevent}}"/> </view> <image class='img-add' bindtap='_addImg' wx:if="{{!prevent}}"></image> </view> </view> .wxss代码部分
/* 上传图片 */ .load-name { height: 80rpx; line-height: 80rpx; font-size: 30rpx; } .load-box { display: flex; flex-direction: row; flex-wrap: wrap; } .img-item, .img-add { position: relative; width: 140rpx; height: 140rpx; margin: 20rpx; } .img-add { border: 1px solid #ccc; } .
怎么制作网站?手把手教你10个网站建设的步骤!网站建设需要进行10个步骤,首先要确定网站建设的目标,考虑用户、品牌信息和竞争对手等,避免方向错误。其次,绘制网站建设地图和原型,确定位置大小、逻辑关系、统一性和用户体验等。然后,制作设计规范,规范化常用元素。接着,进行网站建设切图标注,拆分内容并重新构建原型。前端工程师通过重构设计图纸建立静态页面和调取数据。完成后,进行网站走查,确保与设计稿一致。优化网站内容和SEO,吸引用户和增加流量。测试每个页面并确保在所有设备和浏览器上正确加载后,网站就可以正式上线。最后,掌握专业的网站建设工具即时设计,提供灵感和设计素材,并省去重复劳动。
1、确定网站建设的目标 在建设网站之前,要考虑网站的用户、主要目的、品牌核心信息、竞争对手等问题,以避免项目朝着错误的方向发展。
2、绘制网站建设地图 绘制网站的逻辑框架,帮助设计师和开发人员快速了解网站的信息体系结构,并解释各个页面与内容元素之间的关系。
3、绘制网站建设原型 通过绘制低保真和高保真原型,确定网站建设的位置大小、逻辑关系、整体视觉统一性、页面可用性、设计合理性和用户体验感等,可以使用在线版PS即时设计辅助完成设计。
4、确定网站建设设计规范 制作设计规范,将建设网站的常用元素规范化并分成不同类别,比如字体、主体色、图表和图片等。
5、进行网站建设切图标注 将网站中所有内容拆分成单个元素并重新构建网站原型,使用即时设计可以方便地切图标注并支持多种尺寸的切图导出。
6、前端交付代码 前端工程师通过重构设计图纸,将其变为静态页面,并调取数据,从而建立一个网站。
7、网站走查 完成网站后,需要进行项目走查以确保网站与设计稿一致,这对于网站最终呈现效果非常重要。
8、网站优化 网站需要有优秀的内容和好的SEO,以吸引用户和增加流量。
9、网站测试及上线 在测试每个页面并确保网站在所有设备和浏览器上正确加载之后,网站就可以正式上线。
10. 掌握专业的网站建设工具即时设计 做网站建设的话普遍用在线协作设计软件即时设计,里面的矢量设计功能够完美胜任网页广告设计需求,同时一体化的协作和交付功能能够很好地联系起团队的每一个成员进行审核、修改以及提交等等。
以上就是网站建设的10个步骤了,在了解了怎么制作网站后,我们还需要熟练掌握网页设计工具以及了解网站建设的设计趋势和设计规范等。即时设计的官方模板集合提供了网页广告设计的灵感和设计素材。即时设计内置 丰富社区资源,集成腾讯、阿里、字节、今日头条、蚂蚁设计等众多大厂优秀的设计系统,所有 UI 设计师均可直接拖拽复用,从此省去大量模块化设计环节的重复劳动,设计无需从0开始。
目录
Mysql日志:
Mysql日志是什么,有什么用?
一、重做日志(redo log),回滚日志(undo log)的简单介绍
二、Mysql错误日志:(默认是开启的)
作用:
当然我们也可以自己配置error log的位置(配置文件路径:/etc/my.cnf)
三、Mysql的慢日志:(默认是关闭的)
作用:
可以很明显的看到,我们的Mysql慢日志是关闭的,所以当我们想要打开时,我们应该在Mysql的配置文件里进行修改:
但是为什么Mysql默认情况下没有打开慢日志呢?
四、Mysql的通用日志:(默认是关闭的)
作用:
我们如何打开Mysql的通用日志呢,我们应该与上面的慢日志一样,修改mysqld的配置文件
但是为什么Mysql默认情况下没有打开通用日志呢?
五、Mysql的二进制日志:(默认是关闭的)(很重要,有机密信息的记录)
作用:
二进制文件是如何产生的呢?
Mysql实例:
如何开启二进制日志文件呢?
什么时候会产生新的二进制日志文件呢?
我们又是如何知道我们现在正在使用哪个二进制日志文件呢:(通过show master status;命令)
我们可以通过show binary logs 查看所有二进制日志文件的大小
我们又是如何删除所有的二进制日志文件呢:(使用reset master)
如何手动清除二进制日志文件呢:(purge binary log 命令)
参考文档:(13条消息) MySql自动清除binary logs日志_purge binary logs_hanchao5272的博客-CSDN博客
查看二进制日志:
我们可以使用mysqlbinlog命令:
参考文档:MySQL 数据库之Binlog日志使用总结 - 散尽浮华 - 博客园 (cnblogs.com)
MySQL的binlog日志 - 马丁传奇 - 博客园 (cnblogs.com)
二进制日志文件的格式
二进制日志的格式:
1、row level
2、statement level
3、mixed level
Mysql日志: Mysql日志是什么,有什么用? MySQL日志是MySQL数据库中记录各种事件和操作的文件(应用程序把工作过程中的事情记录下来保存到文件中,保存下来的东西就是日志)。它包括多种不同类型的日志文件,如二进制日志、错误日志、慢查询日志等。这些日志文件可以用于诊断和解决问题,例如查找错误、分析数据库性能、恢复数据等。
可以帮助我们了解程序是否正常运行,用来排错,数据分析等
Exam5 ListView组件简单应用 ListView组件简单应用 Exam5 ListView组件简单应用目的实验内容及实验步骤采用SimpleAdapter自定义Adapter运行及结果:实验总结 目的 掌握常用的UI布局及组件;
掌握使用Intent启动Activity的方法
掌握ListView组件的简单应用
实验内容及实验步骤 采用ListView+数据适配器实现下图所示的美食图文列表功能,需求如下:
1、采用图文列表界面,每行显示一个美食信息,包括图片、名称、描述三项信息,名称及描述文字采用合适的字号及颜色。
2、点击列表中的一行,打开美食详情页面显示该行的美食信息详情
3、分别使用SimpleAdapter及自定义Adapter实现
采用SimpleAdapter MainActivity.java**
package com.example.test; import androidx.appcompat.app.AppCompatActivity; import android.content.Intent; import android.media.Image; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.AdapterView; import android.widget.ListView; import android.widget.SimpleAdapter; import android.widget.Toast; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; public class MainActivity extends AppCompatActivity { List<Map<String,Object>> list; ListView listviewFoods; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); listviewFoods = findViewById(R.id.listViewFoods); final String[] name = new String[]{"
拓扑图:
本实验使用动态主机配置协议(DHCP),生成树协议(STP),动态路由协议(OSPF),双机热备份协议(VRRP),网络地址转换协议(NAT),防火墙的配置,无线AC,AP 的配置,VLAN划分,IP地址划分协议技术完成校园网实验拓扑。
core-sw1:
<Huawei>sys [Huawei]sys core-sw1(修改核心交换机名称) [core-sw1]undo info en(关闭信息提示) Info: Information center is disabled. [core-sw1]vlan batch 5 7 10 20 30 40 50 60 100 to 101(创建vlan) [sw1]int Vlan 10(进入vlan10) [core-sw1-Vlanif10]ip ad 192.168.10.254 24(配置vlan10 IP地址) [core-sw1-Vlanif10]vrrp vrid 10 virtual-ip 192.168.10.252(配置VRRP协议的虚拟IP地址) [core-sw1-Vlanif10]vrrp vrid 10 priority 120(配置优先级,缺省值为100越大越优先) [core-sw1-Vlanif10]vrrp vrid 10 track interface GigabitEthernet 0/0/1(创建监视端口实现快速主备切换) [core-sw1-Vlanif10]vrrp vrid 10 track interface GigabitEthernet 0/0/2(创建监视端口实现快速主备切换) [core-sw1-Vlanif10]q(退出) [core-sw1]int Vlanif 20(进入vlan20) [core-sw1-Vlanif20]ip address 192.168.20.254 24(配置vlan20 IP地址) [core-sw1-Vlanif20]vrrp vrid 20 virtual-ip 192.
概述 索引是一种用于快速查询和检索数据的数据结构。常见的索引结构有: B 树, B+树和 Hash。
索引的底层数据结构 Hash表 & B+树 哈希表是键值对的集合,通过键(key)即可快速取出对应的值(value),因此哈希表可以快速检索数据(接近 O(1))。
为何能够通过 key 快速取出 value呢? 原因在于 哈希算法(也叫散列算法)。通过哈希算法,我们可以快速找到 value 对应的 index,找到了 index 也就找到了对应的 value。
hash = hashfunc(key) index = hash % array_size 但是,哈希算法有个 Hash 冲突 问题,也就是说多个不同的 key 最后得到的 index 相同。通常情况下,我们常用的解决办法是 链地址法。链地址法就是将哈希冲突数据存放在链表中。就比如 JDK1.8 之前 HashMap 就是通过链地址法来解决哈希冲突的。不过,JDK1.8 以后HashMap为了减少链表过长的时候搜索时间过长引入了红黑树。 为了减少 Hash 冲突的发生,一个好的哈希函数应该“均匀地”将数据分布在整个可能的哈希值集合中。
既然哈希表这么快,为什么MySQL 没有使用其作为索引的数据结构呢?
Hash 冲突问题 :我们上面也提到过Hash 冲突了,不过对于数据库来说这还不算最大的缺点。Hash 索引不支持顺序和范围查询(Hash 索引不支持顺序和范围查询是它最大的缺点: 假如我们要对表中的数据进行排序或者进行范围查询,那 Hash 索引可就不行了。 B 树& B+树 B 树也称 B-树,全称为 多路平衡查找树 ,B+ 树是 B 树的一种变体。
ppt预览方法 下载pptx 预览文件https://pptx.js.org/pages/demo3.html
将对应的js 引入到文件里,我使用的是umi + react ,
文件格式是class 类型的,如果是hook可以使用useScript 的格式进行引入,
文件存放在umi 的pulic 文件夹下面(没有的话自己建一个),引入时前缀自动为/
const urlList = [ '/js/jquery-1.11.3.min.js', '/js/jszip.min.js', '/js/filereader.js', '/js/d3.min.js', '/js/divs2slides.min.js', '/js/nv.d3.min.js', '/js/pptxjs.js', ] const scriptList: any = [] for (let index = 0; index < urlList.length; index++) { const url = urlList[index]; const script = document.createElement('script'); script.async = false; scriptList.push(script); script.src = url; document.body.appendChild(script); } this.setState({ scriptList: scriptList }, () => { this.fetchPPTX(this.props.filePath); }) 从url 中获取pptx 文件
该脚本使用了Python的socket模块来进行网络连接和通信,并利用concurrent.futures模块实现了并发扫描。它接受三个命令行参数:目标IP地址、起始端口和结束端口。如果没有提供这些参数,脚本将打印出用法信息并退出。脚本使用多线程并发扫描指定的端口范围,如果端口开放,则打印出端口号、协议类型和“开放”字样。
扫描计时,端口开放的协议
执行效果: 脚本 import sys import socket import concurrent.futures from datetime import datetime # 获取命令行参数 if len(sys.argv) == 2: target = sys.argv[1] start_port = 1 end_port = 65535 elif len(sys.argv) == 4: target = sys.argv[1] start_port = int(sys.argv[2]) end_port = int(sys.argv[3]) else: print("用法: python3 portscanner.py <目标IP> [<起始端口> <结束端口>]") sys.exit() # 获取当前时间 time_start = datetime.now() # 打印扫描开始信息 print("-" * 50) print("开始扫描: " + target) print("扫描时间: " + str(time_start)) print("-" * 50) # 获取目标主机的IP地址 try: target_ip = socket.
Spring Boot 的自动装配(Auto-configuration)是 Spring Boot 的核心特性之一,它使得开发者无需手动配置大量的 Spring 组件,而是通过在 classpath 中寻找 Spring Boot Starter 模块和条件化配置,自动完成配置和组装。这样,开发者就可以将精力集中在业务逻辑上,而不是在繁琐的配置上。
1.启动类:main方法 这是一个Spring Boot项目中的启动类,自动装配也要从这开始
@SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } } 我们重点关注这个注解和run方法
@SpringBootApplication
SpringApplication.run(Application.class, args);
2.核心注解 2.1@SpringBootApplication 源码就不贴上来了
@SpringBootApplication相当于 @SpringBootConfiguration + @EnableAutoConfiguration + @ComponentScan
2.2@SpringBootConfiguration @SpringBootConfiguration 是继承自Spring的 @Configuration 注解,@SpringBootConfiguration 作用相当于 @Configuration。
spring 3.0中增加了@Configuration,@Bean。可基于JavaConfig形式对 Spring 容器中的bean进行更直观的配置。SpringBoot推荐使用基于JavaConfig的配置形式。
在spring中基于xml方式配置:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd" default-lazy-init="true"> <bean id="mockService" class="..MockServiceImpl"> .
一、B树 1.1 B树的定义 B树是一颗多路平衡查找树,它可以用于高效地存储和查找大量的数据。我们描述一颗B树时需要指定它的阶数,阶数表示了一个结点最多有多少个孩子结点,一般用字母m表示阶数。当m取2时,就是我们常见的二叉搜索树。
一颗m阶的B树定义如下:
每个结点最多有m-1个关键字。根结点最少可以只有1个关键字。非根结点至少有Math.ceil(m/2)-1个关键字。每个结点中的关键字都按照从小到大的顺序排列,每个关键字的左子树中的所有关键字都小于它,而右子树中的所有关键字都大于它。所有叶子结点都位于同一层,或者说根结点到每个叶子结点的长度都相同。 上图是一颗阶数为4的B树。在实际应用中的B树的阶数m都非常大(通常大于100),所以即使存储大量的数据,B树的高度仍然比较小。每个结点中存储了关键字(key)和关键字对应的数据(data),以及孩子结点的指针。我们将一个key和其对应的data称为一个记录。但为了方便描述,除非特别说明,后续文中就用key来代替(key, value)键值对这个整体。在数据库中我们将B树(和B+树)作为索引结构,可以加快查询速速,此时B树中的key就表示键,而data表示了这个键对应的条目在硬盘上的逻辑地址。
1.2 B树的插入操作 插入操作是指插入一条记录,即(key, value)的键值对。如果B树中已存在需要插入的键值对,则用需要插入的value替换旧的value。若B树不存在这个key,则一定是在叶子结点中进行插入操作。
根据要插入的key的值,找到叶子结点并插入。判断当前结点key的个数是否小于等于m-1,若满足则结束,否则进行第3步。以结点中间的key为中心分裂成左右两部分,然后将这个中间的key插入到父结点中,这个key的左子树指向分裂后的左半部分,这个key的右子树指向分裂后的右半部分,然后将当前结点指向父结点,继续进行第3步。 下面以5阶B树为例,介绍B树的插入操作,在5阶B树中,结点最多有4个key,最少有2个key:
在空树中插入39
此时根节点只有一个key,根节点也是叶子结点继续插入22,97和41
根节点有4个key继续插入53
插入后超过了最大允许的关键字个数4,所以以key值41为中心进行分裂,结果如下图所示,分裂后当前结点指向父结点,满足B树条件,插入操作结束。当阶树m为偶数时,需要分裂时就不存在排序恰好在中间的key,那么我们选择中间位置的前一个key或者中间位置的后一个key为中心进行分裂均可
依次插入13,21,40,同样会造成分裂,结果如下所示
依次插入30,27,33,此时分裂一次,再次插入36,35,34,此时又会分裂一次,最后插入24,29,结果如下图所示:
插入key值为26的记录,插入后的结果如下图所示
此时当前结点需要以27为中心分裂,并向父结点进位27,然后当前结点指向父结点,结果如下所示:
进位后导致当前结点(即根结点)也需要分裂,分裂的结果如下图所示:
分裂后当前结点指向新的根,此时无需调整最后再依次插入key为17,28,29,31,32的记录,结果如下图所示:
在实现B树的代码中,为了使代码编写更加容易,我们可以将结点中存储记录的数组长度定义为m而非m-1,这样方便底层的结点由于分裂向上层插入一个记录时,上层有多余的位置存储这个记录。同时,每个结点还可以存储它的父结点的引用,这样就不必编写递归程序。
一般来说,对于确定的m和确定类型的记录,结点大小是固定的,无论它实际存储了多少个记录。但是分配固定结点大小的方法会存在浪费的情况,比如key为28,29所在的结点,还有2个key的位置没有使用,但是已经不可能继续在插入任何值了,因为这个结点的前序key是27,后继key是30,所有整数值都用完了。所以如果记录先按key的大小排好序,再插入到B树中,结点的使用率就会很低,最差情况下使用率仅为50%。
1.3 B树的删除操作 删除操作是指,根据key删除记录,如果B树中的记录中不存对应key的记录,则删除失败。
如果当前需要删除的key位于非叶子结点上,则用后继key(这里的后继key均指后继记录的意思)覆盖要删除的key,然后在后继key所在的子支中删除该后继key。此时后继key一定位于叶子结点上,这个过程和二叉搜索树删除结点的方式类似。删除这个记录后执行第2步该结点key个数大于等于Math.ceil(m/2)-1,结束删除操作,否则执行第3步。如果兄弟结点key个数大于Math.ceil(m/2)-1,则父结点中的key下移到该结点,兄弟结点中的一个key上移,删除操作结束。否则,将父结点中的key下移与当前结点及它的兄弟结点中的key合并,形成一个新的结点。原父结点中的key的两个孩子指针就变成了一个孩子指针,指向这个新结点。然后当前结点的指针指向父结点,重复上第2步。 有些结点它可能即有左兄弟,又有右兄弟,那么我们任意选择一个兄弟结点进行操作即可。
下面以5阶B树为例,介绍B树的删除操作,5阶B树中,结点最多有4个key,最少有两个key
原始状态
在上面的树种删除21,删除后结点的关键字个数仍然大于等于2,所以删除结束
在上述情况下接着删除27。从上图可以得知27位于非叶子结点种,所以用27的后继替换它。从图中可以看出,27的后继为28,我们用28替换27,然后在28(原27)的右孩子结点中删除28。删除后的结果如下所示:
删除后发现,当前叶子结点的记录的个数小于2,而它的兄弟结点中有3个记录(当前结点还有一个右兄弟,选择右兄弟就会出现合并结点的情况,无论选哪一个都行,只是最后B树的形态不一样而已),我们可以从兄弟结点中接取一个key。所以父结点中的28下移,兄弟结点中的26上移,删除结束。结果如下所示:
在上述情况下继续删除32,结果如下图:
当删除后,当前结点中只有一个key,而兄弟结点中也仅有两个key。所以只能让父结点中的30下移和这两个孩子结点中的key合并,成为一个新的结点,当前结点的指针指向父结点。结果如下图所示:
当前key的个数满足条件,故删除结束上述情况下,我们继续删除key为40的记录,删除后结果如下图所示:
同理,当前结点的记录数小于2,兄弟结点中没有多余key,所以父结点中的key下移,和兄弟(这里我们选择左兄弟,选择右兄弟也可以)结点合并,合并后的指向当前结点的指针就指向了父结点。
同理,对于当前结点而言只能继续合并了,最后结果如下所示。
合并后结点当前结点满足条件,删除结束。 二、B+树 2.1 定义 各种资料上B+树的定义各有不同,一种定义方式是关键字个数和孩子结点个数相同。这里我们采取维基百科上所定义的方式,即关键字个数比孩子结点个数小1,这种方式是和B树基本等价的。下图就是一颗阶数为4的B+树:
除此之外B+树还有以下的要求:
B+树包含2种类型的结点:内部结点(也称索引结点)和叶子结点。根结点本身即可以是内部结点,也可以是叶子结点。根结点的关键字个数最少可以只有1个。B+树与B树最大的不同是内部结点不保存数据,只用于索引,所有数据(或者说记录)都保存在叶子结点中。m阶B+树表示了内部结点最多有m-1个关键字(或者说内部结点最多有m个子树),阶数m同时限制了叶子结点最多存储m-1个记录。内部结点中的key都按照从小到大的顺序排列,对于内部结点中的一个key,左树中的所有key都小于它,右子树中的key都大于等于它。叶子结点中的记录也按照key的大小排列。每个叶子结点都存有相邻叶子结点的指针,叶子结点本身依关键字的大小自小而大顺序链接。 2.2 插入操作 若为空树,创建一个叶子结点,然后将记录插入其中,此时这个叶子结点也是根结点,插入操作结束。针对叶子类型结点:根据key值找到叶子结点,向这个叶子结点插入记录。插入后,若当前结点key的个数小于等于m-1,则插入结束。否则将这个叶子结点分裂成左右两个叶子结点,左叶子结点包含前m/2个记录,右结点包含剩下的记录,将第m/2+1个记录的key进位到父结点中(父结点一定是索引类型结点),进位到父结点的key左孩子指针向左结点,右孩子指针向右结点。将当前结点的指针指向父结点,然后执行第3步。针对索引类型结点:若当前结点key的个数小于等于m-1,则插入结束。否则,将这个索引类型结点分裂成两个索引结点,左索引结点包含前(m-1)/2个key,右结点包含m-(m-1)/2个key,将第m/2个key进位到父结点中,进位到父结点的key左孩子指向左结点, 进位到父结点的key右孩子指向右结点。将当前结点的指针指向父结点,然后重复第3步。 下面是一颗5阶B树的插入过程,5阶B树的结点最少2个key,最多4个key
空树中插入5
依次插入8,10,15
插入16
插入16后超过了关键字的个数限制,所以要进行分裂。在叶子结点分裂时,分裂出来的左结点2个记录,右边3个记录,中间key成为索引结点中的key,分裂后当前结点指向了父结点(根结点)。结果如下图所示。
当然我们还有另一种分裂方式,给左结点3个记录,右结点2个记录,此时索引结点中的key就变为15。插入17
插入18,插入后如下图所示:
当前结点的关键字个数大于5,进行分裂。分裂成两个结点,左结点2个记录,右结点3个记录,关键字16进位到父结点(索引类型)中,将当前结点的指针指向父结点。
当前结点的关键字个数满足条件,插入结束。插入若干数据后:
继续在上图中插入7,结果如下所示:
当前结点的关键字个数超过4,需要分裂。左结点2个记录,右结点3个记录。分裂后关键字7进入到父结点中,将当前结点的指针指向父结点,结果如下图所示。
当前结点的关键字个数满足条件,插入结束。 2.3 删除操作 如果叶子结点中没有相应的key,则删除失败。否则执行下面的步骤
删除叶子结点中对应的key。删除后若结点的key的个数大于等于Math.ceil(m-1)/2 – 1,删除操作结束,否则执行第2步。若兄弟结点key有富余(大于Math.
作者:阿秀
校招八股文学习网站:https://interviewguide.cn
这是阿秀的第「267」篇原创
小伙伴们大家好,我是阿秀。
不知道什么时候八股文这个说法开始流传出去了,以前是没有这个说法的,我印象中就是近三五年流传开来的。
现在无论是找实习,校招还是社招, 感觉计算机相关的求职都离不开八股文了,操作系统、计算机网络、数据库MySQL、Redis等面试问题。
很多人也把我当初的学习笔记当做八股文直接去背,但很多人都找我说感觉背起来太吃力了,边看边忘,看完了也忘完了。。
我想说,你不吃力就是怪事了,肯定吃力。
因为我当初是学的,一点一点慢慢学过来的,然后把自己学的东西记下来,后面又整理成笔记的。
然后现在你不学,你直接背,妄图构建一个空中楼阁,当然会吃力。。。。
问的人多了,我就想着说一下应该怎么看八股文吧,不只是我的学习笔记,还有很多你们从网上看到的各种八股文,其实都可以按照下面这个思维去学。
我给的建议是自己要要做适当做笔记做总结的,八股文资料很多,但大家还是要做一份自己的八股文,最好就是那种一问一答的形式,就跟我当初做的笔记相似,这样才方便面试前快速复习。
你可以先看一个问题,然后把答案捂住,自己试着自己去回答,试着用自己的话去回答一遍,然后再看答案,比对一下差多少,哪里差?哪里没答好,然后把自己的答案融合八股文上的答案做笔记记下来。
在你做笔记的时候,不要直接去复制粘贴八股上的回答,而是以自己的语言写出回答,就当这是面试官问你这个问题的情景,你要如何回答一样。
只有这样你才记得才牢固,很多学弟学妹就是也是整理自己的八股,结果都是和字节原样复制粘贴,那样当然记不住。
结果面试问到自己整理过的原题都答不好,更不要说那些自己没整理过的问题了。
像这种机试比较典型的整理自己的八股时没有思考,直接死记硬背了,这样是不行的。
除了这种形式去记忆八股,你也可以从面经或者自己的面试经历中总结出来自己的方式,以前就有一个学弟跟我分享过他看八股文的形式,我分享一下。
八股的理解及使用都是从日常实习的经验里总结出来的。
第一步,遇到一个问题先想自己应该怎么回答回答,答完后在脑海里拓展相关知识点;
第二步,找到相关知识点进行记忆和理解;
第三步,重复上述过程。
这里也说一下,我最近在学习圈里发布了《阿秀的校招笔记》第五版,此次还额外提供了亮白版和暗黑版两种版本。
这并不是大家在网上随便就能找到的八股文,而是我从校园->职场多年计算机自学过程的记录以及学弟学妹们计算机校招&秋招经验总结文章的汇总。
毕竟也没有那个八股文合集能在 github 仓库达到 4.1K+star,也没有哪个八股文会连续两年内一直持续做勘误和更新。
截止目前为止已经提交过 539 个commit,累计勘误超过 120 个。
539 个commit 持续做勘误 这是以前的一些勘误和更新记录。
V1.0 - 2021.02.27
增加C/C++部分 217 道面试题
V1 - 2021.03.08
增加操作系统部分 62 道面试题
增加计算机网络部分 33 道面试题
V2 - 2021.03.23
粉丝建议增加页码,已采纳
再次增加计算机网络部分 33 道面试题,计网部分目前共计 66 道题
V3 - 2021.04.01
开放编辑权限,目前PDF可以复制、编辑、注释(盗版太严重,但很多粉丝都来私信说希望开放编辑权限,也好做标记,安排上!)
增加计算机网络部分 34 道面试题,计网部分目前共计 100 道题
目录
1、解压与安装mysql
2、配置mysql,修改密码并使其可以远程访问
redHat版本 7.6 ,mysql版本 8.0.26,mysql下载地址
1、解压与安装mysql # 下载tar包 wget -i -c http://dev.mysql.com/get/mysql-8.0.26-1.el7.x86_64.rpm-bundle.tar # 解压缩文件 tar -xvf mysql-8.0.26-1.el7.x86_64.rpm-bundle.tar # 按照以下顺序安装mysql组件 rpm -ivh mysql-community-common-8.0.26-1.el7.x86_64.rpm rpm -ivh mysql-community-client-plugins-8.0.26-1.el7.x86_64.rpm # 卸载原有的mysql-libs yum remove mysql-libs # 继续按顺序安装mysql组件 rpm -ivh mysql-community-libs-8.0.26-1.el7.x86_64.rpm rpm -ivh mysql-community-client-8.0.26-1.el7.x86_64.rpm rpm -ivh mysql-community-server-8.0.26-1.el7.x86_64.rpm # 查看mysql服务状态 service mysqld status # 开启服务 service mysqld start 2、配置mysql,修改密码并使其可以远程访问 # 修改my.cnf文件,在文件的最后一行添加“skip-grant-tables” vim /etc/my.cnf # 重启mysql服务 service mysqld restart # 本地登录mysql,不用输密码直接回车进入 mysql -u root -p # 将当前 user 和 privilige 表中的用户信息/权限设置从 mysql 库中提取到内存里 flush privileges; # 配置root密码,比如密码“Abc#2021” ALTER USER 'root'@'localhost' IDENTIFIED BY 'Abc#2021'; # 修改my.
Spring Boot的自动装配是它的一大特点,可以大大提高开发效率,减少重复性代码的编写。本文将详细讲解Spring Boot如何实现自动装配。
1. 什么是自动装配 在传统的Spring框架中,我们需要手动配置和管理Bean的依赖关系,但在Spring Boot中,大量的配置可以自动完成。这是因为Spring Boot中引入了自动装配的概念。自动装配指的是根据应用程序的依赖关系自动配置Spring Bean,而无需手动配置。
2. 自动装配的原理 Spring Boot的自动装配是通过条件注解实现的。Spring Boot会在classpath中寻找符合条件的类,并根据这些类自动配置Bean。这些条件注解有:
@ConditionalOnClass:类路径下存在指定的类时才会生效。@ConditionalOnMissingBean:容器中不存在指定Bean时才会生效。@ConditionalOnProperty:指定的属性是否有指定的值。 3. 自动装配的步骤 自动装配的步骤如下:
Spring Boot启动时会加载**META-INF/spring.factories**文件,该文件中配置了所有自动装配的类。根据条件注解和实现类,Spring Boot筛选出符合条件的类。Spring Boot会根据类中的配置信息来自动配置Bean。配置完成后,Spring Boot会将所有的Bean注册到容器中,以供其他组件使用。 4. 自定义自动配置 除了使用Spring Boot提供的自动配置之外,我们还可以自定义自己的自动配置。自定义自动配置需要完成以下步骤:
创建一个配置类,并在类上添加@Configuration和@EnableConfigurationProperties注解。在配置类中使用@ConditionalOnClass、@ConditionalOnMissingBean等条件注解,指定自动配置的条件。使用@ConfigurationProperties注解创建配置属性类,并在配置类中注入该属性类。编写需要自动配置的Bean。使用**@Bean注解将Bean注册到容器中。 5.代码案例 为了更深刻的理解上述概念,下面我们用一个完整的代码案例来说明一下:
首先,让我们创建一个简单的Maven项目,并添加Spring Boot依赖。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <version>2.6.2</version> </dependency> 接下来,我们需要定义一个自动配置类来进行自动装配。自动配置类通常包含在一个单独的模块中,并在项目中使用Maven依赖进行引用。
@Configuration @ConditionalOnClass(UserService.class) @EnableConfigurationProperties(UserProperties.class) public class UserAutoConfiguration { @Autowired private UserProperties userProperties; @Bean @ConditionalOnMissingBean public UserService userService() { return new UserService(userProperties); } } 上面的代码中,@Configuration注解表示这是一个配置类。@ConditionalOnClass注解表示只有在classpath中存在UserService类时才进行自动配置。@EnableConfigurationProperties注解用于启用自动配置属性,表示需要配置UserProperties类。
接下来,我们需要定义UserProperties类来保存我们的配置属性。
@ConfigurationProperties(prefix = "user") public class UserProperties { private String username; private String password; public String getUsername() { return username; } public void setUsername(String username) { this.
表结构
CREATE TABLE `t_score` (
`id` varchar(100) NOT NULL,
`course` varchar(100) NOT NULL,
`score` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`,`course`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
表数据
INSERT INTO `t_score` VALUES ('0001', '001', '30');
INSERT INTO `t_score` VALUES ('0001', '002', '88');
INSERT INTO `t_score` VALUES ('0001', '003', '45');
INSERT INTO `t_score` VALUES ('0002', '001', '57');
INSERT INTO `t_score` VALUES ('0002', '003', '33');
INSERT INTO `t_score` VALUES ('0003', '002', '87');
1. 二分查找算法(非递归) 1.1 二分查找算法(非递归)介绍 1) 前面《数据结构7 -查找》中的二分查找算法,是使用递归的方式,下面我们讲解二分查找算法的非递归方式。
2) 二分查找法只适用于从有序的数列中进行查找(比如数字和字母等),将数列排序后再进行查找。
3) 二分查找法的运行时间为对数时间O(㏒₂n) ,即查找到需要的目标位置最多只需要㏒₂n步。假设从[0,99]的队列(100个数,即n=100)中寻到目标数30,则需要查找步数为㏒₂100 , 即最多需要查找7次( 2^6 < 100 < 2^7)。
1.2 二分查找算法(非递归)代码实现 数组 {1,3, 8, 10, 11, 67, 100}, 编程实现二分查找, 要求使用非递归的方式完成.
public class BinarySearchNoRecur { public static void main(String[] args) { int[] arr = {1,3, 8, 10, 11, 67, 100}; System.out.println(binarySearch(arr,8)); //2 } //二分查找的非递归实现 /** * @param arr 待查找的数组,arr是升序排列 * @param target 需要查找的数 * @return 返回对应下标,-1 表示没有找到 * */ public static int binarySearch(int[] arr, int target){ int left = 0; int right = arr.
1.如果在安装路径中没有配置文件则需要手动创建一个,文件名:my.ini,添加如下内容:
[mysql] default-character-set=utf8 [mysqld] port = 3306 #安装路径 basedir=D:\mysql\mysqlServer 5.7 #设置数据库存放数据的目录,不需要自己手动创建 datadir=D:\mysql\mysqlServer 5.7\data max_connections=200 character-set-server=utf8 default-storage-engine=INNODB 在mysql5.7以上是没有data目录,需要手动初始化服务
解决办法:进入mysql的bin目录,执行mysqld --initialize-insecure
配置后手动启动mysql服务或命令行启动net start mysql 命令启动服务则发现可以启动成功!
目录标题 消息丢失1. 生产者生产消息到RabbitMQ Server 消息丢失场景1. 网络问题2. 代码层面,配置层面,考虑不全导致消息丢失解决方案:开启confirm模式 2. 队列本身可能丢失消息1. 消息未完全持久化,当机器重启后,消息会全部丢失,甚至Queue也不见了解决方案:交换机持久化:在声明交换器时将 durable 设为 true。队列持久化:在声明队列的时候把 durable 参数设置为true。消息持久化: 2. 单节点模式问题,节点挂了,消息只存在当前节点。硬盘坏了,那消息真的就无法恢复了3. 默认的集群模式,消息只会存在与当前节点中,并不会同步到其他节点,其他节点也仅只会同步该节点的队列结构工作原理 解决方案:镜像部署,消息会同步到其他节点上,可以设置同步的节点个数,但吞吐量会下降。工作原理GM总结 3. 消费端可能丢失消息消费端采用自动ack机制,还没有处理完毕,消费端宕机。解决方案:改为手动ack,当消息正确处理完成后,再通知mq。消费端处理消息异常后,回传nack,这样mq会把这条消息投递到另外一个消费端上。消息应答的方法demo 消息丢失 消息从生产到消费,要经历三个阶段,分别是生产、队列转发与消费,每个环节都可能丢失消息。
以下以RabbitMQ为例,来说明各个阶段会产生的问题以及解决方式。在说明之前,先回顾一下RabbitMQ的一个基本架构图
1. 生产者生产消息到RabbitMQ Server 消息丢失场景 1. 网络问题 外界环境问题导致:发生网络丢包、网络故障等造成RabbitMQ Server端收不到消息,因为生产环境的网络是很复杂的,网络抖动,丢包现象很常见,下面会讲到针对这个问题是如何解决的。
2. 代码层面,配置层面,考虑不全导致消息丢失 一般情况下,生产者使用Confirm模式投递消息,如果方案不够严谨,比如RabbitMQ Server 接收消息失败后会发送nack消息通知生产者,生产者监听消息失败或者没做任何事情,消息存在丢失风险;
生产者发送消息到exchange后,发送的路由和queue没有绑定,消息会存在丢失情况,下面会讲到具体的例子,保证意外情况的发生,即使发生,也在可控范围内。
解决方案:开启confirm模式 首先生产者通过调用channel.confirmSelect方法将信道设置为confirm模式,一旦信道进入confirm模式,所有在该信道上面发布的消息都会被指派一个唯一的ID(从1开始),一旦消息被投递到所有匹配的队列之后,RabbitMQ就会发送一个确认(Basic.Ack)给生产者(包含消息的唯一deliveryTag和multiple参数)。
其实Confirm模式有三种方式实现:
串行confirm模式:producer每发送一条消息后,调用waitForConfirms()方法,等待broker端confirm,如果服务器端返回false或者在超时时间内未返回,客户端进行消息重传。
for(int i = 0;i<50;i++){ channel.basicPublish( exchange, routingKey, mandatory, immediate, messageProperties, message.getContent() ); if (channel.waitForConfirms()) { System.out.println("发送成功"); } else { //发送失败这里可进行消息重新投递的逻辑 System.out.println("发送失败"); } } 批量confirm模式:producer每发送一批消息后,调用waitForConfirms()方法,等待broker端confirm。
问题:一旦出现confirm返回false或者超时的情况时,客户端需要将这一批次的消息全部重发,这会带来明显的重复消息数量,并且当消息经常丢失时,批量confirm性能应该是不升反降的。
for(int i = 0;i<50;i++){ channel.
文章目录 一、安装配置 node.js1.1 下载安装1.2 配置环境变量1.3 修改模块下载位置1.4 设置淘宝镜像 二、创建启动 Vue三、开发环境 VSCode3.1 开发插件3.2 Vue 项目结构 提示:以下是本篇文章正文内容,前端系列学习将会持续更新 一、安装配置 node.js 1.1 下载安装 官网:https://nodejs.org/en
1.2 配置环境变量 配置环境变量:在系统变量的 Path 中添加node.js安装路径 D:\node.js\。
安装完成后,测试下 nodejs 和 npm 是否安装成功!
1.3 修改模块下载位置 修改模块下载位置:npm 全局下载模块的保存位置。
①先查看 npm 默认存放位置:
npm get prefix C:\Users\14156\AppData\Roaming\npm npm get cache C:\Users\14156\AppData\Local\npm-cache ②在 nodejs 安装目录下,创建 “node_global” 和 “node_cache” 两个文件夹
③修改默认文件夹
# 设置全局模块的安装路径到 “node_global” 文件夹, npm config set prefix "D:\node.js\node_global" # 设置缓存到 “node_cache” 文件夹 npm config set cache "
声明: 1. 本文为我的个人复习总结, 并非那种从零基础开始普及知识 内容详细全面, 言辞官方的文章
2. 由于是个人总结, 所以用最精简的话语来写文章
3. 若有错误不当之处, 请指出
软硬链接 软链接(快捷方式): 删除原文件时, 软链接则失效
硬链接: 删除硬文件时, 硬链接不会失效
硬链接是多个目录项中的「索引节点」指向同一个 inode,但是 inode 是不可能跨越文件系统的,每个文件系统都有各自的 inode 数据结构和列表,所以硬链接不可跨文件系统
由于多个目录项都是指向一个 inode, 那么只有删除原文件以及所有硬链接时, 系统才会彻底删除该文件
IO类型 缓冲IO 先访问标准库的缓存, 再访问文件
非缓冲IO 不访问标准库的缓存, 直接去访问文件
直接IO 直接访问磁盘, 不会发生内核态与用户态间的数据拷贝
非直接IO 不直接访问磁盘, 会发生内核态与用户态间的数据拷贝
读操作时, 数据从内核态拷贝到用户态
写操作时, 数据从用户态拷贝到内核态
五种IO模型 BIO(同步阻塞)
一个线程只能连接一个Socket连接, 处理一个Socket的请求
accept时阻塞等待有客户端连接, read时阻塞等待有数据输入
NIO(同步非阻塞)
一个线程可以连接多个Socket连接, 处理多个Socket的请求; 更好的利用了线程, 避免因一个文件阻塞而导致整个线程啥也不干了
accept时得不到连接 和 read时得不到数据就算了, 直接返回个默认值负数, 不会在那一直阻塞着等待数据输入
IO多路复用(同步(半同步), IO多路复用器)
别名: 事件驱动IO
一个线程可以连接多个Socket连接, 处理多个Socket的请求; 更好的利用了线程, 避免因一个文件阻塞而导致整个线程啥也不干了
第1关:画图接口 对传入的x,y两个数组做折线图,x对应x轴,y对应y轴。并保存到Task1/image1/T2.png,具体要求如下:
折线图的figsize为(10, 10);
文件名为Task1/image1/T2.png。
import matplotlib
matplotlib.use(‘Agg’)
import matplotlib.pyplot as plt
def student(x,y):
# ********** Begin *********#
plt.figure(figsize=(10,10))
plt.plot(x,y)
plt.savefig(‘Task1/image1/T2.png’)
# ********** End **********# 第2关:线形图 根据输入数据input_data,input_data1绘制两条折线图。依次为两组数据设置颜色样式为–g,:r;设置图例为L1,L2,具体要求如下:
折线图的figsize为(10, 10);
图形保存到Task2/img/T1.png。
def student(input_data,input_data1):
# ********* Begin *********# plt.figure(figsize=(10,10)) plt.plot(input_data,'--g',label="L1") plt.plot(input_data1,':r',label="L2") plt.legend() plt.savefig("Task2/img/T1.png") # ********* End *********# 第3关:散点图 ,根据输入的三组数据绘制三组不同参数的散点图,具体要求如下:
第一组数据参数设置标记大小为area,透明度为0.5;
第二组数据参数设置标记大小为area,标记颜色为绿色,透明度为0.6;
第三组数据参数设置标记大小为area,标记颜色为area,标记样式为v,透明度为0.7;
图形的figsize为(10, 10);
图形保存到Task3/img/T1.png。
def student(x,y,x2,y2,x3,y3,area):
‘’’
根据输入的三组数据绘制三组不同参数的散点图
:param x,y: 第一组数据,类型为array
:param x2,y2: 第二组数据,类型为array
:param x3,y3: 第三组数据,类型为array
:param area: 标记大小参数的值,类型为array
请使用指针的方法编写程序,程序的功能是从键盘输入一个字符串(字符串长度小于100),删除其中的字母a后输出。例如,输入字符串abcaca,输出bcc。
输入样例: abcaca 输出样例: bcc 代码长度限制
16 KB
时间限制
400 ms
内存限制
64 MB
#include<stdio.h> #include<string.h> int main(){ char str[102];//开辟一个比指定字符串size更大的容量; gets(str); getchar();//缓冲不必要的多余字符避免乱码; int i=0,j=0; int count=0;//统计所输入的字符串的长度; for(i=0;i<strlen(str);i++){ if(str[i]!='a'){ count++; } } char ptr[count];//再新建一个size大小和所输入字符串一样大的数组ptr: for(i=0;i<strlen(str);i++){ if(str[i]!='a'){ ptr[j]=str[i];//将原字符串中不为'a'的变量赋值给ptr; j++; } } ptr[j++]='\0';//必须要有这一步否则后面会有乱码; puts(ptr); }
前言:
这里获取的是网卡的设备名称然后通过设备名称判断网卡类型,如无线网卡、有线网卡、虚拟网卡,其他网卡(如vpn客户端软件);使用如下代码打包后的程序可运行于win7、win10、win11系统;Python版本为3.8.9;因为工作中临时用到,就随手写的,所以代码规范没有太注意,看不懂的可以打断点分析,不过相信大家都能看的,确实非常简单,需要其他信息的在此基础上自行修改就行。
源码:
# -*- coding: utf-8 -*- ''' 需要的数据: Wmic Path Win32_NetworkAdapterConfiguration get IPAddress,Index Wmic Path Win32_NetworkAdapter get productname,Index 以上是获取当前活动网卡及网卡信息的两条命令!可用cmd或Powershell运行。 ''' import subprocess import re import os # ret, val = subprocess.getstatusoutput(r"Wmic Path Win32_NetworkAdapter get GUID,MACAddress,NetEnabled,productname,Index") ret, val = subprocess.getstatusoutput(r"Wmic Path Win32_NetworkAdapterConfiguration get Index") # print(ret) #print(val) list1=val.split("\n\n") # 每个一行中间相隔2个换行! list1.pop() # 去除最后一个无用的元素 #print(list1) ret1, val1 = subprocess.getstatusoutput(r"Wmic Path Win32_NetworkAdapterConfiguration get IPAddress") # print(ret) #print(val1) list2=val1.split("\n\n") # 每个一行中间相隔2个换行! list2.
本篇文章主要分享在树莓派虚拟机上安装Miniconda+Mediapipe+Pytorch+TensorFlow的简易版教程,使用的镜像为2020-02-12-rpd-x86-buster.iso,虚拟机软件为VMware16。
目录
一、准备工作
二、Miniconda的安装
三、Mediapipe的安装
四、Pytorch安装
五、TensorFlow安装
一、准备工作 1、首先查看系统情况,输入如下命令。
uname -a 我的虚拟机的结果如下图所示。
二、Miniconda的安装 1、下载网址我一般会从Miniconda官网或者清华的镜像站上下载,在第一部分中查看过系统是Linux-x86_64,所以我们也选择这个后缀的进行下载,同时Miniconda2中的初始Python版本是2,Miniconda3中的初始Python版本才是3,这点要注意。latest表示最新的。
在终端输入以下命令,下载安装包(二选一,镜像站上的肯定快一些)
wget https://mirrors.tuna.tsinghua.edu.cn/anaconda/miniconda/Miniconda3-latest-Linux-x86_64.sh wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86.sh 下载完成后如图所示
2、为该脚本在用户pi下赋予可执行权限,执行该脚本。
在终端输入如下命令,敲回车,如下图所示。
chmod +x /home/pi/Miniconda3-latest-Linux-x86_64.sh sh /home/pi/Miniconda3-latest-Linux-x86_64.sh 3、敲击回车,进入下一步,下图是敲击回车后的图片
4、按一下键盘上的"Q"键,跳到协议的末尾,如下图所示
5、是否接受以上的协议?输入yes,敲击回车,如下图所示
6、接下来输入的是Miniconda的安装路径,可以敲回车选择默认,也可以输入其他的路径。
7、最后你会发现报错了哈哈,报错为:/home/pi/miniconda3/conda.exe: not found
别急,我们慢慢来。先在终端输入如下命令,删除刚刚的安装留下的痕迹,接着我们安装一下lib64z1这个包。如果你的虚拟机是32位的话是要安装lib32z1这个包。
rm -rf miniconda3 sudo apt-get install lib64z1 -y 8、然后我们继续输入刚刚的命令,运行脚本,并重复上述步骤,重复的步骤只展示图片。
sh /home/pi/Miniconda3-latest-Linux-x86_64.sh 9、执行到输入安装路径这一步时,输入你想安装的路径,选择默认路径/home/pi/miniconda3的敲回车即可。敲完回车后如图所示。
10、输入yes后,结果如下图所示。
11、输入如下命令,查看conda是否安装成功。
conda -V 12、很遗憾,并没有安装成功,需要按照如下命令配置环境变量,直接复制到终端敲回车即可。注意这里的"PATH=……/bin"中省略号的内容应为你的安装路径。
cat << EOF >> ~/.bashrc export PATH=/home/pi/miniconda3/bin:$PATH EOF source ~/.bashrc 13、之后输入如下命令,成功显示结果,说明Miniconda安装成功,第二部分完成。关于conda的换源以及部分操作,可以查看这篇文章:Ubuntu18.04安装miniconda3及使用 - 知乎 (zhihu.com)
conda -V 14、解决Miniconda的问题,我参考了如下链接:
C语言的scanf实现输入 scanf作用scanf的规范点转换规范常见的输入错误实现整数的输入实现小数的输入实现字符输入字符串的输入scanf与printf的不同 scanf作用 scanf将键盘输入的字符串根据转换规范,转换成二进制表示的整数、浮点数、字符或
字符串等。
根据用户的输入 赋值给程序的数据
举个例子
#include <stdio.h> int main() { char c; short s; int n; long l; float f; double df; scanf("%hhd %hd %d %ld %f %lf", &c, &s, &n, &l, &f, &df); printf("%d %d %d %d %f %f\n", c, s, n, l, f, df); return 0; } 运行程序后,在键盘输入:“11 22 33 42 5.6 71.8”。scanf函数,将字符串"1 2 3 4 5.6 7.8",根据转换规范,
分别转换为各类二进制数据,并存储到变量中
scanf的规范点 scanf是一个变参函数。 scanf("%hhd %hd %d %ld %f %lf"
打开Xshell6,无法成功启动,报错:
解决方法:运行里面的绿化bat文件
在Go语言中,有各种运算符可用于执行不同类型的操作。以下是一些常见的Go语言运算符及其说明和示例:
下面是一个表格,归纳了常见的运算符类型和它们的说明:
运算符类型运算符说明算术运算符+相加两个操作数-相减两个操作数*相乘两个操作数/相除两个操作数%取模(取余数)赋值运算符=将右侧的值赋给左侧的变量+=将右侧的值加到左侧的变量上-=将右侧的值减去左侧的变量*=将右侧的值乘以左侧的变量/=将左侧的变量除以右侧的值关系运算符==判断两个操作数是否相等!=判断两个操作数是否不相等>判断左侧的操作数是否大于右侧的操作数<判断左侧的操作数是否小于右侧的操作数>=判断左侧的操作数是否大于等于右侧的操作数<=判断左侧的操作数是否小于等于右侧的操作数逻辑运算符&&逻辑与(AND),当两个条件都为真时,结果为真||逻辑或(OR),当至少一个条件为真时,结果为真!逻辑非(NOT),取反一个条件的值位运算符&按位与,对两个数的二进制进行按位与操作|按位或,对两个数的二进制进行按位或操作^按位异或,对两个数的二进制进行按位异或操作<<左移,将一个数的二进制表示向左移动指定位数>>右移,将一个数的二进制表示向右移动指定位数 这个表格提供了常见运算符类型和相应的说明,帮助你理解不同类型的运算符及其用途。记住,在实际使用运算符时,需要根据具体的需求和数据类型选择适当的运算符。
算术运算符:
+:相加
a := 5 b := 3 result := a + b // 8 -:相减
a := 5 b := 3 result := a - b // 2 *:相乘
a := 5 b := 3 result := a * b // 15 /:相除
a := 5 b := 3 result := a / b // 1 %:取模(取余数)
a := 5 b := 3 result := a % b // 2 赋值运算符:
MySQL UPDATE 语句用于修改表中的数据。以下是 MySQL UPDATE 语句的用法和一些例子:
基本用法:
UPDATE table_name SET column1 = value1, column2 = value2 WHERE condition; 其中,table_name 是要更新数据的表名,column1、column2 是要更新的列名,value1、value2 是要更新的新值,condition 是更新的条件。
例子 1:
假设有一个名为 customers 的表,其中包含 customer_id、customer_name 和 country 列。我们要将 ID 为 1 的客户的国家更改为 ‘USA’,可以使用以下语句:
UPDATE customers SET country = 'USA' WHERE customer_id = 1; 例子 2:
假设有一个名为 products 的表,其中包含 product_id、product_name 和 price 列。我们要将所有价格低于 10 的产品价格增加 2,可以使用以下语句:
UPDATE products SET price = price + 2 WHERE price < 10; 例子 3:
1 Redis组件模型 Redis 组件的系统架构如图所示,主要包括事件处理、数据存储及管理、用于系统扩展的主从复制/集群管理,以及为插件化功能扩展的 Module System 模块。
Redis的客户端与服务端的交互过程如下所示:
1.1 事件处理机制 Redis 中的事件处理模块,采用的是作者自己开发的 ae 事件驱动模型,可以进行高效的网络 IO 读写、命令执行,以及时间事件处理。
其中,网络 IO 读写处理采用的是 IO 多路复用技术,通过对 evport、epoll、kqueue、select 等进行封装,同时监听多个 socket,并根据 socket 目前执行的任务,来为 socket 关联不同的事件处理器。
当监听端口对应的 socket 收到连接请求后,就会创建一个 client 结构,通过 client 结构来对连接状态进行管理。在请求进入时,将请求命令读取缓冲并进行解析,并存入到 client 的参数列表。
然后根据请求命令找到 对应的redisCommand ,最后根据命令协议,对请求参数进一步的解析、校验并执行。Redis 中时间事件比较简单,目前主要是执行 serverCron,来做一些统计更新、过期 key 清理、AOF 及 RDB 持久化等辅助操作。
1.1.1 redis单线程模型 Redis 基于 Reactor 模式设计开发了一套高效的事件处理模型 (Netty 的线程模型也基于 Reactor 模式,Reactor 模式不愧是高性能 IO 的基石),这套事件处理模型对应的是 Redis 中的文件事件处理器(file event handler)。由于文件事件处理器(file event handler)是单线程方式运行的,所以我们一般都说 Redis 是单线程模型。
Redis 基于 Reactor 模式开发了自己的网络事件处理器:这个处理器被称为文件事件处理器(file event handler)。
1. 使用vscode 链接容器
2. 使用 docker ps -a 查看所有容器 3. - docker commit CONTAINER_NAME jingxiang_name # 将容器保存成镜像 4. docker images # 查看本地镜像是否生成 5. - docker save -o [name.tar] [jingxiang_name] # 将容器保存成tar文件 6. 将生成的tar包拉到本地文件夹就好了也可以用 命令
scp username@主机名: /home/acs/boke.tar 拉到本地
WSL+Ubuntu环境配置+集成桌面保姆级教程(非远程桌面) 写在前面:
该安装指导默认安装在C盘,请确保C盘有足够的空间(150G),若没有足够空间,可用DiskGenius将C盘后面的盘里内容镜像备份迁移,然后再重新导入
本教程参照官方教程说明:适用于 Linux 的 Windows 子系统文档 | Microsoft Learn
一、安装wsl 有两种方法,一是命令行安装,二是应用商店安装,方式二较为简单,此处省略方法一,想了解的可以参照官方教程
打开Microsoft Store,搜索wsl和自己想要安装的linux发行版(此处以Ubuntu-22.04为例)并下载安装
安装完之后直接按Win在所有应用里搜索打开即可
首次打开时会要求输入用户名和密码(此用户名和密码与windows用户名无关)
二、子系统配置 1.更换国内镜像源 # 用vim打开源文件 sudo vim /etc/apt/sources.list 想深入了解如何使用vim的可以自行百度
键盘单击"i"进入插入模式
将所有镜像地址更改为http://mirrors.tuna.tsinghua.edu.cn/ubuntu/
键盘"Esc"退出插入模式,然后输入:wq保存并退出
# 更新和升级包 sudo apt update && sudo apt upgrade 2.Linux GUI集成桌面 (1)安装vGPU驱动程序 Inte GPU 驱动程序
AMD GPU 驱动程序
NVIDIA GPU 驱动程序
重启wsl生效:
在PowerShell终端
# 更新wsl wsl --update # 重启Wsl wsl --shutdown (2)运行Linux GUI应用 在linux终端中:
# 更新发行版中的包 sudo apt update 可选的有
Gedit 是 GNOME 桌面环境的默认文本编辑器。
防抖(Debounce)和节流(Throttle)都是用来控制某个函数在一定时间内的触发次数.
作用:减少触发频率,提高性能或者说避免资源浪费。
区别:
防抖 (多次触发 只执行最后一次)
节流 (规定时间内 只触发一次)
应用场景:
防抖:登录、短信验证等按钮避免用户点击太快,发行多次请求;调整浏览器窗口大小时,resize 次数过于频繁,计算过多,造成页面卡顿的情况;文本编辑器实时保存;搜索框等。
节流: 鼠标连续不断地触发某事件(如点击事件),单位时间内只触发一次;监听滚动事件,例如:懒加载;每隔多少秒计算一次相关数据。
防抖
let timeout = null /** * 防抖原理:一定时间内,只有最后一次操作,再过wait毫秒后才执行函数 * * @param {Function} func 要执行的回调函数 * @param {Number} wait 延时的时间 * @param {Boolean} immediate 是否立即执行 * @return null */ function debounce(func, wait = 500, immediate = false) { // 清除定时器 if (timeout !== null) clearTimeout(timeout) // 立即执行,此类情况一般用不到 if (immediate) { const callNow = !timeout timeout = setTimeout(() => { timeout = null }, wait) if (callNow) typeof func === 'function' && func() } else { // 设置定时器,当最后一次操作后,timeout不会再被清除,所以在延时wait毫秒后执行func回调方法 timeout = setTimeout(() => { typeof func === 'function' && func() }, wait) } } export default debounce 节流
TPS(Transactions Per Second,每秒事务数)波动大通常指在某个系统中,其处理事务的速度在不同时间段内波动较大。这种波动可能导致系统性能不稳定,影响用户体验和系统可靠性。以下是一些可能的原因、排除方法和解决方案:
原因:
资源限制:系统的硬件资源(如CPU、内存、网络带宽)受限,无法满足高负载时的要求。锁竞争:多个事务之间争夺共享资源(如数据库表、文件)时,可能出现锁竞争,导致性能下降。不合理的算法或数据结构:系统中使用的算法或数据结构可能不适合处理高负载,导致性能波动大。配置问题:系统的配置参数设置不合理,未能最大化系统的性能。 排除方法:
需要根据具体的系统环境和问题进行定制化的排除和解决方案选择。对于复杂的系统问题,可能需要进行深入的性能分析和调优工作,甚至对系统进行重构或升级。
监测和分析:使用性能监测工具来检测系统的TPS波动情况,并进行详细分析以确定波动的原因。压力测试:模拟高负载情况下的系统压力,观察TPS的波动情况,以便发现潜在问题。资源优化:增加或优化系统的硬件资源,如增加服务器数量、升级硬件配置等。锁优化:通过减少锁竞争或改进锁机制,减少事务间的争用,提高系统性能。算法和数据结构优化:针对系统中使用的算法和数据结构,进行优化和改进,以提高系统处理事务的效率和稳定性。可以考虑使用更高效的算法或数据结构,减少不必要的计算或数据访问操作。 解决方案:
编写高效的代码:优化系统的代码逻辑和算法,尽量减少不必要的计算和数据访问操作。并发控制:采用合适的并发控制机制,如锁机制、事务隔离级别等,以避免锁竞争和并发冲突问题。负载均衡:使用负载均衡技术将请求分发到多个服务器上,以平衡系统的负载,提高整体的处理能力。缓存优化:通过引入缓存机制,缓存常用的数据或计算结果,减少对底层资源的访问,提高系统的响应速度和吞吐量。系统调优:根据监测和分析结果,对系统的配置参数进行调优,以最大化系统的性能和稳定性。
以下是一个简单的仓库管理系统的示例代码,使用Python编写:
class Item: def __init__(self, name, quantity): self.name = name self.quantity = quantity class Warehouse: def __init__(self): self.items = [] def add_item(self, item): self.items.append(item) def remove_item(self, item): self.items.remove(item) def update_item(self, item, quantity): item.quantity = quantity def list_items(self): for item in self.items: print(f"{item.name}: {item.quantity}") warehouse = Warehouse() while True: print("1. Add item") print("2. Remove item") print("3. Update item quantity") print("4. List items") print("5. Exit") choice = input("Enter your choice: ") if choice == "
$regPaths = "HKLM:\SOFTWARE\Autodesk","HKCU:\Software\Autodesk" foreach ($regPath in $regPaths) { # 删除键和它的所有子键和值 Remove-Item -Path $regPath -Recurse }
1. 认识调试器 1.1 含义 一个能让程序运行、暂停、然后对进程的状态进行观测甚至修改的工具。
在日常的开发当中使用非常广泛。(PHP开发者以及前端开发者除外)
1.2 常见的调试器 Go语言的自带的 delve 简写为 “dlv”GNU组织提供的 gdbPHP Xdebug前端浏览器debug 调试 1.3 调试器实现原理 本质上讲:硬件上和操作系统层面早就已经帮我们预留了口子去实现调试功能。
1.3.1 中断 中断interrupt 字面意思是"打断"、“阻止”。其实计算机领域的意思和它的字面意思一样,就是打断现在CPU或者进程的执行状态。
为了减少对正在运行的进程的影响,中断处理就要尽可能的快速执行,但是介于中断处理程序在进行响应中断时,有排他性,这就导致一个中断在处理完成之前,其他中断均不能进行响应,倘若一个中断处理的时间过长,则会导致其他中断不能够及时接收到信息甚至中断丢失,为了处理这个问题;操作系统将中断分为两部分:上半部硬中断 和 下半部软中断。
上半部分就是快速接收中断,下半部分会开启中断处理线程延迟处理上半部接收到的中断。
CPU中预留了寄存器(中断寄存器interrupt register),来帮我们实现硬中断,操作系统层面提供了系统调用来帮我们实现软中断。
中断其实是计算机系统中的一种异步事件的处理机制,可以提高系统的并发处理能力。
1.3.2 编译型调试器实现原理 系统中存在一个中断向量表,记录中断信号编码与中断处理程序的对应关系(程序入口地址)
CPU在执行完指令后,都会去check一下是否需要中断了, 如果不需要中断,则继续执行PC寄存器(CS、IP)的下一条指令,如果需要中断,则根据中断向量表,把向量表中的处理程序的入口地址的指令压入PC寄存器,执行中断程序处理。
在中断向量表中有一个int3 指令,是计算机硬件和操作系统支持的调试中断信号。
debug调试器会直接更改当前程序中打断点的位置代码指令并将原来代码存储到寄存器,让其触发int3中断信号,系统在触发中断异常后,debug调试程序捕获中断异常从而让程序停在断点处,如果断点去掉,则会将之前在存储器的原来代码恢复。
所以,其实用其他中断信号,也可以制作调试器,不一定非得int3
1.3.3 解释型调试器实现原理 解释型的语言无需了解CPU中断机制和系统调用,但是实现思路是类似的,就是插入一段代码来断住,且支持环境数据的查看,然后断点放开后就继续执行。
例如:js的 V8 引擎本身就支持debug能力,对外提供了很多接口,以socket 暴露出去,信息格式为 v8 debug protocol
2. 人人都会犯错,正确认识bug 只要程序还是人在写,就会有bug。人无完人,不能因为一个“幼稚”、“低级”的bug把一个人一棒子打死,认为他不是个优秀的程序猿,这种看法实际在一定程度上忽视了人类犯错的复杂性和影响因素的多样性。
2.1 时间因素: 很多项目需要赶进度,抢占蓝海市场,则把软件工期压缩的很短,这样一来就只考虑了开发的效率而忽视了开发的质量;导致代码中出现很多bug
2.2 系统的迭代: 假如将软件系统比作建房子,没有哪一个建筑,能够经得起如此频繁和多的修改,也不可能有人能够在房屋设计之前,就预料到未来需要设计成什么样,每一次的修改和迭代会慢慢破坏软件设计的完整性,直到最后,可能因为一个很小的点,就要修改大量之前的逻辑,如果修改不全,则就会引入bug;
2.3 程序的复杂性: 绝大多数的代码维护程序猿总是在没有完整了解系统整个全貌的情况下去做一些建议的修改,这是非常不可取的,因为如果系统足够大,如果修改掉很多当前看似“怪异”的逻辑,可能会导致引入更多修补的补丁。
2.4 怎么做? 对于一个团队,我们需要思考如何提供一种机制,去减少bug的发生,而不是埋怨犯错的人,否则会破坏团队风气,降低整体研发效率。
团队:
开发者对代码进行严格的测试,写单测。
把单元测试流程化。
代码评审。
测试人员测试。
3. 如何写出好的代码 意识上:
前言 众所周知,SpringBoot的自动装配是其核心功能之一,SpringBoot提供了许多自动配置类,我们通常会有这样的一个概念:“当应用程序启动时,SpringBoot会扫描路径上的自动配置类进行加载,从而大大简化了项目配置的工作”,在这里,我们来从代码来学习下自动化装配的原理以及流程;文章将尽力地解答如下两个问题:
关于自动装配的bean,尤其是非开发人员所开发的外部资源,是在哪里配置的?而这些配置信息,是在哪里读取解析,并注册到工程中的? 这两个问题很快会有答案~
关于在哪里配置的问题 首先关于第一个问题,我们需要了解下SpringBoot的SpringFactoriesLoader,SpringFactoriesLoader是SpringBoot定义的通用工厂加载机制,我们可以从源码上看下相关的介绍:
/** * General purpose factory loading mechanism for internal use within the framework. * * <p>{@code SpringFactoriesLoader} {@linkplain #loadFactories loads} and instantiates * factories of a given type from {@value #FACTORIES_RESOURCE_LOCATION} files which * may be present in multiple JAR files in the classpath. The {@code spring.factories} * file must be in {@link Properties} format, where the key is the fully qualified * name of the interface or abstract class, and the value is a comma-separated list of * implementation class names.
1、Ubuntu22.04, Vmware Workstation17 pro 2、源码 b_snake.c #include <stdio.h> #include <stdlib.h> #include <termios.h> #include <unistd.h> #include <fcntl.h> #include <time.h> #include <curses.h> #define WIDTH 30 #define HEIGHT 20 int x, y, fruit_x, fruit_y, score; int gameover = 0; int tail_x[100], tail_y[100]; int ntail = 0; enum eDirection {STOP = 0, LEFT, RIGHT, UP, DOWN}; enum eDirection dir; int kbhit(void) { struct termios oldt, newt; int ch; int oldf; tcgetattr(STDIN_FILENO, &oldt); newt = oldt; newt.
使用Hutool和POI读写excel中遇到的错误 报错部分:
Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Handler dispatch failed; nested exception is java.lang.NoSuchMethodError: org.openxmlformats.schemas.spreadsheetml.x2006.main.impl.CTFontsImpl.getXmlObjectArray(Ljavax/xml/namespace/QName;[Lorg/apache/xmlbeans/XmlObject;)[Lorg/apache/xmlbeans/XmlObject;] with root cause
java.lang.NoSuchMethodError: org.openxmlformats.schemas.spreadsheetml.x2006.main.impl.CTFontsImpl.getXmlObjectArray(Ljavax/xml/namespace/QName;[Lorg/apache/xmlbeans/XmlObject;)[Lorg/apache/xmlbeans/XmlObject;
at org.openxmlformats.schemas.spreadsheetml.x2006.main.impl.CTFontsImpl.getFontArray(CTFontsImpl.java:71) ~[poi-ooxml-lite-5.0.0.jar:na]
首先一定要明确的一点说,hutool版本和poi版本一定要适配,不然会出现各种各样的问题,比如让你缺失一个包然后导进去一个新的包,然后就又会出现要继续让你添加新的依赖来导入包,那是一定无法解决问题的,在确保你的远端仓库可以支持你频繁变动依赖版本的情况下,多尝试换几个版本。
比如我下方代码展示的两个版本,因为5.几版本的hutool要求poi版本需要在4.1.2或以上其他版本,但也不是鼓励大家乱改版本摸奖式来解决问题,但是笔者之前hutool使用的版本是5.7.20,poi是4.1.2也无法解决问题。所以在能用的前提下,可以参照我一下的版本来添加依赖,希望可以省去很多不必要的麻烦替你省下时间。
<dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.6.6</version> </dependency> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>4.1.2</version> </dependency>
重新注册一下就可以了: /root/datax/datax-web-2.1.2/modules/datax-executor/bin/datax-executor.sh start
1. 瑞吉外卖 瑞吉外卖技术栈:SpringBoot、MybatisPlus、springMVC
瑞吉外卖是我做的第一个项目,算是我做过所有的项目中最简单的,很适合新手入门,我当时是学完springboot就做了这个
2. 传智健康 传智健康这个项目用到了挺多我之前没有学过的东西,比如POI制作报表、七牛云保存图片、spring security实现权限控制等,做项目的过程中是真的可以学到很多知识,实践还是很重要的。
3.黑马点评 黑马点评这个项目真的很推荐大家去做,黑马那套视频的原理篇可以等到面试前再去看,之前我对缓存击穿、缓存穿透、缓存雪崩这些概念都是一知半解的,看完这个视频,做了这个项目就有一种茅舍顿开的感觉。
黑马点评项目主要包括以下模块: 短信登录:Redis共享session
商户查询缓存:缓存雪崩、穿透等
达人探店:基于List的点赞列表,基于SortedSet的点赞排行榜
优惠券秒杀:Redis的计数器、Lua脚本Redis、分布式锁、Redis三种消息队列
好友关注:基于Set集合的关注、取关、共同关注、消息推送等
附近商户:Redis的GeoHash
用户签到:Redis的BitMap数据统计功能
UV统计:Redis的HyperLogLog的统计功能
黑马点评项目笔记 短信登录模块 一开始,我使用的是用户的昵称作为key值保存到redis中,这样在拦截器中就无法从redis中获取到用户信息,因为在拦截器中不知道用户的基本信息,无法获取到昵称、电话号码等信息
在保存用户信息到redis中的时候,要使用token作为key值进行存储,因为在拦截器对请求进行拦截的时候,用户会携带token发起请求,这样才能够在拦截器中获取到Redis中保存的用户信息。使用hash的形式把用户信息存入redis中可以减少存储空间,也可以使用String的形式,但是需要的存储空间就比较多。
短信登录要注意验证手机号码的正确性
使用拦截器拦截用户的请求
商户查询缓存 缓存:数据交换的缓冲区,存储数据的临时地方,一般读写性能较高
浏览器缓存
应用层缓存:tomcat
数据库缓存:mysql是把数据按页加载到内存的,如果查询的是已经加载到内存中的页的数据,就不用读磁盘了
这里注意static关键字随着类的加载而被加载到内存之中,作为本地缓存,被final修饰所以其引用和对象之间的关系是固定的
缓存更新策略
当内存数据过多时,redis会对部分数据进行淘汰
内存淘汰:redis自动进行,当redis内存达到设定的max-memery的时候,会自动触发淘汰机制,淘汰一些不重要的数据
超时剔除:给redis设置了过期时间TTL之后,redis会将超时的数据进行删除
主动更新:我们手动调用方法把缓存里面的数据删掉,通常用于解决缓存和数据库不一致的问题
数据库缓存不一致问题:先操作数据库,再删除缓存,这种情况出现线程不安全问题的概率比较低。
缓存穿透 缓存穿透: 指客户端不断请求数据库和缓存中都不存在的数据,这样这些请求都会打到数据库上面
缓存穿透常见解决方案
缓存空对象:在第一次向数据库查询不存在的数据时,向redis中存放空对象(设置数据的过期时间),这样当客户端再次发起请求时,直接将空数据返回,就不会再次请求数据库。缺点是会造成额外的内存消耗、可能造成短期的数据不一致问题
布隆过滤:采用哈希思想来解决问题,即使用一个庞大的二进制数据,走哈希思想来判断当前客户端请求的数据是否存在,如果布隆过滤器判断数据存在,则放行该请求。若布隆过滤器判断不存在则直接拒绝该请求。
布隆过滤器优点在于节约内存空间,但是存在误判的情况,因为可能会存在哈希冲突的问题。
增强id的复杂度,避免被猜测id规律
做好数据的基础格式校验
加强用户权限校验
做好热点参数的限流
缓存雪崩 缓存雪崩: 在同一时间段内大量的缓存key同时失效或redis服务宕机,导致大量请求到达数据库,带来巨大压力。
解决方案: 1. 给不同的key的TTL添加随机值
2. 利用redis集群提高服务的可用性 给缓存服务添加降级限流策略 4. 给业务添加多级缓存
缓存击穿 缓存击穿也称为热点key问题就是一个被高并发访问并且缓存重建业务较复杂的key突然失效,无数请求直接访问数据库带来巨大冲击。
常见解决方案
互斥锁:线程1先查询缓存之后,去查询数据库并重构数据到缓存中,在线程1未结束缓存重构的时间段内,其他线程想要访问这个数据都会被阻塞,这方法可能会出现死锁的问题。
逻辑过期:把过期时间设置在redis的value中,这个过期时间并不会真正起作用,若线程1查询缓存发现当前数据已过期,此时线程1获得互斥锁,并且开启另外一个线程去查询数据库完成缓存重构任务后释放锁,此时的线程1直接返回旧数据,若有其他线程也来访问这个数据并发现数据过期但无法获得锁,则直接返回旧数据。这个方法是异步构建缓存,在构建完缓存之前,返回的都是脏数据。
优惠券秒杀 全局ID生成器
全局唯一ID生成策略:
UUID
Redis自增
一、安装openresty 1.添加仓库执行命令 yum install yum-utils yum-config-manager --add-repo https://openresty.org/package/centos/openresty.repo 2.执行安装 yum install openresty 3.安装成功后 会在默认的目录如下 cd /usr/local/openresty 二、安装nginx 默认已经安装好了nginx,在目录:/usr/local/openresty/nginx 下。
修改/usr/local/openresty/nginx/conf/nginx.conf,将配置文件使用的根设置为root,目的就是将来要使用lua脚本的时候 ,直接可以加载在root下的lua脚本。
cd /usr/local/openresty/nginx/conf vi nginx.conf 三、启动 cd /usr/local/openresty/nginx/sbin ./nginx http://192.168.1.133/
yum install libtermcap-devel ncurses-devel libevent-devel readline-devel curl -R -O http://www.lua.org/ftp/lua-5.3.5.tar.gz tar zxf lua-5.3.5.tar.gz cd lua-5.3.5 make linux test lua
漏洞名称:Weblogic Commons Collections 序列化代码执行漏洞(CVE-2015-4852) English Name:Weblogic Commons Collections serialization code execution vulnerability (CVE-2015-4852) CVSS core: 7.5 影响资产数:127703 漏洞描述: WebLogic Server 是一个适用于云和传统环境的应用程序服务器组件。
WebLogic Commons Collections 组件存在远程代码执行漏洞,允许未经身份验证的攻击者通过 IIOP 协议访问易受攻击的 WebLogic Server 并对其进行破坏。 成功利用此漏洞可导致攻击者接管 WebLogic Server,从而导致远程代码执行。
漏洞影响: WebLogic Commons Collections 组件存在远程代码执行漏洞,允许未经身份验证的攻击者通过 IIOP 协议访问易受攻击的 WebLogic Server 并对其进行破坏。 成功利用此漏洞可导致攻击者接管 WebLogic Server,从而导致远程代码执行。
FOFA查询语句(点击直接查看结果): (body=“Welcome to WebLogic Server”) || (title==“Error 404–Not Found”) || (((body=“<h1>BEA WebLogic Server” || server=“Weblogic” || body=“content=\“WebLogic Server” || body=”<h1>Welcome to Weblogic Application" || body=“<h1>BEA WebLogic Server”) && header!
问题出现原因 在通过vue写前端页面时,想要通过点击图标实现图标样式的切换,比如收藏和取消收藏的样式实现。
解决问题思路 我的想法是通过操作dom节点来实现,而在vue中可以通过三种方式来操作节点。
一、原生的js
二、jQuery的方式
三、vue自带方法
解决方法 这里我采用的是第三种方法,因为要使用的地方比较多,于是就将其封装为一个单独的组件,方便使用。
<template> <div class="collect"> <i ref="off" class="el-icon-star-off" @click="change1" style="display: block"></i> <i ref="on" class="el-icon-star-on" @click="change2" style="display: none"></i> </div> </template> <script> export default { name: "Collect", methods: { change1() { this.$refs.on.style.display = "block"; this.$refs.off.style.display = "none"; }, change2() { this.$refs.on.style.display = "none"; this.$refs.off.style.display = "block"; } } } </script>
dll(动态链接库),是一种可以被程序调用的一种通用型文件。
也正因为这个特性,导致在删除垃圾软件后,会残留许多dll难以卸载。
如图
类似的“已在windows资源管理器打开”,如果要删除会耗费大量的精力,可能结果还删不掉。
1、查看dll调用 在删除dll文件的时候需要先确定被哪些exe程序调用了,将此任务结束,才能删除 dll文件。
一个简单的查询方法是cmd中命令查询。
Tasklist /m qingshellext64.dll Tasklist 任务列出,后边加上具体的dll文件全称,就可以看到是具体哪些exe程序调用了该dll文件。
如图
好家伙,直接被explorer调用了。
重新运行,无用。
2、删除正在被调用dll---AlwaysUnloaDll win + R 打开运行框,输入命令 " regedit " 打开注册表,打开以下目录 " HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer
空白处新建
重命名为AlwaysUnloaDll
右键修改值为1
重启
然后就可以删除正在被调用的dlll。
3、AlwaysUnloaDll强制删除dll原理 这是本文的关键
之前搜索了许多资料,但是都是告诉了步骤,没有讲解具体原理。
我有点钻牛角尖了,只想了解原理,在谷歌、百度之后,得到了答案
AlwaysUnloaDll是windows中的一个注册表键值,它的作用就是控制是否强制删除dll文件,如果它的数值设置为0,默认不启用,即无法删除正在被调用的dll,将它的值设为1,开启强制卸载dll功能。
但是如果开启的话可能会导致一些电脑的问题,一些重要的dll缺失会引发问题。比如系统dll
因此建议如果开启了,不要随意删除dll,或者删除垃圾文件的dll后,就再次关闭它
文章目录 一. 计数器:利用redis的INCR命令实现计数器功能,适用于需要频繁更新计数的场景。二、实现短连接三. 分布式锁:利用redis的原子性操作,实现分布式锁,避免多个进程同时修改同一资源造成的数据不一致问题。四. 会话管理:将用户会话信息存储在redis中,实现分布式环境下的会话共享和管理。五. 数据缓存:将经常使用的数据缓存在redis中,减少数据库的读取次数,提高系统性能。六. Redis字符串数据类型的使用场景:session存储。七. 限流控制:利用Redis字符串数据类型的过期时间和自增命令可以实现简单的限流控制,防止系统被恶意攻击或者异常请求所影响。 一. 计数器:利用redis的INCR命令实现计数器功能,适用于需要频繁更新计数的场景。 以下是一个Python代码例子,实现了一个基于Redis的计数器:
import redis class RedisCounter: def __init__(self, key, host='localhost', port=6379, db=0, password=None): self.key = key self.redis = redis.Redis(host=host, port=port, db=db, password=password) def increment(self, amount=1): self.redis.incr(self.key, amount) def decrement(self, amount=1): self.redis.decr(self.key, amount) def get_count(self): return int(self.redis.get(self.key) or 0) 这个计数器类包含了三个方法:increment、decrement和get_count。其中,increment方法使用Redis的incr命令实现增加计数器的功能,decrement方法使用Redis的decr命令实现减少计数器的功能,get_count方法则返回当前计数器的值。
使用这个计数器类非常简单,只需要初始化一个实例,然后调用相应的方法即可:
counter = RedisCounter('my_counter') counter.increment() # 自增1 counter.increment(5) # 自增5 counter.decrement(3) # 自减3 print(counter.get_count()) # 输出当前计数器的值 由于Redis的incr和decr命令是原子性的,因此这个计数器可以保证在高并发场景下的正确性。
INCR命令只能用于操作String类型的值,并且只能对存储整数的值进行自增操作,如果该key不存在,则会先将其初始化为0再执行自增操作。
二、实现短连接 具体步骤如下:
生成短连接字符串,可以使用base62或其他算法,将长链接转换成短链接。
一、C4D - 入门篇 旋转视图:Alt + 鼠标左键缩放视图:Alt + 鼠标右键平移视图:Alt + 鼠标中间移动:E、缩放:T、R:旋转撤回:Ctrl + Z复原视图:Ctrl + shift + Z转为可编辑对象:C局部坐标与世界坐标切换:W循环线选中:双击(确保坐标轴关闭)打组:Alt + G、取消打组:shift + G循环切刀工具:KL挤压:D、内部挤压:I消除:MN实时选择:0、框选:9倒角:MS线性切割:KK MK反转法线:URshift + 12:设置快捷键细分曲面:
在视图里以编辑器细分为主,最终状态以渲染器细分为主
连接对象+删除:
条件需要选中两个物体,如果是父子级中的,按鼠标中间选择父级,右键选择“连接对象+删除”连接对象:
选中两个物体 - 右键 - 连接对象
可以使两个物体连接到一起,然而并不删除原有的物体转为可编辑对象:两个物体变成可编辑对象当前状态转对象:原本物体保存的同事,新生成两个可编辑对象
循环切割:ML
选中线模式 + ML
①分割线在中间、②分割线加一条、③分割线减一条
选中物体按Alt + 细分曲面,直接被细分,不需要拖拽父子级复制:Ctrl + 鼠标左键拖拽实时选择工具:
按住鼠标中间不放可以调节选框大小
①编辑器可见、②渲染器可见、③是否启用
在层级状态栏,选中一个物体 - 按住Ctrl + 左键 - 拖拽,可达到复制效果
独显:①关闭所有独显、②单个物体独显(需要选中)、③选中一层级独显
摄像机焦距:
36是人眼焦距、
135是比较平行结构,长焦(大场景与特写适合)、
20是广角镜头,适用于有冲击力的场景,畸变严重的场景
50,80适用于有角色的场景
启用量化:
启用量化的同事按住shift键可以画出笔直的线
放样工具:
可连接多个样条作为剖面创建对象,剖面就是横截面
会把路径计算成横截面按照对象图层的上下顺序从上到下连接
扫描工具:
将横截面沿着一条路径扫描得到的形状
横截面是第一条路径另一条路径为主路径
同为子集的上面一个样条为横截面,下面一个样条为路径
设置选集:给一个物体上不同的材质
选择 - 设置选集
先将yunshellextv164名称及后缀名改了,例如111.txt
然后打开任务管理器,在进程中找到Windows文件管理器,右键重启,然后找到该文件就可以删除了
前言 因为前边讲了在Unity中如何实现一个BRDF——简单来说就是把公式抄到Shader里,于是这篇文章则主要从原理角度来讲一讲基于物理的BRDF公式为什么长那个样子。本篇文章主要是整理一下去年(2022)十二月底写的关于基于微平面理论的BRDF的一些东西,主要依据的是两篇经典论文:一篇是Cook-Torrance模型的A Reflectance Model for Computer Graphics[1982],另一篇则是GGX模型Microfacet Models for Refraction through Rough Surfaces[2007](这篇论文的作者里也有我的导师…真要说的话本篇文章内容的由来还有一些故事)。
原本计划中应该还有两三篇文章,但这可能是这几天(或者很长一段时间)的最后一篇了,连更几篇确实把我写累了(倒),而且因为一直忙着整理以前的东西,导致我这几天的作业也还没写,导师的项目也几天没动了(其实也是因为还有一些进度存货) 。
BRDF 双向反射分布函数(Bidirectional Reflectance Distribution Function,BRDF)描述了物体表面入射光与反射光的关系,具体从定义上来说是出射方向Radiance与入射方向Irradiance的比值,这里也简单补上辐射度量学里的相关定义:
辐射能量(Radiant Energy):电磁辐射的能量, Q Q Q辐射通量/功率(Radiant Flux/Power):单位时间内通过某一区域的能量, Φ = d Q d t \Phi = \frac{dQ}{dt} Φ=dtdQ辐射强度(Radiant Intensity):单位立体角内的辐射通量, I ( ω ) = d Φ d ω I(\omega) = \frac{d\Phi}{d\omega} I(ω)=dωdΦ辐射照度(Irradiance):单位面积内的辐射通量, E ( x ) = d Φ ( x ) d A E(x) = \frac{d\Phi(x)}{dA} E(x)=dAdΦ(x)辐射亮度(Radiance):单位立体角、单位投影面积内的辐射通量, L ( p , ω ) = d 2 Φ ( p , ω ) d ω d A c o s θ L\left( {p,\omega} \right) = \frac{d^{2}\Phi(p,\omega)}{d\omega dAcos\theta} L(p,ω)=dωdAcosθd2Φ(p,ω) 这时候我们再来看渲染方程, L i ( p , ω i ) n ⋅ ω i d ω i L_{i}\left( {p,\omega_{i}} \right)n \cdot \omega_{i}\mathbb{d}\omega_{i} Li(p,ωi)n⋅ωidωi其实算的就是入射方向的Irradiance d E ( ω i ) {d}E(\omega_{i}) dE(ωi),中间的点乘是因为入射的角度不同接收到的能量就不同。
设置定时任务 可以使用 Linux 中的 crontab 工具来设置定时任务,具体步骤如下:
打开终端,输入 crontab -e 命令,打开 crontab 编辑器。在编辑器中,输入 30 9 * * 1-5 command,其中 30 9 * * 1-5 表示每周一到周五的 9:30,command 表示需要执行的命令。编辑完成后,保存并退出编辑器即可。 例如,如果要在每周一到周五的 9:30 执行 /usr/bin/python3 /home/user/main.py,则可以在 crontab 编辑器中输入以下命令:
# 定时 python3 main.py文件的路径 30 9 * * 1-5 /usr/bin/python3 /home/user/main.py 保存并退出编辑器后,系统会在每周一到周五的 9:30 执行 /usr/bin/python3 /home/user/main.py 命令。
crontab中* * * * *的作用 * * * * * 是 Linux 定时任务中的时间格式,它是由5个字段组成,分别表示分钟、小时、日期、月份、星期几,它们的含义如下:
第1个字段表示分钟,取值范围为 0-59;第2个字段表示小时,取值范围为 0-23;第3个字段表示日期,取值范围为 1-31;第4个字段表示月份,取值范围为 1-12;第5个字段表示星期几,取值范围为 0-6,其中 0 表示星期日,1 表示星期一,以此类推。 每个字段都可以使用以下取值方式:
一、简介 Elasticsearch是一个基于Lucene的全文搜索和分析引擎,Lucene Core是一个完全用Java编写的高性能、全功能搜索引擎库。
它可以快速地存储、实时搜索和分析大量数据。
它可以扩展到上百台服务器,处理PB级数据。PB = 2^50 Byte, 在数值上约等于1000个TB。 人类功能记忆容量约1.25TB, 也意味着800个人类记忆相当于1PB。
二、认知 1、Lucene Lucene,封装好了各种建立倒排索引、匹配索引进行搜索的各种算法。我们可以引入Lucene,基于它的API进行开发。
ElasticSearch就在Lucene的基础上实现的,对Lucene进行了良好的封装,简化开发,并提供了很多高级功能
ElasticSearch生态
ElasticSearch 为快速检索和分析大数据而生,目前已形成丰富的生态。
成熟的ELK体系:
Elasticsearch: 位于Elastic堆栈核心的分布式搜索和分析引擎Logstash + Beats:收集、聚合、丰富数据,存储到Elasticsearch中Kibana: 以交互方式探索、可视化、共享对数据的见解,并管理和监视堆栈 2、倒排索引 索引:通过key来寻找value。与之相反,就是倒排索引
Elasticsearch使用倒排索引的结构,适用于快速的全文搜索。一个倒排索引由文档中所有不重复词的列表构成,对于其中每个词,有一个包含它的文档列表。
正排索引: 书的目录
倒排索引: 词典中单词的索引页
查询包含“关键搜索”的文档的过程
通过倒排索引获得“关键搜索”对应的文档id列表通过正排索引查询文档id列表的完整内容返回最终结果 For instance
Doc 1 : no your po no your work
Doc 2 : enjoy your team work
Doc 3 : enjoy challenge with your team
为了创建索引,ES引擎通过分词器将每个文件的内容拆成单独的词(词条/term),再将这些词条创建成不含重复词条的排序列表,然后列出每个词条出现在哪个文档:
termDoc 1Doc 2Doc 3no✅po✅work✅✅enjoy✅✅your✅✅✅team✅✅With✅challenge✅ 这种结构由文档中所有不重复的词的列表构成,对于其中每个词都有至少一个文档与之关联。这种由属性值来确定记录的位置的结构就是倒排索引,带有倒排索引的文件被称为倒排文件
核心概念:
词条(term):索引中最小的存储和查询单元。词典(Term Dictionary):字典,是词条的组合。倒排表(Post list) 一个文档通常由多个词组成,倒排表记录了某个词在那些文档出现过以及出现的位置。每条记录称为一个倒排项(Posting)。记录了文档编号、词频等信息。 倒排文件(Inverted File) 所有单词的倒排列表按顺序存储在磁盘的某个文件中,即为倒排文件词典在内存中; 倒排文件在磁盘中 3、基本概念 field 字段, (类似Mysql中的一个字段) Document 文档,一条数据,用json格式表示一个Document包含多个field, json中的key即field Type 类型,一个Document分组,(类似于Mysql中的table)一个Type包含多个Document,同一个Type中的Document所拥有的field可以不同,但最好保持一致 Index 索引,(类似Mysql中的database)一个Index包含多个Type。默认情况下,Document中所有的field都会被索引,这些field才会被搜索到 shard 分片可以将一个Index中的数据切分为多个shard,然后存储到多台服务器上,以增大一个Index可以存储的数据量,加速检索能力,提升系统性能 replica 副本与shard存储的数据是相同的,起到备份作用当shard发生故障时,可以从replica中读取数据,保证系统不受影响 Node 节点单个Elasticsearch实例,一台机器可以有多个节点节点名称默认随机分配 Cluster 集群一组Elasticsearch实例默认集群名称为 elasticsearch Elasticsearch名称ElasticSearch概念数据库Index索引库Type类型表Document文档行field字段列 Document文档 Json Object,由字段(field)组成
一、前言 通篇的关键就是知道ShiroRealm类重写的doGetAuthenticationInfo这个方法,到底是谁的方法。
从上图我们可以知道,ShiroRealm最终继承到了AuthenticatingRealm这个方法。
二、自定义的ShiroRealm类 ps:该图中①上的注释是没看过底层的时候,先入为主的理解。
在自定义的ShiroRealm中,看上去像验证的就是①,但也只是创建了一个SimpleAuthenticationInfo对象,最后②return出去就没了。
小白的我尝试Debug去找实际执行的代码,却只跳到了SimpleAuthenticationInfo类中,最后一无所获。
我们回过头看下ShiroRealm,它继承了AuthorizingRealm
进到了AuthorizingRealm里面,你会发现还是什么没有,但你会注意到,它继承了AuthenticatingRealm(他们的关系如 一前言 所示)。
三、AuthenticatingRealm类 在该类中,通过Find可以发现,AuthenticatingRealm类中含有抽象方法doGetAuthenticationInfo,也就意味着,ShiroRealm中@Override就是它。
我们在看下是谁调用了这个方法,最后可以发现,在AuthenticatingRealm类中,getAuthenticationInfo()方法里的②调用了该方法;也就是说,我们在ShiroRealm中return的内容在这里。
讲到这里,我们分别在看下①和③。
1、getCachedAuthenticationInfo(token)方法 ①从字面意思,参数和最后对象的类名,可以知道,这就是获取token中的用户名和密码。
补充:看到这里眼睛尖的朋友会发现,在上图中,①和②都是返回info(AuthenticationInfo类);代码逻辑是: // 通过缓存和token获取用户信息 info = getCachedAuthenticationInfo(token); // 用户信息不存在 if(info == null){ // 在ShiroRealm中@Override,获取登录用户的信息 info = doGetAuthenticationInfo(token); ... } AuthenticationInfo类(如下图)和AuthenticationToken有点类似,可以扩展看下shiro AuthenticationToken体系。
getCachedAuthenticationInfo主要是看缓存和token,获取用户信息。
2、assertCredentialsMatch(token, info)方法 在该方法里面有CredentialsMatcher(),虽然我不知道是什么,但下面的if里面调用了该对象的方法cm.doCredentialsMatch(token, info)。
从接口上可以看到有4个实现类,废话不多说,一个个看了后发现第一个只返回true,第二个处理有salt的,第四个处理没salt的,第三个太长了没细看。
对比下来,salt应该就是我们要找的。
ps:加密对小白的我难度有点大,暂时就不再深究了,有兴趣的大佬们可以去看看。
可以看到这里,我们就知道doCredentialsMatch方法中,最后token解密的数据和数据库中的数据相同,就会返回true,也就意味着验证通过。
如果返回false,就会抛出IncorrectCredentialsException(看assertCredentialsMatch(token, info)方法的第一张图),在controller中就会捕获该异常,返回给前端。
四、总结图
在项目开发中需要js实现千位分隔符来分割一个整数,比如我想将数字 1234567 显示为“1,234,567”。我该怎么实现呢?
方法一、利用循环 function numberWithCommas(x) { x = x.toString(); var pattern = /(-?\d+)(\d{3})/; while (pattern.test(x)) x = x.replace(pattern, "$1,$2"); return x; } numberWithCommas(12312124545);//'12,312,124,545' 方法二、toLocaleString var num = 12345.1234 num.toLocaleString();//'12,345.123' 方法三、 Intl.NumberFormat const number = 123456.789 const formatter = new Intl.NumberFormat() console.log(formatter.format(number)) // 123,456.789