GoLand 项目从 0 到 1:第二天 —— 数据库自动化

第二天核心任务:自动化与多数据库支持

第二天的开发聚焦于数据库自动化流程构建MongoDB 业务链路扩展,通过工具化手段解决数据库操作的重复性问题,同时完善多数据库支持能力。经过一天的开发,项目已实现数据库初始化、迁移、种子数据填充的全自动化,并完成 MongoDB 接口与模型的完整适配,基础工具链也同步升级以支撑新架构。
注:项目会用到多个数据库:mysql、mangodb、Neo4j,减少建表和初始化表的工作量

一、核心模块:数据库自动化流程

关键实现与代码解析

1. /main.go

新增数据库集中式初始化(只列举了mysql)

// 新版本 - 统一初始化入口
if err := database.InitMySQL(); err != nil {logger.Fatal("Failed to initialize databases:", err)
}
defer func() {err := database.CloseDB()if err != nil {logger.Fatal("initialize databases is exception:", err)}
}()
func InitMySQL() error {// 使用新的数据库初始化器initializer := NewDatabaseInitializer()return initializer.InitAllDatabases()
}
func (di *DatabaseInitializer) InitAllDatabases() error {logger.Info("Starting database initialization...")// 1. 初始化MySQLif err := di.initMySQL(); err != nil {return fmt.Errorf("failed to initialize MySQL: %w", err)}// 初始化MongoDB迁移和种子数据// ...省略...logger.Info("All databases initialized successfully")return nil
}
func (di *DatabaseInitializer) initMySQL() error {logger.Info("Initializing MySQL database...")// 1. 首先连接到MySQL服务器(不指定数据库)if err := di.connectToMySQLServer(); err != nil {return fmt.Errorf("failed to connect to MySQL server: %w", err)}// 2. 创建数据库(如果不存在 CREATE DATABASE)if err := di.createDatabase(); err != nil {return fmt.Errorf("failed to create database: %w", err)}// 3. 连接到指定数据库if err := di.connectToDatabase(); err != nil {return fmt.Errorf("failed to connect to database: %w", err)}// 4. 自动迁移表结构if err := di.autoMigrateTables(); err != nil {return fmt.Errorf("failed to migrate tables: %w", err)}// 5. 运行数据库迁移(如果启用)if di.config.Database.AutoMigrate {if err := di.runMigrations(); err != nil {return fmt.Errorf("failed to run migrations: %w", err)}}// 6. 运行种子数据(INSERT INTO)if di.config.Database.AutoSeed {if err := di.runSeeders(); err != nil {return fmt.Errorf("failed to run seeders: %w", err)}}logger.Info("MySQL database initialized successfully")return nil
}
// connectToMySQLServer 连接到MySQL服务器
func (di *DatabaseInitializer) connectToMySQLServer() error {cfg := di.config.Database// 连接到MySQL服务器(不指定数据库)dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/?charset=utf8mb4&parseTime=True&loc=Local",cfg.Username,cfg.Password,cfg.Host,cfg.Port,)var err errorDB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{Logger: gormlogger.Default.LogMode(gormlogger.Info),})if err != nil {return fmt.Errorf("failed to connect to MySQL server: %w", err)}// 配置连接池sqlDB, err := DB.DB()if err != nil {return fmt.Errorf("failed to get sql.DB: %w", err)}sqlDB.SetMaxOpenConns(cfg.MaxOpenConns)sqlDB.SetMaxIdleConns(cfg.MaxIdleConns)sqlDB.SetConnMaxLifetime(time.Duration(cfg.MaxLifetime) * time.Second)return nil
}
func (di *DatabaseInitializer) createDatabase() error {dbName := di.config.Database.Database// 检查数据库是否存在var count int64DB.Raw("SELECT COUNT(*) FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = ?", dbName).Scan(&count)if count == 0 {logger.Info("Creating database:", dbName)// 创建数据库createSQL := fmt.Sprintf("CREATE DATABASE `%s` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci", dbName)if err := DB.Exec(createSQL).Error; err != nil {return fmt.Errorf("failed to create database %s: %w", dbName, err)}logger.Info("Database created successfully:", dbName)} else {logger.Info("Database already exists:", dbName)}return nil
}
// autoMigrateTables 自动迁移表结构
func (di *DatabaseInitializer) autoMigrateTables() error {logger.Info("Starting table migration...")// 使用新的MySQL迁移管理器migrationManager := NewMySQLMigrationManager()if err := migrationManager.RunMigrations(); err != nil {return fmt.Errorf("failed to run migrations: %w", err)}logger.Info("Table migration completed successfully")return nil
}// RunMigrations 运行所有迁移
func (mm *MySQLMigrationManager) RunMigrations() error {logger.Info("Starting MySQL migrations...")// 定义所有需要迁移的模型models := []interface{}{&mysql.User{},&mysql.Demo{},// 在这里添加更多模型}// 执行迁移for _, model := range models {if err := mm.migrateModel(model); err != nil {return fmt.Errorf("failed to migrate model %T: %w", model, err)}}logger.Info("MySQL migrations completed successfully")return nil
}
// runSeeders 运行种子数据
func (di *DatabaseInitializer) runSeeders() error {logger.Info("Starting database seeding...")seeder := NewSeeder()if err := seeder.RunSeeders(); err != nil {return fmt.Errorf("failed to run seeders: %w", err)}logger.Info("Database seeding completed successfully")return nil
}
// RunSeeders 运行所有种子数据
func (s *Seeder) RunSeeders() error {logger.Info("Starting database seeding...")// 运行各种种子数据seeders := []func() error{s.seedUsers,s.seedDemos,// 在这里添加更多种子数据}for _, seeder := range seeders {if err := seeder(); err != nil {return fmt.Errorf("failed to run seeder: %w", err)}}logger.Info("Database seeding completed successfully")return nil
}
// seedUsers 种子用户数据
func (s *Seeder) seedUsers() error {// 检查是否已有用户数据var count int64if err := s.db.Model(&mysql.User{}).Count(&count).Error; err != nil {return fmt.Errorf("failed to count users: %w", err)}if count > 0 {logger.Info("Users already seeded, skipping...")return nil}// 创建默认管理员用户adminPassword, err := utils.HashPassword("admin123")if err != nil {return fmt.Errorf("failed to hash admin password: %w", err)}adminUser := mysql.User{Username: "admin",Password: adminPassword,Email:    "admin@example.com",Role:     "admin",Status:   1,}if err := s.db.Create(&adminUser).Error; err != nil {return fmt.Errorf("failed to create admin user: %w", err)}logger.Info("Users seeded successfully")return nil
}

