使用Go语言的gorm框架查询数据库并分页导出到Excel实例

news2024/11/24 23:46:28

文章目录

  • 基本配置
    • 配置文件管理
    • 命令行工具: Cobra
      • 快速入门
      • 基本用法
  • 生成mock数据
    • SQL准备
    • gorm自动生成结构体代码
    • 生成mock数据
  • 查询数据
  • 导出Excel
    • 使用 excelize
    • 实现思路
    • 完整代码参考
  • 入口文件
  • 效果演示
    • 分页导出多个Excel文件
    • 合并为一个完整的Excel文件
  • 完整代码

基本配置

配置文件管理

添加依赖 go get github.com/spf13/viper,支持 JSON, TOML, YAML, HCL 等格式的配置文件。

在项目根目录下面新建 conf 目录,然后新建 application.yml 文件,此文件需要忽略版本控制。每次修改后,记得同步修改 conf/application.yml.demo 文件,让别人也知道你添加或修改了哪些内容。

server:
  port: 8080
datasource:
  driverName: mysql
  host: "127.0.0.1"
  port: "3306"
  database: go-demo-2025
  username: root
  password: "123456"
  charset: utf8
  loc: Asia/Shanghai

配置初始化: common/initialization.go

// 配置初始化
func InitConfig() {
	workDir, _ := os.Getwd()               //获取目录对应的路径
	viper.SetConfigName("application")     //配置文件名
	viper.SetConfigType("yml")             //配置文件类型(后缀名)
	viper.AddConfigPath(workDir + "/conf") //执行go run对应的路径配置
	fmt.Println(workDir)
	err := viper.ReadInConfig()
	if err != nil {
		panic(err)
	}
}

数据库配置: 使用 gorm 初始化数据库配置,参考 common/database.go 文件

var DB *gorm.DB

// https://gorm.io/zh_CN/docs/index.html
func InitDB() *gorm.DB {
	//从配置文件中读取数据库配置信息
	host := viper.GetString("datasource.host")
	port := viper.Get("datasource.port")
	database := viper.GetString("datasource.database")
	username := viper.GetString("datasource.username")
	password := viper.GetString("datasource.password")
	charset := viper.GetString("datasource.charset")
	loc := viper.GetString("datasource.loc")
	args := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=%s&parseTime=true&loc=%s",
		username,
		password,
		host,
		port,
		database,
		charset,
		url.QueryEscape(loc))
	//fmt.Println(args)
	db, err := gorm.Open(mysql.Open(args), &gorm.Config{
		Logger: logger.Default.LogMode(logger.Info), //配置日志级别,打印出所有的sql
	})
	if err != nil {
		fmt.Println(err)
		panic("failed to connect database, err: " + err.Error())
	}
	DB = db
	return db
}

命令行工具: Cobra

Cobra是Go的CLI框架。它包含一个用于创建强大的现代CLI应用程序的库和一个用于快速生成基于Cobra的应用程序和命令文件的工具。

简单理解, 类似于 thinkphp 封装的 php think xxx 的命令行工具.

Cobra 官网: https://cobra.dev

快速入门

  • 安装: go get github.com/spf13/cobra
  • 入口文件: command.go
  • 核心文件: cmd/cobra.go

基本用法

测试Demo: command/testCmd.go

执行: go run command.go testCmd --paramA 100 --paramB 200 hello your name

输出:

--- test 运行 ---
参数个数: 3
100
200
0=>hello
1=>your
2=>name

更多参考: https://www.cnblogs.com/niuben/p/13886555.html

生成mock数据

SQL准备

CREATE TABLE `user` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `user_id` bigint(20) unsigned NOT NULL COMMENT '用户编号',
  `name` varchar(255) NOT NULL DEFAULT '' COMMENT '用户姓名',
  `age` tinyint(4) unsigned NOT NULL DEFAULT '0' COMMENT '用户年龄',
  `address` varchar(255) NOT NULL DEFAULT '' COMMENT '地址',
  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '添加时间',
  `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `key_user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

gorm自动生成结构体代码

需要引入gorm.io/gen扩展,参考代码:gorm_generate_db_struct.go

package main

import (
	"gorm.io/driver/mysql"
	"gorm.io/gen"
	"gorm.io/gorm"
	"strings"
)

