【Go】excelize库实现excel导入导出封装(一),自定义导出样式、隔行背景色、自适应行高、动态导出指定列、动态更改表头
前言
最近在学go操作excel,毕竟在web开发里,操作excel是非常非常常见的。这里我选择用 excelize
库来实现操作excel。
为了方便和通用,我们需要把导入导出进行封装,这样以后就可以很方便的拿来用,或者进行扩展。
我参考的是这篇文章:【GO】excelize导入导出封装
功能
这个导入导出封装,除了基本的导入导出,我还需要一些其他功能。例如:设置隔行背景色、自适应行高、忽略指定字段或导出指定字段、复杂表头
等等。
因为实际项目中,操作excel不可能只是导出一个很简单的excel,实际项目中的要求往往要复杂的多。
导入
导入有以下几个通用的实现
- 导入单个sheet的数据(已完成)
- 导入指定sheet的数据(已完成)
- 导入多个sheet的数据(已完成)
导出
导出呢,就要复杂很多了,一级表头的普通导出是最简单的,实际项目中往往还会有多级表头,然后不管是一级还是多级表头,还需要有各种要求的样式,隔行背景色、自适应行高这种已经算简单的了,复杂点的还有一对多的纵向单元格合并。
所以导出需要实现以下这些:
- 普通导出(已完成)
- 一级表头
- 单个sheet
- 复杂表头、树形结构表头导出(未完成)
- 多个sheet导出(未完成)
- 基于map导出(未完成)
- 一对多纵向合并单元格(未完成)
- 动态导出列(已完成)
- 忽略指定字段
- 导出指定字段
- 动态更改表头名称
- 隔行背景色样式(已完成)
- 自适应行高样式(已完成)
这篇文章我们就来实现那几个已完成(未完成的还没开始实现呢,还有好多没实现,哭了)
其实上面这些功能,我之前早就在Java中实现了。感兴趣的话可以去这篇文章看看,有完整代码:
poi+easypoi实现表头多层循环,多级动态表头、树形结构动态表头、纵向合并单元格、多个sheet导出
实现
我们先在项目中,创建一个excel文件夹,里面放的就是我们封装的实现函数
准备
既然是通用的导入导出,那每次导入导出不同表格时,不可能说写死导入哪些列(列名),而是应该是按照不同表格对应的不同结构体来进行解析数据或导出数据。
所以我们可以定义一个专门用于解析excel的tag结构体(类似于easypoi的@Excel注解),在这个tag结构体定义几个字段,什么表头名称、列下标、列宽啊这些
用的时候呢,就是在不同结构体中,使用反引号去定义 表头名称、列下标、列宽 这些的值。
在 excel.go
中
自定义一个tag结构体
package excelimport ("github.com/pkg/errors""github.com/xuri/excelize/v2""regexp""strconv""strings"
)// 定义正则表达式模式
const (ExcelTagKey = "excel"Pattern = "name:(.*?);|index:(.*?);|width:(.*?);|needMerge:(.*?);|replace:(.*?);"
)type ExcelTag struct {Value interface{}Name string // 表头标题Index int // 列下标(从0开始)Width int // 列宽NeedMerge bool // 是否需要合并Replace string // 替换(需要替换的内容_替换后的内容。比如:1_未开始 ==> 表示1替换为未开始)
}// 构造函数,返回一个带有默认值的 ExcelTag 实例
func NewExcelTag() ExcelTag {return ExcelTag{// 导入时会根据这个下标来拿单元格的值,当目标结构体字段没有设置index时,// 解析字段tag值时Index没读到就一直默认为0,拿单元格的值时,就始终拿的是第一列的值Index: -1, // 设置 Index 的默认值为 -1}
}
定义好了tag结构体,我们还需要给它绑定解析tag的方法
// 读取字段tag值
func (e *ExcelTag) GetTag(tag string) (err error) {// 编译正则表达式re := regexp.MustCompile(Pattern)matches := re.FindAllStringSubmatch(tag, -1)if len(matches) > 0 {for _, match := range matches {for i, val := range match {if i != 0 && val != "" {e.setValue(match, val)}}}} else {err = errors.New("未匹配到值")return}return
}// 设置ExcelTag 对应字段的值
func (e *ExcelTag) setValue(tag []string, value string) {if strings.Contains(tag[0], "name") {e.Name = value}if strings.Contains(tag[0], "index") {v, _ := strconv.ParseInt(value, 10, 8)e.Index = int(v)}if strings.Contains(tag[0], "width") {v, _ := strconv.ParseInt(value, 10, 8)e.Width = int(v)}if strings.Contains(tag[0], "needMerge") {v, _ := strconv.ParseBool(value)e.NeedMerge = v}if strings.Contains(tag[0], "replace") {e.Replace = value}
}
用的时候,比如在某个用户信息结构体中
自定义一个excel对象结构体
定义好了tag结构体,同样是在 excel.go
文件中,我们还需要一个excel对象结构体,里面有excel file对象、样式等属性,然后再给它绑定设置样式的方法。
type Excel struct {F *excelize.File // excel 对象TitleStyle int // 表头样式HeadStyle int // 表头样式ContentStyle1 int // 主体样式1,无背景色ContentStyle2 int // 主体样式2,有背景色
}// 初始化
func ExcelInit() (e *Excel) {e = &Excel{}// excel构建e.F = excelize.NewFile()// 初始化样式e.getTitleRowStyle()e.getHeadRowStyle()e.getDataRowStyle()return e
}// 获取边框样式
func getBorder() []excelize.Border {return []excelize.Border{ // 边框{Type: "top", Color: "000000", Style: 1},{Type: "bottom", Color: "000000", Style: 1},{Type: "left", Color: "000000", Style: 1},{Type: "right", Color: "000000", Style: 1},}
}// 标题样式
func (e *Excel) getTitleRowStyle() {e.TitleStyle, _ = e.F.NewStyle(&excelize.Style{Alignment: &excelize.Alignment{ // 对齐方式Horizontal: "center", // 水平对齐居中Vertical: "center", // 垂直对齐居中},Fill: excelize.Fill{ // 背景颜色Type: "pattern",Color: []string{"#fff2cc"},Pattern: 1,},Font: &excelize.Font{ // 字体Bold: true,Size: 16,},Border: getBorder(),})
}// 列头行样式
func (e *Excel) getHeadRowStyle() {e.HeadStyle, _ = e.F.NewStyle(&excelize.Style{Alignment: &excelize.Alignment{ // 对齐方式Horizontal: "center", // 水平对齐居中Vertical: "center", // 垂直对齐居中WrapText: true, // 自动换行},Fill: excelize.Fill{ // 背景颜色Type: "pattern",Color: []string{"#FDE9D8"},Pattern: 1,},Font: &excelize.Font{ // 字体Bold: true,Size: 14,},Border: getBorder(),})
}// 数据行样式
func (e *Excel) getDataRowStyle() {style := excelize.Style{}style.Border = getBorder()style.Alignment = &excelize.Alignment{Horizontal: "center", // 水平对齐居中Vertical: "center", // 垂直对齐居中WrapText: true, // 自动换行}style.Font = &excelize.Font{Size: 12,}e.ContentStyle1, _ = e.F.NewStyle(&style)style.Fill = excelize.Fill{ // 背景颜色Type: "pattern",Color: []string{"#cce7f5"},Pattern: 1,}e.ContentStyle2, _ = e.F.NewStyle(&style)
}
导入
接下来我们就可以来实现导入函数的封装了,在 excel_import.go
文件中
package excelimport ("github.com/pkg/errors""github.com/xuri/excelize/v2""go-web/util""reflect""strconv"
)// ImportExcel 导入数据(单个sheet)
// 需要在传入的结构体中的字段加上tag:excel:"title:列头名称;"
// f 获取到的excel对象、dst 导入目标对象【传指针】
// headIndex 表头的索引,从0开始(用于获取表头名字)
// startRow 头行行数(从第startRow+1行开始扫)
func ImportExcel(f *excelize.File, dst interface{}, headIndex, startRow int) (err error) {sheetName := f.GetSheetName(0) // 单个sheet时,默认读取第一个sheeterr = importData(f, dst, sheetName, headIndex, startRow)return
}// ImportBySheet 导入数据(读取指定sheet)sheetName Sheet名称
func ImportBySheet(f *excelize.File, dst interface{}, sheetName string, headIndex, startRow int) (err error) {// 当需要读取多个sheet时,可以通过下面的方式,来调用 ImportBySheet 这个函数//sheetList := f.GetSheetList()//for _, sheetName := range sheetList {// ImportBySheet(f,dst,sheetName,headIndex,startRow)//}err = importData(f, dst, sheetName, headIndex, startRow)return
}// 解析数据
func importData(f *excelize.File, dst interface{}, sheetName string, headIndex, startRow int) (err error) {rows, err := f.GetRows(sheetName) // 获取所有行if err != nil {err = errors.New(sheetName + "工作表不存在")return}dataValue := reflect.ValueOf(dst) // 取目标对象的元素类型、字段类型和 tag// 判断数据的类型if dataValue.Kind() != reflect.Ptr || dataValue.Elem().Kind() != reflect.Slice {err = errors.New("Invalid data type")}heads := []string{} // 表头dataType := dataValue.Elem().Type().Elem() // 获取导入目标对象的类型信息// 遍历行,解析数据并填充到目标对象中for rowIndex, row := range rows {if rowIndex == headIndex {heads = row}if rowIndex < startRow { // 跳过头行continue}newData := reflect.New(dataType).Elem() // 创建新的目标对象// 遍历目标对象的字段for i := 0; i < dataType.NumField(); i++ {// 这里要用构造函数,构造函数里指定了Index默认值为-1,当目标结构体的tag没有指定index的话,那么 excelTag.Index 就一直为0// 那么 row[excelizeIndex] 就始终是 row[0],始终拿的是第一列的数据var excelTag = NewExcelTag()field := dataType.Field(i) // 获取字段信息和tagtag := field.Tag.Get(ExcelTagKey)if tag == "" { // 如果tag不存在,则跳过continue}err = excelTag.GetTag(tag)if err != nil {return}cellValue := ""if excelTag.Index >= 0 { // 当tag里指定了index时,根据这个index来拿数据excelizeIndex := excelTag.Index // 解析tag的值if excelizeIndex >= len(row) { // 防止下标越界continue}cellValue = row[excelizeIndex] // 获取单元格的值} else { // 否则根据表头名称来拿数据if util.IsContain(heads, excelTag.Name) { // 当tag里的表头名称和excel表格里面的表头名称相匹配时if i >= len(row) { // 防止下标越界continue}cellValue = row[i] // 获取单元格的值}}// 根据字段类型设置值switch field.Type.Kind() {case reflect.Int:v, _ := strconv.ParseInt(cellValue, 10, 64)newData.Field(i).SetInt(v)case reflect.String:newData.Field(i).SetString(cellValue)}}// 将新的目标对象添加到导入目标对象的slice中dataValue.Elem().Set(reflect.Append(dataValue.Elem(), newData))}return
}
导入这里用到了一个 IsContain
函数,代码如下:
// 判断数组中是否包含指定元素
func IsContain(items interface{}, item interface{}) bool {switch items.(type) {case []int:intArr := items.([]int)for _, value := range intArr {if value == item.(int) {return true}}case []string:strArr := items.([]string)for _, value := range strArr {if value == item.(string) {return true}}default:return false}return false
}
导出
在 excel_export.go
文件中
package excelimport ("fmt""github.com/pkg/errors""github.com/xuri/excelize/v2""net/http""reflect""sort""strings"
)// GetExcelColumnName 根据列数生成 Excel 列名
func GetExcelColumnName(columnNumber int) string {columnName := ""for columnNumber > 0 {columnNumber--columnName = string('A'+columnNumber%26) + columnNamecolumnNumber /= 26}return columnName
}// ================================= 普通导出 =================================// NormalDownLoad 导出excel并下载(单个sheet)
func NormalDownLoad(fileName, sheet, title string, isGhbj bool, list interface{}, res http.ResponseWriter) error {f, err := NormalDynamicExport(list, sheet, title, "", isGhbj, false, nil)if err != nil {return err}DownLoadExcel(fileName, res, f)return nil
}// NormalDynamicDownLoad 动态导出excel并下载(单个sheet)
// isIgnore 是否忽略指定字段(true 要忽略的字段 false 要导出的字段)
// fields 选择的字段,多个字段用逗号隔开,最后一个字段后面也要加逗号,如:字段1,字段2,字段3,
// changeHead 要改变表头的字段,格式是{"字段1":"更改的表头1","字段2":"更改的表头2"}
func NormalDynamicDownLoad(fileName, sheet, title, fields string, isGhbj, isIgnore bool,list interface{}, changeHead map[string]string, res http.ResponseWriter) error {f, err := NormalDynamicExport(list, sheet, title, fields, isGhbj, isIgnore, changeHead)if err != nil {return err}DownLoadExcel(fileName, res, f)return nil
}// NormalDynamicExport 导出excel
// ** 需要在传入的结构体中的字段加上tag:excelize:"title:列头名称;index:列下标(从0开始);"
// list 需要导出的对象数组、sheet sheet名称、title 标题、isGhbj 是否设置隔行背景色
func NormalDynamicExport(list interface{}, sheet, title, fields string, isGhbj, isIgnore bool, changeHead map[string]string) (file *excelize.File, err error) {e := ExcelInit()err = ExportExcel(sheet, title, fields, isGhbj, isIgnore, list, changeHead, e)if err != nil {return}return e.F, err
}// 构造表头(endColName 最后一列的列名 dataRow 数据行开始的行号)
func normalBuildTitle(e *Excel, sheet, title, fields string, isIgnore bool, changeHead map[string]string, dataValue reflect.Value) (endColName string, dataRow int, err error) {dataType := dataValue.Type().Elem() // 获取导入目标对象的类型信息var exportTitle []ExcelTag // 遍历目标对象的字段for i := 0; i < dataType.NumField(); i++ {var excelTag ExcelTagfield := dataType.Field(i) // 获取字段信息和tagtag := field.Tag.Get(ExcelTagKey)if tag == "" { // 如果非导出则跳过continue}if fields != "" { // 选择要导出或要忽略的字段if isIgnore && strings.Contains(fields, field.Name+",") { // 忽略指定字段continue}if !isIgnore && !strings.Contains(fields, field.Name+",") { // 导出指定字段continue}}err = excelTag.GetTag(tag)if err != nil {return}// 更改指定字段的表头标题if changeHead != nil && changeHead[field.Name] != "" {excelTag.Name = changeHead[field.Name]}exportTitle = append(exportTitle, excelTag)}// 排序sort.Slice(exportTitle, func(i, j int) bool {return exportTitle[i].Index < exportTitle[j].Index})var titleRowData []interface{} // 列头行for i, colTitle := range exportTitle {endColName := GetExcelColumnName(i + 1)if colTitle.Width > 0 { // 根据给定的宽度设置列宽_ = e.F.SetColWidth(sheet, endColName, endColName, float64(colTitle.Width))} else {_ = e.F.SetColWidth(sheet, endColName, endColName, float64(20)) // 默认宽度为20}titleRowData = append(titleRowData, colTitle.Name)}endColName = GetExcelColumnName(len(titleRowData)) // 根据列数生成 Excel 列名if title != "" {dataRow = 3 // 如果有title,那么从第3行开始就是数据行,第1行是title,第2行是表头e.F.SetCellValue(sheet, "A1", title)e.F.MergeCell(sheet, "A1", endColName+"1") // 合并标题单元格e.F.SetCellStyle(sheet, "A1", endColName+"1", e.TitleStyle)e.F.SetRowHeight(sheet, 1, float64(30)) // 第一行行高e.F.SetRowHeight(sheet, 2, float64(30)) // 第二行行高e.F.SetCellStyle(sheet, "A2", endColName+"2", e.HeadStyle)if err = e.F.SetSheetRow(sheet, "A2", &titleRowData); err != nil {return}} else {dataRow = 2 // 如果没有title,那么从第2行开始就是数据行,第1行是表头e.F.SetRowHeight(sheet, 1, float64(30))e.F.SetCellStyle(sheet, "A1", endColName+"1", e.HeadStyle)if err = e.F.SetSheetRow(sheet, "A1", &titleRowData); err != nil {return}}return
}// 构造数据行
func normalBuildDataRow(e *Excel, sheet, endColName, fields string, row int, isGhbj, isIgnore bool, dataValue reflect.Value) (err error) {//实时写入数据for i := 0; i < dataValue.Len(); i++ {startCol := fmt.Sprintf("A%d", row)endCol := fmt.Sprintf("%s%d", endColName, row)item := dataValue.Index(i)typ := item.Type()num := item.NumField()var exportRow []ExcelTagmaxLen := 0 // 记录这一行中,数据最多的单元格的值的长度//遍历结构体的所有字段for j := 0; j < num; j++ {dataField := typ.Field(j) //获取到struct标签,需要通过reflect.Type来获取tag标签的值tagVal := dataField.Tag.Get(ExcelTagKey)if tagVal == "" { // 如果非导出则跳过continue}if fields != "" { // 选择要导出或要忽略的字段if isIgnore && strings.Contains(fields, dataField.Name+",") { // 忽略指定字段continue}if !isIgnore && !strings.Contains(fields, dataField.Name+",") { // 导出指定字段continue}}var dataCol ExcelTagerr = dataCol.GetTag(tagVal)fieldData := item.FieldByName(dataField.Name) // 取字段值rwsTemp := fieldData.Len() // 当前单元格内容的长度if rwsTemp > maxLen { //这里取每一行中的每一列字符长度最大的那一列的字符maxLen = rwsTemp}// 替换if dataCol.Replace != "" {split := strings.Split(dataCol.Replace, ",")for j := range split {s := strings.Split(split[j], "_") // 根据下划线进行分割,格式:需要替换的内容_替换后的内容if s[0] == fieldData.String() {dataCol.Value = s[1]}}} else {dataCol.Value = fieldData}if err != nil {return}exportRow = append(exportRow, dataCol)}// 排序sort.Slice(exportRow, func(i, j int) bool {return exportRow[i].Index < exportRow[j].Index})var rowData []interface{} // 数据列for _, colTitle := range exportRow {rowData = append(rowData, colTitle.Value)}if isGhbj && row%2 == 0 {_ = e.F.SetCellStyle(sheet, startCol, endCol, e.ContentStyle2)} else {_ = e.F.SetCellStyle(sheet, startCol, endCol, e.ContentStyle1)}if maxLen > 25 { // 自适应行高d := maxLen / 25f := 25 * d_ = e.F.SetRowHeight(sheet, row, float64(f))} else {_ = e.F.SetRowHeight(sheet, row, float64(25)) // 默认行高25}if err = e.F.SetSheetRow(sheet, startCol, &rowData); err != nil {return}row++}return
}// 下载
func DownLoadExcel(fileName string, res http.ResponseWriter, file *excelize.File) {// 设置响应头res.Header().Set("Content-Type", "text/html; charset=UTF-8")res.Header().Set("Content-Type", "application/octet-stream")res.Header().Set("Content-Disposition", "attachment; filename="+fileName+".xlsx")res.Header().Set("Access-Control-Expose-Headers", "Content-Disposition")err := file.Write(res) // 写入Excel文件内容到响应体if err != nil {http.Error(res, err.Error(), http.StatusInternalServerError)return}
}
测试
ok,终于写完了导入导出,接下来就是测试啦
在 excel_main.go
文件中
package mainimport ("fmt""github.com/xuri/excelize/v2""go-web/util/excel"
)func main() {//export()imports()
}type Test struct {Id string `excel:"name:用户账号;"`Name string `excel:"name:用户姓名;index:1;"`Email string `excel:"name:用户邮箱;width:25;"`Com string `excel:"name:所属公司;"`Dept string `excel:"name:所在部门;"`RoleKey string `excel:"name:角色代码;"`RoleName string `excel:"name:角色名称;replace:1_超级管理员,2_普通用户;"`Remark string `excel:"name:备注;width:40;"`
}// 导出
func export() {var testList = []Test{{"fuhua", "符华", "fuhua@123.com", "太虚剑派", "开发部", "CJGLY", "1", "备注备注"},{"baiye", "白夜", "baiye@123.com", "天命科技有限公司", "执行部", "PTYG", "2", ""},{"chiling", "炽翎", "chiling@123.com", "太虚剑派", "行政部", "PTYG", "2", "备注备注备注备注"},{"yunmo", "云墨", "yunmo@123.com", "太虚剑派", "财务部", "CJGLY", "1", ""},{"yuelun", "月轮", "yuelun@123.com", "天命科技有限公司", "执行部", "CJGLY", "1", ""},{"xunyu", "迅羽","xunyu@123.com哈哈哈哈哈哈哈哈这里是最大行高测试哈哈哈哈哈哈哈哈这11111111111里是最大行高测试哈哈哈哈哈哈哈哈这里是最大行高测试","天命科技有限公司", "开发部", "PTYG", "2","备注备注备注备注com哈哈哈哈哈哈哈哈这里是最大行高测试哈哈哈哈哈哈哈哈这里是最大行高测试哈哈哈哈哈哈哈哈这里是最大行高测里是最大行高测试哈哈哈哈哈哈哈哈这里是最大行高测试"},}changeHead := map[string]string{"Id": "账号", "Name": "真实姓名"}//f, err := excel.NormalExport(testList, "Sheet1", "用户信息", "Id,Email,", true, true, changeHead)f, err := excel.NormalDynamicExport(testList, "Sheet1", "用户信息", "", true, false, changeHead)if err != nil {fmt.Println(err)return}f.Path = "C:\\Users\\Administrator\\Desktop\\测试.xlsx"if err := f.Save(); err != nil {fmt.Println(err)return}
}// 导入
func imports() {f, err := excelize.OpenFile("C:\\Users\\Administrator\\Desktop\\测试.xlsx")if err != nil {fmt.Println("文件打开失败")}importList := []Test{}err = excel.ImportExcel(f, &importList, 1, 2)if err != nil {fmt.Println(err)}for _, t := range importList {fmt.Println(t)}
}
实现效果
然后我们再来看看实现效果,说实话,我觉得这表格还挺好看的哩,不愧是我
导出
导入
最后
这样,我们就实现了一个通用的导入导出工具封装。
上面这些就是全部代码啦,后续等我把剩下几个复杂导出弄完(挖坑…),我会把这些代码全部抽出来,做成一个独立的组件模块,然后上传到Git上,这样以后不管做哪个项目,用的时候直接在go.mod引入就可以啦~完美😁
好啦,以上就是本篇文章的全部内容了,如果你觉得对你有帮助或者觉得博主写得不错,千万不要吝啬你的大拇指哟(^U^)ノ~YO
相关文章:
【Go】excelize库实现excel导入导出封装(一),自定义导出样式、隔行背景色、自适应行高、动态导出指定列、动态更改表头
前言 最近在学go操作excel,毕竟在web开发里,操作excel是非常非常常见的。这里我选择用 excelize 库来实现操作excel。 为了方便和通用,我们需要把导入导出进行封装,这样以后就可以很方便的拿来用,或者进行扩展。 我参…...
【开发篇】二十、SpringBoot整合RocketMQ
文章目录 1、整合2、消息的生产3、消费4、发送异步消息5、补充:安装RocketMQ 1、整合 首先导入起步依赖,RocketMQ的starter不是Spring维护的,这一点从starter的命名可以看出来(不是spring-boot-starter-xxx,而是xxx-s…...
OpenCV实现求解单目相机位姿
单目相机通过对极约束来求解相机运动的位姿。参考了ORBSLAM中单目实现的代码,这里用opencv来实现最简单的位姿估计. mLeftImg cv::imread(lImg, cv::IMREAD_GRAYSCALE); mRightImg cv::imread(rImg, cv::IMREAD_GRAYSCALE); cv::Ptr<ORB> OrbLeftExtractor …...
深入解析PostgreSQL:命令和语法详解及使用指南
文章目录 摘要引言基本操作安装与配置连接和退出 数据库操作创建数据库删除数据库切换数据库 表操作创建表删除表插入数据查询数据更新数据删除数据 索引和约束创建索引创建约束 用户管理创建用户授权用户修改用户密码 备份和恢复备份数据库恢复数据库 高级特性结语参考文献 摘…...
Elasticsearch数据搜索原理
Elasticsearch 是一个开源的、基于 Lucene 的分布式搜索和分析引擎,设计用于云计算环境中,能够实现实时的、可扩展的搜索、分析和探索全文和结构化数据。它具有高度的可扩展性,可以在短时间内搜索和分析大量数据。 Elasticsearch 不仅仅是一个…...
vue模版语法-{{}}/v-text/v-html/v-once
一、{{}}双括号:用于文本渲染 1、 {{变量名}}:data中返回对象的变量名 2、{{js表达式}}:可以直接进行js表达式处理 3、注意:双大括号中不要写等式书写 二、v-text 指令,用于文本渲染 1、为了解决双大括号渲染数据出现闪烁问题 三、v-cloak …...
前端埋点上传
没事看看: 从用户行为到数据:数据采集全景解析 | 人人都是产品经理 搭建前端监控,采集用户行为的 N 种姿势-前端监控设备 创业公司做数据分析(三)用户行为数据采集系统-CSDN博客...
第11章 Redis(一)
11.1 谈谈你对Redis的理解 难度:★★★ 重点:★★ 白话解析 对Redis的理解无非从三个方面去说一说:背景,是什么,特性。 背景:数据直接存磁盘太慢了,虽然MySQL用到了BufferPool等缓存,但是为了保证数据不丢失,MySQL采用的RedoLog依然要直接写磁盘。所以,数据的存储就…...
freertos信号量之二值信号量
freertos信号量之二值信号量 简介例程 简介 FreeRTOS的二值信号量(Binary Semaphore)是用于实现进程间同步和临界资源保护的重要工具。以下是一些二值信号量的常用函数及其说明: 1)xSemaphoreCreateBinary() 创建一个二值信号量…...
notepad++ 如何去除换行
选中下方的“扩展” “查找目标”输入:\r\n,替换为:空白 最后全部替换。...
PPT NO.2 插入透明校徽
插入透明校徽: ①先下载一个校徽: ②用矢量网站转换一下,这个免费的,很多其他的要钱钱: 位图转矢量图,JPG转矢量,PNG转矢量,GIF转矢量,BMP转矢量 - 在线工具 - 字客网 (fontke.com) 转换完了如下: 打…...
Linux系统部署PostgreSQL 单机数据库
安装方式 1 安装包方式 (Packages and Installers) 支持的操作系统包括 liunxMacosWindowsBSDSolaris 2 源码安装 (Source code) 下载源码包 通过下载地址PostgreSQL: File Browser 可以看到有各个版本的源码目录 选择13.1…...
好用的办公摸鱼神器
http://t.chaojizhu.cn/fawork/Down?uid180819...
手写Java序列化工具
一、思考 假设给一个java bean,让你按照 json 的格式打印出来,你会怎么做? 比如这个java bean 长这样,并且创建了一个叫宝儿姐的朋友 package com.test;public class User {private String name;private Integer age;private Bi…...
mysql面试题26:MySQL中什么是MVCC,它的底层原理是什么
该文章专注于面试,面试只要回答关键点即可,不需要对框架有非常深入的回答,如果你想应付面试,是足够了,抓住关键点 面试官:什么是MVCC,它的底层原理是什么? MVCC(Multi-Version Concurrency Control)是一种并发控制机制,用于在数据库中实现并发事务的隔离性和一致性…...
SQL进阶 - SQL的编程规范
性能优化是一个很有趣的探索方向,将耗时耗资源的查询优化下来也是一件很有成就感的事情,但既然编程是一种沟通手段,那每一个数据开发者就都有义务保证写出的代码逻辑清晰,具有很好的可读性。 目录 引子 小试牛刀 答案 引言 …...
[NISACTF 2022]babyserialize - 反序列化+waf绕过【*】
[NISACTF 2022]babyserialize 一、解题过程二、思考总结(一)、关于题目的小细节(二)、关于弱类型比较技巧 一、解题过程 题目代码: <?php include "waf.php"; class NISA{public $fun"show_me_fl…...
docker部署Vaultwarden密码共享管理系统
Vaultwarden是一个开源的密码管理器,它是Bitwarden密码管理器的自托管版本。它提供了类似于Bitwarden的功能,允许用户安全地存储和管理密码、敏感数据和身份信息。 Vaultwarden的主要特点包括: 1. 安全的数据存储:Vaultwarden使…...
低代码开发技术选型
低代码的技术路径 低代码开发低代码开发优势低代码的技术路径1.表格驱动2.表单驱动3.数据模型4.领域模型 低代码的核心能力企业级低代码开发平台的11项关键能力低代码平台的流程引擎选型低代码平台的流程设计器选型低代码平台的表单设计器选型低代码平台的Vue.js 框架选型 低代…...
在vue2中,v-model和.sync的区别
最近在封装一个弹窗组件时,用了比较复杂的逻辑去做显示和隐藏的逻辑,在查看同事的代码之后,才知道还有更简单的方法,自己已经忘了一些API. popup组件里统一的template: <div v-ifisShowPopup> // 弹窗内容 <…...
nginx 配置
一、nginx安装 下载地址:http://nginx.org/en/download.html,和Keepalived搭配使用,防止nginx挂掉 二、nginx配置 ########### 每个指令必须有分号结束。################# #user administrator administrators; #配置用户或者组…...
【计算机视觉|人脸建模】学习从图像中回归3D面部形状和表情而无需3D监督
本系列博文为深度学习/计算机视觉论文笔记,转载请注明出处 标题:Learning to Regress 3D Face Shape and Expression from an Image without 3D Supervision 链接:[1905.06817] Learning to Regress 3D Face Shape and Expression from an I…...
Linux系统之部署h5ai目录列表程序
Linux系统之部署h5ai目录列表程序 一、h5ai介绍1.1 h5ai简介1.2 h5ai特点 二、本地环境介绍2.1 本地环境规划2.2 本次实践介绍 三、检查本地环境3.1 检查本地操作系统版本3.2 检查系统内核版本 四、安装httpd软件4.1 检查yum仓库4.2 安装httpd软件4.3 启动httpd服务4.4 查看htt…...
Java-Exception
目录 异常概念ErrorException 体系图常见运行时异常NullPointerExceptionArithmeticExceptionArrayIndexOutOfBoundExceptionClassCastExceptionNumberFormatException 常见的编译异常异常处理机制自定义异常throw和throws对比 异常是Java编程中的常见问题,了解如何…...
C++并发与多线程(2) | 线程运行开始和结束的基本方式
当程序运行起来,生成一个进程,该进程所属的主线程开始自动运行。当主线程从main()函数返回,则整个进程执行完毕。 主线程从main()开始执行,那么我们自己创建的线程,也需要从一个函数开始运行(初始函数),一旦这个函数运行完毕,就代表着我们这个线程运行结束。 整个进…...
vue3前端开发-flex布局篇
文章目录 1.传统布局与flex布局优缺点传统布局flex布局建议 2. flex布局原理2.1 布局原理 3. flex常见属性3.1 父项常见属性3.2 子项常见属性 4.案例实战(携程网首页) 1.传统布局与flex布局优缺点 传统布局 兼容性好布局繁琐局限性,不能再移动端很好的布局 flex布…...
网络是什么?(网络零基础入门篇)
1.如何理解局域网和广域网? 2.路由器和交换机是怎么样工作的? 3.三层交换机能不能代替路由器? -- 局域网 广域网 -- 企业网架构,运营商架构,数据中心架构 -- 局域网 通过 交换机连接的 转发 相同的ip地址…...
【JavaEE】线程安全的集合类
文章目录 前言多线程环境使用 ArrayList多线程环境使用队列多线程环境使用哈希表1. HashTable2. ConcurrentHashMap 前言 前面我们学习了很多的Java集合类,像什么ArrayList、Queue、HashTable、HashMap等等一些常用的集合类,之前使用这些都是在单线程中…...
【C++算法】is_partitioned、partition_copy和partition_point
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、is_partitioned函数:1.1 is_partitioned是什么?1.2 函数原型1.3 示例代码1.4 更多示例代码 二、partition_copy函数2.1 概念2.2 函数…...
MyBatis(JavaEE进阶系列4)
目录 前言: 1.MyBatis是什么 2.为什么要学习MyBatis框架 3.MyBatis框架的搭建 3.1添加MyBatis框架 3.2设置MyBatis配置 4.根据MyBatis写法完成数据库的操作 5.MyBatis里面的增删改查操作 5.1插入语句 5.2修改语句 5.3delete语句 5.4查询语句 5.5like查…...
专做日淘的网站/最近三天的新闻大事摘抄
※ 写在前面Hi 各位,是我旅客君,又和大家见面了,大家还记得之前 MacBook 的体验评测吗?非常感谢大家对我的支持,这次就继续为大家带来这台 MacBook Pro 搭配显卡扩展坞的体验评测。如果还没有看过的可以先点击一下这个…...
游戏私服发布网站怎么做/小程序商城制作一个需要多少钱
uedtior errorhandler 配置文件加载失败 这个uedtior太扯淡了,在本地idea和tomcat上各种没问题,一上线就不能上传图片,我试过了网上说的各种方法都不管用。最后我用一个十分扯淡的方式解决了。 我的框架是springboot 在后端有ueditor包&…...
化工厂网站建设/免费个人自助建站
基本模式匹配一切从最基本的开始。模式,是正规表达式最基本的元素,它们是一组描述字符串特征的字符。模式可以很简单,由普通的字符串组成,也可以非常复杂,往往用特殊的字符表示一个范围内的字符、重复出现,…...
品牌全案策划案例/上海谷歌seo
1、类加载机制 虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。 2、类加载的时机 类从被加载到虚拟机内存中开始,到卸载出…...
沈阳做网站有名公司/热搜关键词查询
文章目录一、App应用配置总结1.app应用一、App应用配置 在每个应用目录中都包含了apps.py文件,用于保存该应用的相关信息。 在创建应用时,Django会向apps.py文件中写入一个该应用的配置类,如 from django.apps import AppConfigclass Book…...
wordpress 主题大学/百度怎么投广告
时 间:2015-02-05 08:17:11作 者:摘 要:连接SQL Server 数据库出错的解决方案正 文:经常有人反映说SQL Server 客户端连接不上。现在将这类问题归纳如下:一、SQL Server 实例(服务)未启动打开“SQL Server 配置管理器”(或者“管理工具”中的“服务”)ÿ…...