二、基础能力升级:工具链适配新架构

  1. 雪花 ID 生成器(pkg/utils/snowflake.go)

    • 支持配置workerID,解决分布式部署时 ID 冲突问题
    • 处理时间回拨异常:系统时间回退时暂停生成,确保 ID 单调递增
package utilsimport ("fmt""sync""time"
)const (// 时间戳位数timestampBits = 41// 机器ID位数machineIDBits = 10// 序列号位数sequenceBits = 12// 最大值maxMachineID = (1 << machineIDBits) - 1maxSequence  = (1 << sequenceBits) - 1// 偏移量machineIDShift = sequenceBitstimestampShift = sequenceBits + machineIDBits// 起始时间戳 (2023-01-01 00:00:00 UTC)epoch = 1672531200000
)// Snowflake 雪花ID生成器
type Snowflake struct {mutex     sync.MutexmachineID int64sequence  int64lastTime  int64
}var (defaultSnowflake *Snowflakeonce             sync.Once
)// NewSnowflake 创建雪花ID生成器
func NewSnowflake(machineID int64) (*Snowflake, error) {if machineID < 0 || machineID > maxMachineID {return nil, fmt.Errorf("machine ID must be between 0 and %d", maxMachineID)}return &Snowflake{machineID: machineID,sequence:  0,lastTime:  0,}, nil
}// GetDefaultSnowflake 获取默认雪花ID生成器
func GetDefaultSnowflake() *Snowflake {once.Do(func() {var err errordefaultSnowflake, err = NewSnowflake(1) // 默认机器ID为1if err != nil {panic(fmt.Sprintf("failed to create default snowflake: %v", err))}})return defaultSnowflake
}// NextID 生成下一个ID
func (s *Snowflake) NextID() int64 {// 加锁保证线程安全,防止并发时序列号冲突s.mutex.Lock()defer s.mutex.Unlock() // 确保函数退出时自动解锁// 获取当前时间戳(毫秒级)now := time.Now().UnixMilli()// 时钟回拨检查:如果当前时间小于上次生成ID的时间// 说明系统时钟被回拨,返回0表示错误if now < s.lastTime {return 0}// 同一毫秒内的处理逻辑if now == s.lastTime {// 序列号递增,使用位与运算确保不超过最大值(4095)s.sequence = (s.sequence + 1) & maxSequence// 序列号溢出检查(当sequence从最大值加1后归零)if s.sequence == 0 {// 等待直到下一毫秒now = s.waitNextMillis(s.lastTime)}} else {// 新的时间窗口(毫秒),重置序列号为0s.sequence = 0}// 更新最后生成ID的时间戳s.lastTime = now// 生成IDid := ((now - epoch) << timestampShift) |(s.machineID << machineIDShift) |s.sequencereturn id
}// waitNextMillis 等待下一毫秒
func (s *Snowflake) waitNextMillis(lastTimestamp int64) int64 {timestamp := time.Now().UnixMilli()for timestamp <= lastTimestamp {timestamp = time.Now().UnixMilli()}return timestamp
}// GenerateID 生成雪花ID(使用默认生成器)
func GenerateID() int64 {return GetDefaultSnowflake().NextID()
}// ParseID 解析雪花ID
func ParseID(id int64) map[string]int64 {timestamp := (id >> timestampShift) + epochmachineID := (id >> machineIDShift) & maxMachineIDsequence := id & maxSequencereturn map[string]int64{"timestamp": timestamp,"machineID": machineID,"sequence":  sequence,}
}// GetTimestampFromID 从ID中获取时间戳
func GetTimestampFromID(id int64) int64 {return (id >> timestampShift) + epoch
}// GetMachineIDFromID 从ID中获取机器ID
func GetMachineIDFromID(id int64) int64 {return (id >> machineIDShift) & maxMachineID
}// GetSequenceFromID 从ID中获取序列号
func GetSequenceFromID(id int64) int64 {return id & maxSequence
}

2. Token生成/解析逻辑(pkg/auth/jwt.go

package auth  // 认证相关功能包import ("errors""time""golang-server/config"  // 项目配置模块"github.com/golang-jwt/jwt/v5"  // JWT官方库
)// Claims 自定义JWT声明结构,包含用户信息和标准声明
type Claims struct {UserID   int64  `json:"user_id"`   // 用户唯一标识Username string `json:"username"`  // 用户名Role     string `json:"role"`      // 用户角色jwt.RegisteredClaims              // 嵌入标准声明(过期时间、签发者等)
}// GenerateToken 生成JWT访问令牌
// @param userID 用户ID
// @param username 用户名
// @param role 用户角色
// @return 签名的令牌字符串
// @return 错误信息(如果有)
func GenerateToken(userID int64, username, role string) (string, error) {cfg := config.GetConfig()  // 获取应用配置// 初始化声明信息claims := Claims{UserID:   userID,Username: username,Role:     role,RegisteredClaims: jwt.RegisteredClaims{// 设置过期时间(从配置读取秒数)ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Duration(cfg.JWT.ExpireTime) * time.Second)),// 设置签发时间IssuedAt:  jwt.NewNumericDate(time.Now()),// 设置生效时间(立即生效)NotBefore: jwt.NewNumericDate(time.Now()),},}// 使用HS256算法创建带声明的令牌token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)// 使用配置密钥签名令牌return token.SignedString([]byte(cfg.JWT.Secret))
}// ParseToken 解析并验证JWT令牌
// @param tokenString 待验证的令牌字符串
// @return 解析后的声明信息
// @return 错误信息(如果令牌无效或过期)
func ParseToken(tokenString string) (*Claims, error) {cfg := config.GetConfig()  // 获取应用配置// 带声明解析令牌token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {// 验证签名算法是否正确if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {return nil, jwt.ErrSignatureInvalid}// 返回签名密钥return []byte(cfg.JWT.Secret), nil})if err != nil {return nil, err  // 返回解析错误(过期/格式错误等)}// 类型断言验证自定义声明if claims, ok := token.Claims.(*Claims); ok && token.Valid {return claims, nil  // 返回有效声明}return nil, errors.New("invalid token")  // 令牌无效
}// RefreshToken 刷新访问令牌
// @param tokenString 旧令牌字符串
// @return 新令牌字符串
// @return 错误信息(如果旧令牌无效)
func RefreshToken(tokenString string) (string, error) {// 解析旧令牌获取用户信息claims, err := ParseToken(tokenString)if err != nil {return "", err  // 旧令牌无效时返回错误}// 使用原有用户信息生成新令牌return GenerateToken(claims.UserID, claims.Username, claims.Role)
}

总结与次日计划

第二天通过数据库自动化解决了手动操作的繁琐与风险,通过MongoDB 扩展丰富了数据存储能力,基础工具链的升级则为后续开发奠定了稳定基础。
次日将进行需求评审及表设计,就先不更新了

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

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

相关文章

qt框架,使用webEngine如何调试前端

解决 Qt 5.14.2 中启用开发者工具的问题问题在于 Qt 5.14.2 中 QWebEngineSettings::DeveloperExtrasEnabled 属性已被弃用或更改。正确启用开发者工具的完整方法&#xff08;Qt 5.14.2&#xff09;1. 修改 main.cpp#include <QWebEngineView> #include <QWebEngineSe…

【Atlassian生态】Jira Cloud单站点现可支持10万用户:架构升级与龙智云迁移服务

作为Atlassian全球白金合作伙伴&#xff0c;龙智团队非常激动地宣布&#xff1a;Jira迎来历史性突破——Jira Cloud单个站点最高可支持10万用户&#xff01;覆盖Enterprise、Premium和Standard版本。现在&#xff0c;更多的团队可以将Jira作为核心协作中枢&#xff0c;以加速目…

深入解析JVM垃圾回收调优:性能优化实践指南

深入解析JVM垃圾回收调优&#xff1a;性能优化实践指南 一、技术背景与应用场景 随着互联网业务的飞速发展&#xff0c;Java 应用在高并发、大内存场景下对 JVM 性能提出了更高要求。垃圾回收&#xff08;Garbage Collection&#xff0c;GC&#xff09;作为 JVM 的核心组件之一…

万字解析Redission ---深入理解Redission上锁过程

Redisson获取锁过程 RLock lock redissonClient.getLock("lock:order" userId); boolean isLock lock.tryLock(1L, TimeUnit.SECONDS);调用tyrLock其实就是下面的方法&#xff0c;如果说没有指定锁的过期时间&#xff0c;可以看到这边设置为了-1Overridepublic bo…

NVM踩坑实录:配置了npm的阿里云cdn之后,下载nodejs老版本(如:12.18.4)时,报404异常,下载失败的问题解决

文章目录一、情景还原二、分析原因三、解决方案一、情景还原 有个老项目&#xff0c;需要用到 node 的 12.18.4 版本。 小case&#xff0c;我装了 nvm 的&#xff0c;根本构不成挑战&#xff0c;敲敲命令就可以了&#xff1a; # 安装12.18.4版本的nodejs nvm install 12.18.…

优秀案例:基于python django的智能家居销售数据采集和分析系统设计与实现,使用混合推荐算法和LSTM算法情感分析

1 绪论1.1 研究的背景和意义本文所研究设计的智能家居销售数据采集与分析系统主要是为了提升数据的采集效率&#xff0c;并且实现及时采集到的线上电商平台及线下店面的多重渠道销售数据的采集与分析&#xff0c;精确地进行相关的数据采集并应用先进的数据挖掘算法进行分析挖掘…

【传感器标定(四):多传感器融合定位系统中的标定与时间同步方案】

1. 系统框架概述 本方案采用"三层标定框架"&#xff0c;整体架构如下图所示&#xff1a; #mermaid-svg-WhuG9fzKdHSAzSNh {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-WhuG9fzKdHSAzSNh .error-icon{f…

PostgreSQL 跨库查询方法

问题描述&#xff1a; PostgreSQL 跨库查询方法 select rc.tm,fq,fq_old,sccd,unitcd from resource_calc_scene_section_result as rc inner join "mxpt_yushuiqingsrc_databases"."public".st_river_r_hi as st on st.stcd rc.bscd and st.tm rc.tmwher…

毕业论文参考文档(免费)—DHT11 温湿度传感器的硬件与软件系统设计

毕业论文参考文档&#xff08;免费&#xff09;—DHT11 温湿度传感器的硬件与软件系统设计第一章 硬件系统设计 1.1 硬件组成与接口设计 DHT11 采用 4 引脚封装&#xff08;SOP-4&#xff09;&#xff0c;如图 1-1 所示。核心硬件由三部分构成&#xff1a;电阻式湿度检测元件、…

壁纸管理 API 文档

壁纸管理 API 文档环境&#xff1a;Python 3.9、Flask 2.x、PyMySQL 1.x 运行&#xff1a;python app.py 监听&#xff1a;http://0.0.0.0:5000通用响应格式 {"code": 200, // 业务码&#xff1a;200 成功&#xff0c;201 创建成功&#xff0c;400 参数错误&am…

常见问题三

在前端开发中&#xff0c;Vue 的数据响应机制、脚本加载策略以及函数式编程技巧是高频考点和日常开发的核心基础。本文将围绕这几个关键点展开详细解析&#xff0c;帮助开发者深入理解其原理与应用。一、Vue2 与 Vue3 的数据响应原理对比Vue 的核心特性之一是数据响应式—— 当…

清华大学顶刊发表|破解无人机抓取与投递难题

在城市配送、应急物资投放和仓储拣选等场景&#xff0c;人们期待无人机能够独立完成“取-运-投”全流程。然而主流多旋翼通常采用下挂式夹爪或机械臂&#xff0c;包裹悬在机体下方&#xff0c;带来重心下移、转动惯量增加等问题。为突破这一结构瓶颈&#xff0c;清华大学机械工…

【机器学习之推荐算法】基于矩阵分解和损失函数梯度下降的协同过滤算法实现

基于矩阵分解的CF算法实现&#xff08;一&#xff09;&#xff1a;LFM LFM也就是前面提到的Funk SVD矩阵分解 LFM原理解析 LFM(latent factor model) 隐语义模型核心思想是通过隐含特征联系用户和物品&#xff0c;如下图&#xff1a;P矩阵是User-LF矩阵&#xff0c;即用户和隐含…

篇五 网络通信硬件之PHY,MAC, RJ45

一 简介 本章节主要介绍下phy模块, mac模块&#xff0c;RJ45连接器&#xff0c;及硬件通信接口MDIO,MII,RMII,GMII,RGMII 二 介绍ITEM描述PHY负责网络信号的物理收发&#xff0c;调制解调&#xff0c;编解码&#xff0c;波形整形&#xff0c;电平转换&#xff0c;自协商&#x…

命令执行漏洞和[GXYCTF2019]Ping Ping Ping

获取flag&#xff08;传木马文件&#xff09; 文件地址可以用 3个方法 echo PD9waHAgQGV2YWwoJF9QT1NUWzEyM10pOyA/Pg | base64 -d > aab.php curl https://bashupload.com/atR2C/111.txt > shell.php wget https://bashupload.com/atR2C/111.txt 用定向符 ls …

[LeetCode]每日温度

题目链接 每日温度 题目描述 思路解析 &#xff1a;单调栈 单调栈介绍&#xff1a; 单调栈是一种特殊的栈数据结构&#xff0c;其核心特性是栈内元素始终保持单调递增或单调递减的顺序。这种特性使其在解决「寻找下一个更大 / 更小元素」「区间最值」等问题时具有极高效率&a…

reflections:Java非常好用的反射工具包

文章目录一、写在前面二、使用一、写在前面 开源地址&#xff1a;https://github.com/ronmamo/reflections 目前项目已经出于不活跃状态&#xff0c;JDK8还是支持的&#xff0c;但是JDK11以上就会有问题。 Reflections 会扫描并索引您项目类路径的元数据&#xff0c;允许在运…

电脑32位系统能改64位系统吗

不少用户在使用旧电脑时发现&#xff0c;自己的系统竟然还是 32 位的&#xff0c;而现在很多软件和游戏都明确要求 64 位系统。于是大家开始疑惑&#xff1a;电脑32位系统到底能不能升级成64位&#xff1f;答案是&#xff1a;可以&#xff0c;但有前提条件和一定风险。这篇文章…

Shell判断结构

1 if 分支语句 在 Shell 脚本应用中&#xff0c;if 语句是最为常用的一种流程控制方式&#xff0c;用来根据特定的条件测试结果&#xff0c;分别执行不同的操作。 根据不同的复杂程度&#xff0c;if 语句的选择结构可以分为三种基本类型&#xff0c;适用于不同的应用场合&#…

再论物理世界的维数

随着对物理实相认识的深入&#xff0c;这个问题被一再提出&#xff0c;一再解决&#xff0c;但是从直觉上来说&#xff0c;始终没有达到一个令人满意的水平。问题是什么&#xff1f;既然一切皆是振动&#xff0c;那么这些振动是如何构造我们的物理实相的&#xff0c;比如如何构…