func main() {
	// 初始化配置
	common.InitConfig()

	// 连接数据库
	db := common.InitDB()

	// 生成实例
	g := gen.NewGenerator(gen.Config{
		// 相对执行`go run`时的路径, 会自动创建目录
		OutPath: "old_crm_models/query",

		// WithDefaultQuery 生成默认查询结构体(作为全局变量使用), 即`Q`结构体和其字段(各表模型)
		// WithoutContext 生成没有context调用限制的代码供查询
		// WithQueryInterface 生成interface形式的查询代码(可导出), 如`Where()`方法返回的就是一个可导出的接口类型
		Mode: gen.WithDefaultQuery | gen.WithQueryInterface,

		// 表字段可为 null 值时, 对应结体字段使用指针类型
		//FieldNullable: true, // generate pointer when field is nullable

		// 表字段默认值与模型结构体字段零值不一致的字段, 在插入数据时需要赋值该字段值为零值的, 结构体字段须是指针类型才能成功, 即`FieldCoverable:true`配置下生成的结构体字段.
		// 因为在插入时遇到字段为零值的会被GORM赋予默认值. 如字段`age`表默认值为10, 即使你显式设置为0最后也会被GORM设为10提交.
		// 如果该字段没有上面提到的插入时赋零值的特殊需要, 则字段为非指针类型使用起来会比较方便.
		FieldCoverable: false, // generate pointer when field has default value, to fix problem zero value cannot be assign: https://gorm.io/docs/create.html#Default-Values

		// 模型结构体字段的数字类型的符号表示是否与表字段的一致, `false`指示都用有符号类型
		FieldSignable: false, // detect integer field's unsigned type, adjust generated data type
		// 生成 gorm 标签的字段索引属性
		FieldWithIndexTag: false, // generate with gorm index tag
		// 生成 gorm 标签的字段类型属性
		FieldWithTypeTag: true, // generate with gorm column type tag
	})
	// 设置目标 db
	g.UseDB(db)

	// 自定义字段的数据类型
	// 统一数字类型为int64,兼容protobuf
	dataMap := map[string]func(detailType string) (dataType string){
		"tinyint":   func(detailType string) (dataType string) { return "int64" },
		"smallint":  func(detailType string) (dataType string) { return "int64" },
		"mediumint": func(detailType string) (dataType string) { return "int64" },
		"bigint":    func(detailType string) (dataType string) { return "int64" },
		"int":       func(detailType string) (dataType string) { return "int64" },
	}
	// 要先于`ApplyBasic`执行
	g.WithDataTypeMap(dataMap)

	// 自定义模型结体字段的标签
	// 将特定字段名的 json 标签加上`string`属性,即 MarshalJSON 时该字段由数字类型转成字符串类型
	jsonField := gen.FieldJSONTagWithNS(func(columnName string) (tagContent string) {
		//toStringField := `balance, `
		toStringField := ``
		if strings.Contains(toStringField, columnName) {
			return columnName + ",string"
		}
		return columnName
	})
	// 将非默认字段名的字段定义为自动时间戳和软删除字段;
	// 自动时间戳默认字段名为:`updated_at`、`created_at, 表字段数据类型为: INT 或 DATETIME
	// 软删除默认字段名为:`deleted_at`, 表字段数据类型为: DATETIME
	//autoUpdateTimeField := gen.FieldGORMTag("update_time", "column:update_time;type:int unsigned;autoUpdateTime")
	//autoCreateTimeField := gen.FieldGORMTag("create_time", "column:create_time;type:int unsigned;autoCreateTime")
	// 模型自定义选项组
	//fieldOpts := []gen.ModelOpt{jsonField, autoCreateTimeField, autoUpdateTimeField}
	fieldOpts := []gen.ModelOpt{jsonField}

	// 创建模型的结构体,生成文件在 model 目录; 先创建的结果会被后面创建的覆盖
	// 创建全部模型文件, 并覆盖前面创建的同名模型
	allModel := g.GenerateAllTable(fieldOpts...)

	// 创建模型的方法,生成文件在 query 目录; 先创建结果不会被后创建的覆盖
	g.ApplyBasic(allModel...)

	g.Execute()
}

参考: https://segmentfault.com/a/1190000042502370

生成的结构体代码如下:

