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
英文地址,請注明出處:http://en.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;比如如何構…