【Golang学习】1-基于mysql增删改查
golang语言复习,学习一些重点和组件。通过这个简单的demo,来复习/学习golang
代码
数据库:config.go
作用很简单,连接到数据库
package db
import ("database/sql""fmt""time"_ "github.com/go-sql-driver/mysql"
)// 数据库配置
const (DB_USERNAME = "root"DB_PASSWORD = "" // 如果你设置了密码,请在这里填写DB_HOST = "127.0.0.1"DB_PORT = "3306"DB_NAME = "employee_db"
)var DB *sql.DB
// InitDB 初始化数据库连接
func InitDB() error {dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=true",DB_USERNAME, DB_PASSWORD, DB_HOST, DB_PORT, DB_NAME)var err errorDB, err = sql.Open("mysql", dsn)if err != nil {return fmt.Errorf("无法创建数据库连接: %v", err)}// 设置连接参数DB.SetMaxOpenConns(10)DB.SetMaxIdleConns(5)DB.SetConnMaxLifetime(time.Minute * 3)// 测试连接err = DB.Ping()if err != nil {return fmt.Errorf("无法连接到数据库: %v", err)}fmt.Println("数据库连接成功!")return nil
}
可以注意golang语言的特点,每个逻辑处是例如:DB, err = sql.Open("mysql", dsn)
DB就是正常的执行结果,err就是异常。当err不为空(!=nil时)就可以抛出错误。
数据结构:employee.go
package modelsimport ("time"
)// Employee 结构体表示员工数据模型
type Employee struct {ID intName stringEmail stringPosition stringSalary float64CreatedAt time.TimeUpdatedAt time.Time
}// SearchCriteria 定义搜索条件
type SearchCriteria struct {Name stringPosition stringMinSalary float64MaxSalary float64
}// Pagination 分页相关参数
type Pagination struct {Page intLimit int
}
算是模型层,定义了核心数据结构:
- Employee 结构体:表示员工数据模型,包含员工的基本信息(ID、姓名、邮箱、职位、薪资等)
- SearchCriteria 结构体:用于高级搜索功能,定义搜索条件(按名称、职位、薪资范围等)
- Pagination 结构体:用于分页查询,包含页码和每页显示数量
对数据库操作方法employee_repositor.go
package repositoryimport ("fmt""strings""github.com/employee/db""github.com/employee/models"
)// CreateEmployee 创建新员工
func CreateEmployee(emp *models.Employee) error {query := `INSERT INTO employees (name, email, position, salary, created_at, updated_at)VALUES (?, ?, ?, ?, NOW(), NOW())`result, err := db.DB.Exec(query, emp.Name, emp.Email, emp.Position, emp.Salary)if err != nil {return err}id, err := result.LastInsertId()if err != nil {return err}emp.ID = int(id)return nil
}// GetEmployee 获取单个员工信息
func GetEmployee(id int) (*models.Employee, error) {emp := &models.Employee{}query := "SELECT id, name, email, position, salary, created_at, updated_at FROM employees WHERE id = ?"err := db.DB.QueryRow(query, id).Scan(&emp.ID, &emp.Name, &emp.Email, &emp.Position, &emp.Salary, &emp.CreatedAt, &emp.UpdatedAt,)if err != nil {return nil, err}return emp, nil
}// UpdateEmployee 更新员工信息
func UpdateEmployee(emp *models.Employee) error {query := `UPDATE employeesSET name = ?, email = ?, position = ?, salary = ?, updated_at = NOW()WHERE id = ?`_, err := db.DB.Exec(query, emp.Name, emp.Email, emp.Position, emp.Salary, emp.ID)return err
}// DeleteEmployee 删除员工
func DeleteEmployee(id int) error {query := "DELETE FROM employees WHERE id = ?"_, err := db.DB.Exec(query, id)return err
}// ListEmployees 获取所有员工列表
func ListEmployees() ([]models.Employee, error) {query := "SELECT id, name, email, position, salary, created_at, updated_at FROM employees"rows, err := db.DB.Query(query)if err != nil {return nil, err}defer rows.Close()var employees []models.Employeefor rows.Next() {var emp models.Employeeerr := rows.Scan(&emp.ID, &emp.Name, &emp.Email, &emp.Position, &emp.Salary, &emp.CreatedAt, &emp.UpdatedAt,)if err != nil {return nil, err}employees = append(employees, emp)}return employees, nil
}// SearchEmployees 搜索员工信息并支持分页
func SearchEmployees(criteria models.SearchCriteria, pagination models.Pagination) ([]models.Employee, int, error) {conditions := []string{}args := []interface{}{}if criteria.Name != "" {conditions = append(conditions, "name LIKE ?")args = append(args, "%"+criteria.Name+"%")}if criteria.Position != "" {conditions = append(conditions, "position LIKE ?")args = append(args, "%"+criteria.Position+"%")}if criteria.MinSalary > 0 {conditions = append(conditions, "salary >= ?")args = append(args, criteria.MinSalary)}if criteria.MaxSalary > 0 {conditions = append(conditions, "salary <= ?")args = append(args, criteria.MaxSalary)}whereClause := ""if len(conditions) > 0 {whereClause = "WHERE " + strings.Join(conditions, " AND ")}// 获取总记录数countQuery := fmt.Sprintf("SELECT COUNT(*) FROM employees %s", whereClause)var total interr := db.DB.QueryRow(countQuery, args...).Scan(&total)if err != nil {return nil, 0, err}// 计算偏移量offset := (pagination.Page - 1) * pagination.Limitquery := fmt.Sprintf(`SELECT id, name, email, position, salary, created_at, updated_atFROM employees %sORDER BY id DESCLIMIT ? OFFSET ?`, whereClause)args = append(args, pagination.Limit, offset)rows, err := db.DB.Query(query, args...)if err != nil {return nil, 0, err}defer rows.Close()var employees []models.Employeefor rows.Next() {var emp models.Employeeerr := rows.Scan(&emp.ID, &emp.Name, &emp.Email, &emp.Position, &emp.Salary, &emp.CreatedAt, &emp.UpdatedAt,)if err != nil {return nil, 0, err}employees = append(employees, emp)}return employees, total, nil
}// GetEmployeeStats 获取员工统计信息
func GetEmployeeStats() (map[string]interface{}, error) {stats := make(map[string]interface{})// 获取员工总数var count interr := db.DB.QueryRow("SELECT COUNT(*) FROM employees").Scan(&count)if err != nil {return nil, err}stats["total_employees"] = count// 获取平均薪资var avgSalary float64err = db.DB.QueryRow("SELECT AVG(salary) FROM employees").Scan(&avgSalary)if err != nil {return nil, err}stats["average_salary"] = avgSalary// 获取最高薪资var maxSalary float64err = db.DB.QueryRow("SELECT MAX(salary) FROM employees").Scan(&maxSalary)if err != nil {return nil, err}stats["max_salary"] = maxSalary// 获取各职位员工数量rows, err := db.DB.Query("SELECT position, COUNT(*) as count FROM employees GROUP BY position")if err != nil {return nil, err}defer rows.Close()positions := make(map[string]int)for rows.Next() {var position stringvar count intif err := rows.Scan(&position, &count); err != nil {return nil, err}positions[position] = count}stats["positions"] = positionsreturn stats, nil
}
封装了所有的sql操作,经典的crud。可以看看函数的写法。
防止sql注入:所有插入、更新、删除、查询操作都使用 ?
占位符 + 参数传值方式,而不是字符串拼接。
界面(客户端)employee_service.go
package serviceimport ("bufio""fmt""os""strconv""strings""github.com/employee/models""github.com/employee/repository"
)var reader = bufio.NewReader(os.Stdin)// ReadInput 读取用户输入
func ReadInput(prompt string) string {fmt.Print(prompt)input, _ := reader.ReadString('\n')return strings.TrimSpace(input)
}// ShowMenu 显示主菜单
func ShowMenu() {fmt.Println("\n=== 员工管理系统 ===")fmt.Println("1. 添加员工")fmt.Println("2. 查找员工")fmt.Println("3. 更新员工")fmt.Println("4. 删除员工")fmt.Println("5. 显示所有员工")fmt.Println("6. 高级搜索")fmt.Println("7. 统计信息")fmt.Println("0. 退出")fmt.Println("==================")
}// HandleAddEmployee 处理添加员工
func HandleAddEmployee() {fmt.Println("\n=== 添加新员工 ===")name := ReadInput("请输入员工姓名: ")email := ReadInput("请输入员工邮箱: ")position := ReadInput("请输入职位: ")salaryStr := ReadInput("请输入薪资: ")salary, err := strconv.ParseFloat(salaryStr, 64)if err != nil {fmt.Println("错误:薪资必须是数字")return}emp := &models.Employee{Name: name,Email: email,Position: position,Salary: salary,}if err := repository.CreateEmployee(emp); err != nil {fmt.Printf("添加员工失败: %v\n", err)return}fmt.Printf("添加成功!员工ID为: %d\n", emp.ID)
}// HandleFindEmployee 处理查找员工
func HandleFindEmployee() {fmt.Println("\n=== 查找员工 ===")idStr := ReadInput("请输入员工ID: ")id, err := strconv.Atoi(idStr)if err != nil {fmt.Println("错误:ID必须是数字")return}emp, err := repository.GetEmployee(id)if err != nil {fmt.Printf("查找失败: %v\n", err)return}fmt.Println("\n=== 员工信息 ===")fmt.Printf("ID: %d\n", emp.ID)fmt.Printf("姓名: %s\n", emp.Name)fmt.Printf("邮箱: %s\n", emp.Email)fmt.Printf("职位: %s\n", emp.Position)fmt.Printf("薪资: %.2f\n", emp.Salary)fmt.Printf("创建时间: %s\n", emp.CreatedAt.Format("2006-01-02 15:04:05"))fmt.Printf("更新时间: %s\n", emp.UpdatedAt.Format("2006-01-02 15:04:05"))
}// HandleUpdateEmployee 处理更新员工
func HandleUpdateEmployee() {fmt.Println("\n=== 更新员工信息 ===")idStr := ReadInput("请输入要更新的员工ID: ")id, err := strconv.Atoi(idStr)if err != nil {fmt.Println("错误:ID必须是数字")return}emp, err := repository.GetEmployee(id)if err != nil {fmt.Printf("找不到员工: %v\n", err)return}fmt.Println("\n当前员工信息:")fmt.Printf("姓名: %s\n", emp.Name)fmt.Printf("邮箱: %s\n", emp.Email)fmt.Printf("职位: %s\n", emp.Position)fmt.Printf("薪资: %.2f\n", emp.Salary)fmt.Println("\n请输入新的信息(直接回车保持不变):")if name := ReadInput("新姓名: "); name != "" {emp.Name = name}if email := ReadInput("新邮箱: "); email != "" {emp.Email = email}if position := ReadInput("新职位: "); position != "" {emp.Position = position}if salaryStr := ReadInput("新薪资: "); salaryStr != "" {if salary, err := strconv.ParseFloat(salaryStr, 64); err == nil {emp.Salary = salary} else {fmt.Println("错误:薪资必须是数字,保持原值不变")}}if err := repository.UpdateEmployee(emp); err != nil {fmt.Printf("更新失败: %v\n", err)return}fmt.Println("更新成功!")
}// HandleDeleteEmployee 处理删除员工
func HandleDeleteEmployee() {fmt.Println("\n=== 删除员工 ===")idStr := ReadInput("请输入要删除的员工ID: ")id, err := strconv.Atoi(idStr)if err != nil {fmt.Println("错误:ID必须是数字")return}confirm := ReadInput(fmt.Sprintf("确定要删除ID为%d的员工吗?(y/n): ", id))if strings.ToLower(confirm) != "y" {fmt.Println("取消删除")return}if err := repository.DeleteEmployee(id); err != nil {fmt.Printf("删除失败: %v\n", err)return}fmt.Println("删除成功!")
}// HandleListEmployees 处理显示所有员工
func HandleListEmployees() {fmt.Println("\n=== 员工列表 ===")employees, err := repository.ListEmployees()if err != nil {fmt.Printf("获取员工列表失败: %v\n", err)return}if len(employees) == 0 {fmt.Println("暂无员工记录")return}fmt.Printf("\n共 %d 名员工:\n", len(employees))fmt.Println("ID\t姓名\t\t职位\t\t薪资\t\t邮箱")fmt.Println("========================================================")for _, emp := range employees {fmt.Printf("%d\t%-8s\t%-8s\t%.2f\t%s\n",emp.ID, emp.Name, emp.Position, emp.Salary, emp.Email)}
}// HandleAdvancedSearch 处理高级搜索
func HandleAdvancedSearch() {fmt.Println("\n=== 高级搜索 ===")fmt.Println("1. 按职位搜索")fmt.Println("2. 按薪资范围搜索")fmt.Println("0. 返回主菜单")choice := ReadInput("\n请选择搜索方式: ")var criteria models.SearchCriteriapagination := models.Pagination{Page: 1, Limit: 10}switch choice {case "1":position := ReadInput("请输入职位关键词: ")criteria.Position = positioncase "2":minSalaryStr := ReadInput("请输入最低薪资: ")maxSalaryStr := ReadInput("请输入最高薪资: ")if minSalary, err := strconv.ParseFloat(minSalaryStr, 64); err == nil {criteria.MinSalary = minSalary}if maxSalary, err := strconv.ParseFloat(maxSalaryStr, 64); err == nil {criteria.MaxSalary = maxSalary}case "0":returndefault:fmt.Println("无效的选择")return}employees, total, err := repository.SearchEmployees(criteria, pagination)if err != nil {fmt.Printf("搜索失败: %v\n", err)return}fmt.Printf("\n找到 %d 个结果:\n", total)if len(employees) == 0 {fmt.Println("没有找到匹配的员工")return}fmt.Println("\nID\t姓名\t\t职位\t\t薪资\t\t邮箱")fmt.Println("========================================================")for _, emp := range employees {fmt.Printf("%d\t%-8s\t%-8s\t%.2f\t%s\n",emp.ID, emp.Name, emp.Position, emp.Salary, emp.Email)}
}// HandleStats 处理统计信息
func HandleStats() {fmt.Println("\n=== 统计信息 ===")stats, err := repository.GetEmployeeStats()if err != nil {fmt.Printf("获取统计信息失败: %v\n", err)return}fmt.Printf("员工总数: %d\n", stats["total_employees"])fmt.Printf("平均薪资: %.2f\n", stats["average_salary"])fmt.Printf("最高薪资: %.2f\n", stats["max_salary"])fmt.Println("\n职位分布:")positions := stats["positions"].(map[string]int)for pos, count := range positions {fmt.Printf("- %s: %d人\n", pos, count)}
}
处理用户交互和业务逻辑
- 提供 ReadInput 函数用于读取用户输入
- ShowMenu 函数展示主菜单
- 各种处理函数,如 HandleAddEmployee、HandleFindEmployee 等
- 负责输入验证、数据格式化和结果呈现
- 调用仓库层的函数来执行实际的数据操作
服务层专注于用户体验和业务流程控制,不直接处理数据库操作。
主程序main.go
package mainimport ("fmt""os""github.com/employee/db""github.com/employee/service"
)func main() {fmt.Println("初始化系统...")// 初始化数据库连接if err := db.InitDB(); err != nil {fmt.Printf("数据库连接失败: %v\n", err)fmt.Println("请确保MySQL服务已启动且配置正确")os.Exit(1)}fmt.Println("系统就绪!")for {service.ShowMenu()choice := service.ReadInput("请选择操作: ")switch choice {case "1":service.HandleAddEmployee()case "2":service.HandleFindEmployee()case "3":service.HandleUpdateEmployee()case "4":service.HandleDeleteEmployee()case "5":service.HandleListEmployees()case "6":service.HandleAdvancedSearch()case "7":service.HandleStats()case "0":fmt.Println("感谢使用,再见!")returndefault:fmt.Println("无效的选择,请重试")}}
}
- 初始化数据库连接
- 显示主菜单并进入交互循环
- 根据用户输入调度相应的服务层函数
- 处理程序退出逻辑
其他文件
- go.mod: Go模块定义文件,指定模块名和依赖项
- go.sum: 依赖项的校验和文件,确保依赖项的完整性