type User struct {
	ID         int64     `gorm:"column:id;type:int(11) unsigned;primaryKey;autoIncrement:true;comment:ID" json:"id"`                  // ID
	UserID     int64     `gorm:"column:user_id;type:bigint(20) unsigned;not null;comment:用户编号" json:"user_id"`                        // 用户编号
	Name       string    `gorm:"column:name;type:varchar(255);not null;comment:用户姓名" json:"name"`                                     // 用户姓名
	Age        int64     `gorm:"column:age;type:tinyint(4) unsigned;not null;comment:用户年龄" json:"age"`                                // 用户年龄
	Address    string    `gorm:"column:address;type:varchar(255);not null;comment:地址" json:"address"`                                 // 地址
	CreateTime time.Time `gorm:"column:create_time;type:datetime;not null;default:CURRENT_TIMESTAMP;comment:添加时间" json:"create_time"` // 添加时间
	UpdateTime time.Time `gorm:"column:update_time;type:datetime;not null;default:CURRENT_TIMESTAMP;comment:更新时间" json:"update_time"` // 更新时间
}

生成mock数据

代码路径:service/users/userService.go

// 批量添加mock数据
func (ctx *UserService) BatchCreateMockData() {
	for i := 1; i <= 82; i++ { //循环操作82次
		var users []*model.User
		for j := 1; j <= 100; j++ { //一次添加100条数据
			userid := "1" + fmt.Sprintf("%03d", i) + fmt.Sprintf("%03d", j)
			useridInt, _ := strconv.Atoi(userid)
			users = append(users, &model.User{
				UserID:  int64(useridInt),
				Name:    funcUtils.GenerateRandomChineseName(),
				Age:     int64(common.GenerateRandomNumber()),
				Address: funcUtils.GenerateRandomChinaAddress(),
			})
		}
		err := ctx.GormDB.Create(users).Error
		if err != nil {
			fmt.Println(fmt.Sprintf("第 %d 页添加失败,错误原因:%s", i, err))
		} else {
			fmt.Println(fmt.Sprintf("第 %d 页添加成功", i))
		}
	}
}

备注:以上代码中关于测试数据生成的工具:

  • 中国地址生成器 https://github.com/GoFinalPack/chinese-address-generator
  • 随机生成中国人姓名 https://www.jianshu.com/p/bab0994647b3

在这里插入图片描述
生成的测试数据效果如下:
在这里插入图片描述

查询数据

gorm 查询方法文档: https://gorm.io/zh_CN/docs/query.html

定义查询总数和查询列表的方法,代码路径:service/users/userService.go

// 查询总数
func (ctx *UserService) GetUserCount() int64 {
	var count int64
	err := ctx.GormDB.Model(&model.User{}).Where("1=1").Count(&count).Error
	if err != nil {
		fmt.Println(fmt.Sprintf("查询总数错误:%s", err))
		return 0
	}
	//fmt.Println(fmt.Sprintf("总条数:%d", count))
	return count
}

func (ctx *UserService) GetUserList(page int, pageSize int) []model.User {
	var dataList []model.User
	offset := (page - 1) * pageSize //偏移量
	err2 := ctx.GormDB.Select("*").Where("1=1").Order("id asc").Limit(pageSize).Offset(offset).Find(&dataList).Error
	if err2 != nil {
		fmt.Println(fmt.Sprintf("查询列表错误:%s", err2))
		return nil
	}
	return dataList
}

导出Excel

使用 excelize

使用率高的几个扩展

  • excelize (github.com/xuri/excelize/v2)
  • excelize (github.com/360EntSecGroup-Skylar/excelize)
  • xlsx (github.com/tealeg/xlsx/v3)
  • xxhash (github.com/OneOfOne/xxhash)

相关 Excel 开源类库性能对比: https://xuri.me/excelize/zh-hans/performance.html

此处以 github.com/xuri/excelize/v2 为例演示常用的Excel操作方法. 官方中文文档: https://xuri.me/excelize/zh-hans/

安装excelize go get github.com/xuri/excelize/v2

导出 Excel 文档参考代码:

var filePath string

func init() {
	filePath = fmt.Sprintf("files/%s", time.Now().Format("2006/01/02/"))
}

// WriteExcel 导出 Excel 文档
// data: 要导出的数据
// return: 文件名, error
func WriteExcel(data [][]string, relativePath string, fileName string) (string, error) {
	//创建存放目录
	relativeFilePath := filePath + relativePath + "/"
	_, err := os.ReadDir(relativeFilePath)
	if err != nil {
		// 不存在就创建
		err = os.MkdirAll(relativeFilePath, fs.ModePerm)
		if err != nil {
			fmt.Println(err)
		}
	}

	//创建表格
	file := excelize.NewFile()
	sheetName := "Sheet1"
	index, _ := file.NewSheet(sheetName)
	for i, row := range data {
		for j, val := range row {
			// 列和行 数字索引转excel坐标索引
			cellName, _ := excelize.CoordinatesToCellName(j+1, i+1)
			//fmt.Println("cellName:", cellName)
			// 写入sheet
			file.SetCellValue(sheetName, cellName, val)
		}
	}
	file.SetActiveSheet(index)

	//导出文件
	filePathName := relativeFilePath + fileName + "_" + common.GetMicroTimestamp() + ".xlsx"
	err = file.SaveAs(filePathName)
	if err != nil {
		return "", err
	}
	return filePathName, nil
}

实现思路

导出数据部分,考虑到数据量可能较大,如果一次性查询全量数据,可能造成内存或CPU爆满,因此不建议一次性全部导出,而是采用分页导出到多个文件,然后再将多个文件合并为一个Excel表格文件。

这里需要注意一个细节,就是正常导出的表格数据一般都是按照id(或者添加时间)倒序排列,最新的在前面。但是由于使用了分页导出,如果我们采用 order by id desc limit xxx offset xxx 有可能在分页查询的过程中产生新的数据,那么分页的偏移量(offset)可能导致出现重复数据,就是第一页的某一条数据有可能在第二页重复出现(应该很好理解吧?)。所以查询数据的时候需要 order by id asc 按照 id 从小到大的顺序导出数据就可以避免这个问题。

分页导出后,需要对整体顺序再次反转,最后合并的表格数据才能是按照 由新到旧 的顺序的结果。

// 二维数组/切片 反转
func ReverseTwoDimSlice(slice [][]string) {
	// 按照子切片的第0个元素进行倒序排列
	sort.Slice(slice, func(i, j int) bool {
		return slice[i][0] > slice[j][0] // 返回true表示i在j之前
	})
}

导出的表头,可以考虑使用上面 gorm 生成的 struct 部分,通过反射可以获取,核心代码如下:

func GetStructTag(data any) []string {
	t := reflect.TypeOf(data)
	var result []string
	for i := 0; i < t.NumField(); i++ {
		field := t.Field(i)
		jsonTag := field.Tag.Get("json")
		//fmt.Printf("Field: %s, Tag: %s\n", field.Name, jsonTag)
		result = append(result, jsonTag)
	}
	return result
}

上面代码传入的 data参数为 model 结构体的 tag 的 json 部分:
在这里插入图片描述
如果需要指定导出的表头字段,可以如下定义:

dataKeySlice = [][]string{
	{"id", "ID"},
	{"user_id", "用户ID"},
	{"name", "用户姓名"},
	{"age", "年龄"},
	{"address", "地址"},
	{"create_time", "添加时间"},
	{"update_time", "修改时间"},
}
dataKeys, dataKeysTitle := excelUtils.GetDataKeyAndTitle(dataKeySlice)

// 根据自定义的二维切片,封装导出的表头字段的key和title
func GetDataKeyAndTitle(dataKeySlice [][]string) ([]string, []string) {
	//自定义导出的字段
	var dataKeys []string
	var dataKeysTitle []string

	if len(dataKeySlice) > 0 { //如果定义了key对应的字段值
		for _, v := range dataKeySlice {
			if len(v[1]) > 0 {
				dataKeysTitle = append(dataKeysTitle, v[1])
			} else if len(v[0]) > 0 {
				dataKeysTitle = append(dataKeysTitle, v[0])
			}

			if len(v[0]) > 0 {
				dataKeys = append(dataKeys, v[0])
			}
		}
	}
	return dataKeys, dataKeysTitle
}

导出多个Excel文件后,再对它们进行合并为一个Excel文件:

// 合并一个目录下的所有Excel文件
func MergeExcel(dirPath string, outputFileName string, isDeleteOriginFiles bool) string {
	dir, err := ioutil.ReadDir(dirPath)
	if err != nil {
		fmt.Printf("open dir failed: %s\n", err.Error())
	}

	//设置路径,文件夹放在main的同级目录下
	PathSeperator := string(os.PathSeparator)
	outputdir := dirPath + "/../" + outputFileName

	//合并后的文件
	var new_file *xlsx.File
	var new_sheet *xlsx.Sheet
	new_file = xlsx.NewFile()
	var new_err error
	new_sheet, new_err = new_file.AddSheet("Sheet1")

	for _, fi := range dir {
		//fmt.Printf("open success: %s\n", Pthdir + PthSep+fi.Name())
		if new_err != nil {
			fmt.Printf(new_err.Error())
		}

		//读取文件
		xlFile, err := xlsx.OpenFile(dirPath + PathSeperator + fi.Name())
		if err != nil {
			fmt.Printf("open failed: %s\n", err)
		}
		for _, sheet := range xlFile.Sheets {
			//fmt.Printf("Sheet Name: %s\n", sheet.Name)

			num := 0
			for _, row := range sheet.Rows {
				num++
				//跳过前5行,将后面的行写入新的文件
				//if(num > 5){
				new_row := new_sheet.AddRow()
				//new_row.SetHeightCM(1)

				for _, cell := range row.Cells {
					text := cell.String()
					//fmt.Printf("%s\n", text)

					new_cell := new_row.AddCell()
					new_cell.Value = text
				}
				//}
			}
		}
	}

	//写入文件
	new_err = new_file.Save(outputdir)
	if new_err != nil {
		fmt.Printf(new_err.Error())
	}

	//是否删除原文件
	if isDeleteOriginFiles {
		os.RemoveAll(dirPath)
	}

	outputFilePath, _ := filepath.Abs(outputdir)
	return outputFilePath
}

完整代码参考

代码路径: service/users/userDataExportService.go

package users

import (
	"fmt"
	"go-demo-2025/common"
	"go-demo-2025/utils/excelUtils"
	"go-demo-2025/utils/funcUtils"
	"math"
	"os"
	"path/filepath"
	"strconv"
)

// 通过反射直接获取结构体中的所有数据字段,并转换为map,再根据key的顺序逐一映射到新的切片
func (ctx *UserService) ExportUserList() {
	requestKey := common.GetYmdHis() + "_" + common.RandomString(10)
	count := ctx.GetUserCount()
	count = 1300 //调试数据

	//分页查询列表
	pageSize := 1000                                           //每页查询多少条
	pageCount := math.Ceil(float64(count) / float64(pageSize)) //总页数
	//fmt.Println("总页数:", pageCount)

	//自定义导出的字段
	var dataKeySlice [][]string
	//如果需要导出数据表的所有字段,则注释下面的二维切片
	dataKeySlice = [][]string{
		{"id", "ID"},
		{"user_id", "用户ID"},
		{"name", "用户姓名"},
		{"age", "年龄"},
		{"address", "地址"},
		{"create_time", "添加时间"},
		{"update_time", "修改时间"},
	}
	dataKeys, dataKeysTitle := excelUtils.GetDataKeyAndTitle(dataKeySlice)

	for page := 1; page <= int(pageCount); page++ {
		dataList := ctx.GetUserList(page, pageSize)

		var excelData [][]string
		for key, item := range dataList {
			//如果没有定义指定要导出的字段,则获取数据表的所有字段
			if len(dataKeys) == 0 { //dataKeys切片(model结构体的tag的json部分)
				dataKeys = funcUtils.GetStructTag(item)
			}

			//结构体转为map
			itemMap, _ := funcUtils.StructToMap(item, "json")
			//fmt.Println(itemMap)
			//os.Exit(1)

			//按照顺序将map中的数据填充到key的切片中
			var itemSlice []string
			//第一列使用key,下一步排序用
			itemSlice = append(itemSlice, fmt.Sprintf("%03d", key)) //key前面补两个0,要不然反转的时候会按照字符串顺序排序,导致"2>10".这样改后就是"10>02")
			for _, keys := range dataKeys {                         //按照dataKeys设定的字段,逐一插入到切片中
				itemSlice = append(itemSlice, itemMap[keys])
			}
			excelData = append(excelData, itemSlice)
		}

		funcUtils.ReverseTwoDimSlice(excelData)                     //倒序排列,必须保证第0个元素是 key 值
		excelData = funcUtils.DeleteTwoDimSliceFirstChar(excelData) //删除第0个元素(key值)
		pageDiff := int(pageCount) - page + 1

		//导出Excel文件
		_, err1 := excelUtils.WriteExcel(excelData, requestKey, fmt.Sprintf("用户数据导出_page_%s", fmt.Sprintf("%04d", pageDiff)))
		if err1 != nil {
			fmt.Println("Write excel error: ", err1)
			os.Exit(1)
		}
		//fmt.Println("Write excel success, file name is: ", fileName)
	}

	//导出表头
	var excelDataTitle [][]string
	if len(dataKeysTitle) == 0 {
		dataKeysTitle = dataKeys
	}
	excelDataTitle = append(excelDataTitle, dataKeysTitle)
	fileName, _ := excelUtils.WriteExcel(excelDataTitle, requestKey, "用户数据导出_page_0000")

	//合并多个文件为一个
	absPath, _ := filepath.Abs(fileName) // 获取文件的绝对路径
	dirPath := filepath.Dir(absPath)     //获取文件所在目录的绝对路径
	//fmt.Println(dirPath)
	outputFileName := fmt.Sprintf("用户数据导出_%v.xlsx", requestKey)
	outputFilePath := excelUtils.MergeExcel(dirPath, outputFileName, true)
	fmt.Println("最终导出的文件:", outputFilePath)
}

入口文件

新增命令行脚本: command/userCmd.go

package command

import (
	"fmt"
	"github.com/spf13/cobra"
	"go-demo-2025/service/users"
	"os"
	"time"
)

// go run command.go userCmd --operate exportData
func init() {
	RootCmd.AddCommand(userCmd)
}

var userCmd = &cobra.Command{
	Use:   "userCmd",
	Short: "关于用户相关的命令行操作",
	Long:  ``,
	Run: func(cmd *cobra.Command, args []string) {
		fmt.Println("--- userCmd 运行 ---")
		userCmdRun(args)
	},
}

func userCmdRun(args []string) {
	if operate == "" {
		fmt.Println("缺少参数operate")
		os.Exit(1)
	}

	if operate == "createMockData" { //生成mock数据
		fmt.Println("执行createMockData...")
		service := users.NewUserService()
		service.BatchCreateMockData()
	} else if operate == "exportData" { //导出用户数据
		fmt.Println("执行exportData...")
		service := users.NewUserService()

		startTime := time.Now()
		service.ExportUserList()
		endTime := time.Now()
		timeCost := endTime.Sub(startTime)
		fmt.Println("总耗时: ", timeCost)
	} else {
		fmt.Println("暂未定义此operate的业务逻辑")
		os.Exit(1)
	}
}

效果演示

分页导出多个Excel文件

合并多个文件前的效果演示,在合并多个Excel部分的代码暂时终止一下,看看效果。
在这里插入图片描述

在项目根目录下执行命令 go run command.go userCmd --operate exportData
在这里插入图片描述
生成的文件在:项目根目录下面的 /files/年/月/日/xxx 下面:
在这里插入图片描述
其中 用户数据导出_page_0000_xxx 是表头数据:

在这里插入图片描述

合并为一个完整的Excel文件

接下来, 去掉刚才的 os.Exit(1) 再试一次直接导出一个完整的Excel文件,还是执行命令 go run command.go userCmd --operate exportData

在这里插入图片描述
这次直接合并为多个文件,并且删除之前的多个小文件。
在这里插入图片描述

完整代码

源代码:https://gitee.com/rxbook/go-demo-2025

下载后,解压到自定义目录,配置好 Go 环境,创建数据库go-demo-2025,导入 data/go-demo-2025.sql 的SQL语句,复制 conf/application.yml.democonf/application.yml 修改对应的数据为你自己的数据库连接信息。

  • 执行 go run command.go userCmd --operate createMockData 生成测试用的mock数据;
  • 执行 go run command.go userCmd --operate exportData 即可导出Excel文件;
  • 执行 go run quick_start_demo/gin_http_get.go 快速入门Gin框架http服务;
  • 执行 go run main.go 启动HTTP服务,进入 router/router.go 查看具体测试的路由信息。

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

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

相关文章

Vue环境安装以及配置

这里写目录标题 前言一、前置要求1.安装Node.js2. 安装VScode 二、创建全局安装目录和缓存日志目录三、配置环境变量四、权限五、配置镜像六、vscode插件1. Vue-Offical2. Vue 3 Snippets3. Path Intellisense4. Auto Import5. Auto Close Tag6. Auto Rename Tag7.GitLens总结 …

001 Hadoop安装、Spring整合测试

Hadoop安装、整合测试 文章目录 Hadoop安装、整合测试1.简介1.优点2.组成 2.安装1.安装jdk&#xff08;如已安装可跳过&#xff09;2.安装hadoop1.安装2. 修改配置文件core-site.xml3. 修改配置文件hdfs-site.xml4.启动hadoop5.启动yarn6.执行jps查看7.相关端口及配置位置8.访问…

2024年【制冷与空调设备安装修理】考试及制冷与空调设备安装修理新版试题

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 制冷与空调设备安装修理考试是安全生产模拟考试一点通总题库中生成的一套制冷与空调设备安装修理新版试题&#xff0c;安全生产模拟考试一点通上制冷与空调设备安装修理作业手机同步练习。2024年【制冷与空调设备安装…

STM32CubeIDE使用ADC采用DMA重大BUG

问题描述 STM32CubeIDE 1.8.0问题 大牛攻城狮最近调试STM32L151CBT6。由于项目上使用该款芯片做控制电源使用&#xff0c;其中涉及到多路ADC的数据采样。使用STM32CubeIDE 1.8.0版本详细如下图所示 注意这里的使用的软件版本号很关键。采用该款软件搭建工程&#xff0c;第一次…

架构设计笔记-12-信息系统架构设计理论与实践

目录 知识要点 案例分析 1.Java企业级应用系统 2.c/s架构&#xff0c;b/s架构 知识要点 软件架构风格是描述某一特定应用领域中系统组织方式的惯用模式。架构风格定义了一类架构所共有的特征&#xff0c;主要包括架构定义、架构词汇表和架构约束。 数据挖掘是从数据库的大…

HarmonyOS NEXT 应用开发实战(四、仿知乎日报的首页轮播图实现)

在本篇博文中&#xff0c;我们将探讨如何在HarmonyOS NEXT应用中实现一个仿知乎日报的首页轮播图效果。我们将使用Swiper组件来展示轮播图&#xff0c;并且在轮播图下方添加半透明背景的标题。以下是具体的实现步骤和代码示例。 1. 项目结构与数据源 首先&#xff0c;我们需要…

Django兴农购物网站系统—计算机毕业设计源码38256

摘 要 助农工作是当前我国全面建成小康社会的重点工作&#xff0c;由于我国农村地域广大&#xff0c;贫困人口多&#xff0c;区域差异大&#xff0c;因此&#xff0c;不同区域的扶贫方法也是不一样的。近年来&#xff0c;随着网络的普及。许多农村地区物产丰富&#xff0c;但由…

计组_中断响应的步骤

2024.10.13&#xff1a;计算机组成原理学习笔记 中断响应步骤 中断响应 &#xff08;中断响应的过程也称中断隐指令&#xff09;第一步&#xff1a;关中断第二步&#xff1a;保存断点第三步&#xff1a;引出中断服务程序中断源识别判优方法1&#xff1a;软件查询方法中断源识别…

Redis两种持久化方式

目录 一、Redis持久化 RDB 四种执行场景 底层执行原理 优缺点 AOP 三种fsync策略 AOF重写机制 工作基本流程 优缺点 RDB和AOF的对比 混合持久化 Redis 持久化的主要目的是为了确保数据的持久性和可靠性&#xff0c;避免因意外崩溃或重启导致的数据丢失。以下是一些进…

基于STM32的图形识别智能跟踪小车设计

引言 本项目设计了一个基于STM32的图形识别智能跟踪小车&#xff0c;能够通过摄像头识别特定图形或标志&#xff0c;并自动跟随这些图形进行移动。系统结合了摄像头模块和图像处理算法&#xff0c;实现了对前方物体的识别与跟踪。同时&#xff0c;小车具备避障功能&#xff0c…

顺序表|消失的数字|轮转数组|移除元素|合并有序数组|删除有序数组中重复项(C)

面试题 17.04. 消失的数字 思路一 排序遍历 如果下一个不等于上一个数1&#xff0c;这个下一个数就是消失的数字 时间复杂度 O ( N ⋅ log ⁡ 2 N ) O(N\cdot \log_{2}N) O(N⋅log2​N) 思路二 0N等差数列公式计算结果 - 数组中的值&#xff0c;结果就是消失的数字 int mis…

基于海市蜃楼算法(Fata Morgana Algorithm ,FATA)的多无人机协同三维路径规划(提供MATLAB代码)

一、海市蜃楼算法 海市蜃楼算法&#xff08;Fata Morgana Algorithm &#xff0c;FATA&#xff09;是2024年提出一种新型的群体智能优化算法&#xff0c;它的设计灵感来源于自然现象中的海市蜃楼形成过程。FATA算法通过模仿光线在不均匀介质中的传播方式&#xff0c;提出了两种…

日语学习者福音:4大翻译软件集锦,总有一款适合你!

现在全球化了&#xff0c;学个外语&#xff0c;尤其是日语这种很有魅力的语言&#xff0c;成了很多人的愿望。不管是为了方便旅行&#xff0c;还是喜欢日本文化&#xff0c;或者想在工作中更有竞争力&#xff0c;学日语都挺重要的。学语言不容易&#xff0c;特别是遇到难懂的句…

C#实现Punycode编码/解码

测试代码 string word "我爱你"; string idn "我爱你.中国"; string wordCode PunyCode.Encode(word); string punycode PunyCode.IDN2Punycode(idn);Console.WriteLine(word); Console.WriteLine(wordCode); Console.WriteLine(PunyCode.Decode(word…

【工欲善其事】巧用 PowerShell 自动清除复制 PDF 文本时夹杂的换行符号

文章目录 巧用 PowerShell 自动清除复制 PDF 文本时夹杂的换行符号1 问题描述2 解决方案3 具体步骤4 效果测试5 小结与复盘 巧用 PowerShell 自动清除复制 PDF 文本时夹杂的换行符号 1 问题描述 不知各位是否也为复制过来的文本中夹杂的回车换行符抓狂过&#xff1f;就是在复…

Spring Boot知识管理:提升信息检索效率

4系统概要设计 4.1概述 本系统采用B/S结构(Browser/Server,浏览器/服务器结构)和基于Web服务两种模式&#xff0c;是一个适用于Internet环境下的模型结构。只要用户能连上Internet,便可以在任何时间、任何地点使用。系统工作原理图如图4-1所示&#xff1a; 图4-1系统工作原理…

ubuntu下route命令详解

buntu下route命令详解 1、显示路由表 route -n2、临时路由设置&#xff0c;重启网卡失效#添加一条路由(发往192.168.62这个网段的全部要经过网关192.168.1.1)route add -net 192.168.62.0 netmask 255.255.255.0 gw 192.168.1.1#删除一条路由 删除的时候不用写网关route del …

extern “C“ 的作用、C++ 和 C 编译的不同、C++ 编译过程的五个主要阶段

在 C 中&#xff0c;如果需要从 C 语言导入函数或与 C 代码交互&#xff0c;需要使用 extern "C" 关键字。这是因为 C 和 C 在编译过程中的 符号命名机制&#xff08;即 "名称修饰" 或 "name mangling"&#xff09;不同。 1. extern "C&qu…

怎么把一段音频的人声和背景音乐分开?

在数字音频处理中&#xff0c;将一段音频中的人声和背景音乐分开是一个复杂但又常见的需求。这种技术广泛应用于音乐制作、影视后期、广告制作等多个领域。本文将为你详细解析如何通过不同的方法实现这一目标&#xff0c;帮助你更好地掌握音频分离技术。 一、音频分离的基本概念…

无人直播自动化回复客户咨询

我们插件是根据页面元素变动进行自动化操作的&#xff0c;想要实现网页版自动化&#xff0c;必须了解html以及dom结构&#xff0c;还有xpath定位方法。 各大直播后台页面结构不一样&#xff0c;所以要进行兼容处理&#xff0c;我们一个插件支持以下直播或客服平台 唯一客服浏…