|
|
@@ -10,6 +10,17 @@
|
|
|
|
|
|
### 3.1 文件I/O操作
|
|
|
```go
|
|
|
+package main
|
|
|
+
|
|
|
+import (
|
|
|
+ "bufio"
|
|
|
+ "fmt"
|
|
|
+ "io"
|
|
|
+ "os"
|
|
|
+ "path/filepath"
|
|
|
+ "strings"
|
|
|
+)
|
|
|
+
|
|
|
// 文件读写
|
|
|
func readFile(filename string) (string, error) {
|
|
|
data, err := os.ReadFile(filename)
|
|
|
@@ -19,10 +30,44 @@ func readFile(filename string) (string, error) {
|
|
|
return string(data), nil
|
|
|
}
|
|
|
|
|
|
+// 按行读取文件
|
|
|
+func readFileByLines(filename string) ([]string, error) {
|
|
|
+ file, err := os.Open(filename)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ defer file.Close()
|
|
|
+
|
|
|
+ var lines []string
|
|
|
+ scanner := bufio.NewScanner(file)
|
|
|
+ for scanner.Scan() {
|
|
|
+ lines = append(lines, scanner.Text())
|
|
|
+ }
|
|
|
+
|
|
|
+ if err := scanner.Err(); err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+
|
|
|
+ return lines, nil
|
|
|
+}
|
|
|
+
|
|
|
+// 写入文件
|
|
|
func writeFile(filename, content string) error {
|
|
|
return os.WriteFile(filename, []byte(content), 0644)
|
|
|
}
|
|
|
|
|
|
+// 追加内容到文件
|
|
|
+func appendToFile(filename, content string) error {
|
|
|
+ file, err := os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ defer file.Close()
|
|
|
+
|
|
|
+ _, err = file.WriteString(content)
|
|
|
+ return err
|
|
|
+}
|
|
|
+
|
|
|
// 目录操作
|
|
|
func listFiles(dir string) ([]string, error) {
|
|
|
entries, err := os.ReadDir(dir)
|
|
|
@@ -36,10 +81,146 @@ func listFiles(dir string) ([]string, error) {
|
|
|
}
|
|
|
return files, nil
|
|
|
}
|
|
|
+
|
|
|
+// 递归列出所有文件
|
|
|
+func listAllFiles(dir string) ([]string, error) {
|
|
|
+ var files []string
|
|
|
+
|
|
|
+ err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ if !info.IsDir() {
|
|
|
+ files = append(files, path)
|
|
|
+ }
|
|
|
+ return nil
|
|
|
+ })
|
|
|
+
|
|
|
+ return files, err
|
|
|
+}
|
|
|
+
|
|
|
+// 复制文件
|
|
|
+func copyFile(src, dst string) error {
|
|
|
+ sourceFile, err := os.Open(src)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ defer sourceFile.Close()
|
|
|
+
|
|
|
+ destFile, err := os.Create(dst)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ defer destFile.Close()
|
|
|
+
|
|
|
+ _, err = io.Copy(destFile, sourceFile)
|
|
|
+ return err
|
|
|
+}
|
|
|
+
|
|
|
+// 获取文件信息
|
|
|
+func getFileInfo(filename string) (os.FileInfo, error) {
|
|
|
+ return os.Stat(filename)
|
|
|
+}
|
|
|
+
|
|
|
+// 创建目录
|
|
|
+func createDir(dirName string) error {
|
|
|
+ return os.MkdirAll(dirName, 0755)
|
|
|
+}
|
|
|
+
|
|
|
+func main() {
|
|
|
+ // 创建测试目录和文件
|
|
|
+ testDir := "./test_files"
|
|
|
+ createDir(testDir)
|
|
|
+
|
|
|
+ // 写入测试文件
|
|
|
+ testFile := filepath.Join(testDir, "test.txt")
|
|
|
+ content := "这是测试文件\n包含多行内容\n用于演示文件操作"
|
|
|
+ err := writeFile(testFile, content)
|
|
|
+ if err != nil {
|
|
|
+ fmt.Printf("写入文件错误: %v\n", err)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // 读取文件
|
|
|
+ data, err := readFile(testFile)
|
|
|
+ if err != nil {
|
|
|
+ fmt.Printf("读取文件错误: %v\n", err)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ fmt.Printf("文件内容:\n%s\n", data)
|
|
|
+
|
|
|
+ // 按行读取
|
|
|
+ lines, err := readFileByLines(testFile)
|
|
|
+ if err != nil {
|
|
|
+ fmt.Printf("按行读取错误: %v\n", err)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ fmt.Printf("文件行数: %d\n", len(lines))
|
|
|
+
|
|
|
+ // 追加内容
|
|
|
+ err = appendToFile(testFile, "\n追加的新行")
|
|
|
+ if err != nil {
|
|
|
+ fmt.Printf("追加内容错误: %v\n", err)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // 列出目录中的文件
|
|
|
+ files, err := listFiles(testDir)
|
|
|
+ if err != nil {
|
|
|
+ fmt.Printf("列出文件错误: %v\n", err)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ fmt.Printf("目录中的文件: %v\n", files)
|
|
|
+
|
|
|
+ // 获取文件信息
|
|
|
+ info, err := getFileInfo(testFile)
|
|
|
+ if err != nil {
|
|
|
+ fmt.Printf("获取文件信息错误: %v\n", err)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ fmt.Printf("文件大小: %d 字节\n", info.Size())
|
|
|
+ fmt.Printf("文件修改时间: %v\n", info.ModTime())
|
|
|
+
|
|
|
+ // 复制文件
|
|
|
+ copyFile := filepath.Join(testDir, "test_copy.txt")
|
|
|
+ err = copyFile(testFile, copyFile)
|
|
|
+ if err != nil {
|
|
|
+ fmt.Printf("复制文件错误: %v\n", err)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ fmt.Printf("文件已复制到: %s\n", copyFile)
|
|
|
+
|
|
|
+ // 递归列出所有文件
|
|
|
+ allFiles, err := listAllFiles(".")
|
|
|
+ if err != nil {
|
|
|
+ fmt.Printf("递归列出文件错误: %v\n", err)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ fmt.Printf("当前目录及子目录中的所有文件数量: %d\n", len(allFiles))
|
|
|
+
|
|
|
+ // 清理测试文件
|
|
|
+ os.RemoveAll(testDir)
|
|
|
+ fmt.Println("测试完成,已清理测试文件")
|
|
|
+}
|
|
|
```
|
|
|
|
|
|
### 3.2 网络编程
|
|
|
```go
|
|
|
+package main
|
|
|
+
|
|
|
+import (
|
|
|
+ "bufio"
|
|
|
+ "encoding/json"
|
|
|
+ "fmt"
|
|
|
+ "io"
|
|
|
+ "log"
|
|
|
+ "net"
|
|
|
+ "net/http"
|
|
|
+ "os"
|
|
|
+ "strings"
|
|
|
+ "time"
|
|
|
+)
|
|
|
+
|
|
|
// HTTP客户端
|
|
|
func httpGet(url string) (string, error) {
|
|
|
resp, err := http.Get(url)
|
|
|
@@ -55,21 +236,163 @@ func httpGet(url string) (string, error) {
|
|
|
return string(body), nil
|
|
|
}
|
|
|
|
|
|
+// 带超时的HTTP客户端
|
|
|
+func httpGetWithTimeout(url string, timeout time.Duration) (string, error) {
|
|
|
+ client := http.Client{
|
|
|
+ Timeout: timeout,
|
|
|
+ }
|
|
|
+
|
|
|
+ resp, err := client.Get(url)
|
|
|
+ if err != nil {
|
|
|
+ return "", err
|
|
|
+ }
|
|
|
+ defer resp.Body.Close()
|
|
|
+
|
|
|
+ body, err := io.ReadAll(resp.Body)
|
|
|
+ if err != nil {
|
|
|
+ return "", err
|
|
|
+ }
|
|
|
+ return string(body), nil
|
|
|
+}
|
|
|
+
|
|
|
+// POST请求
|
|
|
+func httpPost(url string, data interface{}) (string, error) {
|
|
|
+ jsonData, err := json.Marshal(data)
|
|
|
+ if err != nil {
|
|
|
+ return "", err
|
|
|
+ }
|
|
|
+
|
|
|
+ resp, err := http.Post(url, "application/json", strings.NewReader(string(jsonData)))
|
|
|
+ if err != nil {
|
|
|
+ return "", err
|
|
|
+ }
|
|
|
+ defer resp.Body.Close()
|
|
|
+
|
|
|
+ body, err := io.ReadAll(resp.Body)
|
|
|
+ if err != nil {
|
|
|
+ return "", err
|
|
|
+ }
|
|
|
+ return string(body), nil
|
|
|
+}
|
|
|
+
|
|
|
// HTTP服务器
|
|
|
func startHTTPServer() {
|
|
|
+ // 静态文件服务
|
|
|
+ fs := http.FileServer(http.Dir("./static"))
|
|
|
+ http.Handle("/static/", http.StripPrefix("/static/", fs))
|
|
|
+
|
|
|
+ // API路由
|
|
|
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
|
|
- fmt.Fprintf(w, "Hello, World!")
|
|
|
+ fmt.Fprintf(w, "Hello, World! 当前时间: %s", time.Now().Format("2006-01-02 15:04:05"))
|
|
|
+ })
|
|
|
+
|
|
|
+ http.HandleFunc("/api/hello", func(w http.ResponseWriter, r *http.Request) {
|
|
|
+ name := r.URL.Query().Get("name")
|
|
|
+ if name == "" {
|
|
|
+ name = "Guest"
|
|
|
+ }
|
|
|
+
|
|
|
+ response := map[string]string{
|
|
|
+ "message": fmt.Sprintf("Hello, %s!", name),
|
|
|
+ "time": time.Now().Format(time.RFC3339),
|
|
|
+ }
|
|
|
+
|
|
|
+ w.Header().Set("Content-Type", "application/json")
|
|
|
+ json.NewEncoder(w).Encode(response)
|
|
|
+ })
|
|
|
+
|
|
|
+ http.HandleFunc("/api/data", func(w http.ResponseWriter, r *http.Request) {
|
|
|
+ if r.Method != http.MethodPost {
|
|
|
+ http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ var data map[string]interface{}
|
|
|
+ if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
|
|
|
+ http.Error(w, "Invalid JSON", http.StatusBadRequest)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // 处理数据
|
|
|
+ data["processed"] = true
|
|
|
+ data["timestamp"] = time.Now().Unix()
|
|
|
+
|
|
|
+ w.Header().Set("Content-Type", "application/json")
|
|
|
+ json.NewEncoder(w).Encode(data)
|
|
|
})
|
|
|
|
|
|
- http.ListenAndServe(":8080", nil)
|
|
|
+ fmt.Println("HTTP服务器启动,监听端口: 8080")
|
|
|
+ log.Fatal(http.ListenAndServe(":8080", nil))
|
|
|
+}
|
|
|
+
|
|
|
+// TCP连接处理
|
|
|
+func handleConnection(conn net.Conn) {
|
|
|
+ defer conn.Close()
|
|
|
+
|
|
|
+ // 设置连接超时
|
|
|
+ conn.SetDeadline(time.Now().Add(30 * time.Second))
|
|
|
+
|
|
|
+ // 发送欢迎消息
|
|
|
+ conn.Write([]byte("欢迎连接TCP服务器!\n"))
|
|
|
+
|
|
|
+ scanner := bufio.NewScanner(conn)
|
|
|
+ for scanner.Scan() {
|
|
|
+ text := scanner.Text()
|
|
|
+ if text == "quit" {
|
|
|
+ break
|
|
|
+ }
|
|
|
+
|
|
|
+ // 处理命令
|
|
|
+ response := processCommand(text)
|
|
|
+ conn.Write([]byte(response + "\n"))
|
|
|
+ }
|
|
|
+
|
|
|
+ if err := scanner.Err(); err != nil {
|
|
|
+ fmt.Printf("连接错误: %v\n", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ fmt.Printf("客户端 %s 断开连接\n", conn.RemoteAddr())
|
|
|
+}
|
|
|
+
|
|
|
+// 处理TCP命令
|
|
|
+func processCommand(command string) string {
|
|
|
+ parts := strings.Fields(command)
|
|
|
+ if len(parts) == 0 {
|
|
|
+ return "无效命令"
|
|
|
+ }
|
|
|
+
|
|
|
+ switch parts[0] {
|
|
|
+ case "time":
|
|
|
+ return fmt.Sprintf("当前时间: %s", time.Now().Format("2006-01-02 15:04:05"))
|
|
|
+ case "echo":
|
|
|
+ if len(parts) > 1 {
|
|
|
+ return strings.Join(parts[1:], " ")
|
|
|
+ }
|
|
|
+ return "echo需要参数"
|
|
|
+ case "reverse":
|
|
|
+ if len(parts) > 1 {
|
|
|
+ text := strings.Join(parts[1:], " ")
|
|
|
+ runes := []rune(text)
|
|
|
+ for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
|
|
|
+ runes[i], runes[j] = runes[j], runes[i]
|
|
|
+ }
|
|
|
+ return string(runes)
|
|
|
+ }
|
|
|
+ return "reverse需要参数"
|
|
|
+ default:
|
|
|
+ return fmt.Sprintf("未知命令: %s", parts[0])
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
// TCP服务器
|
|
|
func startTCPServer() {
|
|
|
- ln, err := net.Listen("tcp", ":8080")
|
|
|
+ ln, err := net.Listen("tcp", ":8081")
|
|
|
if err != nil {
|
|
|
log.Fatal(err)
|
|
|
}
|
|
|
+ defer ln.Close()
|
|
|
+
|
|
|
+ fmt.Println("TCP服务器启动,监听端口: 8081")
|
|
|
|
|
|
for {
|
|
|
conn, err := ln.Accept()
|
|
|
@@ -77,145 +400,2404 @@ func startTCPServer() {
|
|
|
log.Println(err)
|
|
|
continue
|
|
|
}
|
|
|
+
|
|
|
+ fmt.Printf("新客户端连接: %s\n", conn.RemoteAddr())
|
|
|
go handleConnection(conn)
|
|
|
}
|
|
|
}
|
|
|
-```
|
|
|
|
|
|
-### 3.3 数据库操作
|
|
|
-```go
|
|
|
-// MySQL连接
|
|
|
-func connectDB() (*sql.DB, error) {
|
|
|
- db, err := sql.Open("mysql", "user:password@tcp(localhost:3306)/dbname")
|
|
|
+// TCP客户端
|
|
|
+func startTCPClient(serverAddr string) {
|
|
|
+ conn, err := net.Dial("tcp", serverAddr)
|
|
|
if err != nil {
|
|
|
- return nil, err
|
|
|
+ log.Fatal(err)
|
|
|
}
|
|
|
+ defer conn.Close()
|
|
|
|
|
|
- if err := db.Ping(); err != nil {
|
|
|
- return nil, err
|
|
|
+ fmt.Printf("已连接到TCP服务器: %s\n", serverAddr)
|
|
|
+
|
|
|
+ // 读取欢迎消息
|
|
|
+ scanner := bufio.NewScanner(conn)
|
|
|
+ if scanner.Scan() {
|
|
|
+ fmt.Println("服务器:", scanner.Text())
|
|
|
}
|
|
|
|
|
|
- return db, nil
|
|
|
+ // 发送命令
|
|
|
+ reader := bufio.NewReader(os.Stdin)
|
|
|
+ for {
|
|
|
+ fmt.Print("输入命令 (quit退出): ")
|
|
|
+ text, _ := reader.ReadString('\n')
|
|
|
+ text = strings.TrimSpace(text)
|
|
|
+
|
|
|
+ if text == "quit" {
|
|
|
+ break
|
|
|
+ }
|
|
|
+
|
|
|
+ // 发送命令
|
|
|
+ fmt.Fprintln(conn, text)
|
|
|
+
|
|
|
+ // 读取响应
|
|
|
+ if scanner.Scan() {
|
|
|
+ fmt.Println("服务器:", scanner.Text())
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
-// CRUD操作
|
|
|
-type User struct {
|
|
|
- ID int
|
|
|
- Name string
|
|
|
- Age int
|
|
|
+func main() {
|
|
|
+ if len(os.Args) < 2 {
|
|
|
+ fmt.Println("使用方法:")
|
|
|
+ fmt.Println(" HTTP服务器: go run network.go http")
|
|
|
+ fmt.Println(" TCP服务器: go run network.go tcp-server")
|
|
|
+ fmt.Println(" TCP客户端: go run network.go tcp-client <server:port>")
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ mode := os.Args[1]
|
|
|
+
|
|
|
+ switch mode {
|
|
|
+ case "http":
|
|
|
+ startHTTPServer()
|
|
|
+ case "tcp-server":
|
|
|
+ startTCPServer()
|
|
|
+ case "tcp-client":
|
|
|
+ if len(os.Args) < 3 {
|
|
|
+ fmt.Println("请指定服务器地址")
|
|
|
+ return
|
|
|
+ }
|
|
|
+ serverAddr := os.Args[2]
|
|
|
+ startTCPClient(serverAddr)
|
|
|
+ default:
|
|
|
+ fmt.Println("无效模式")
|
|
|
+ }
|
|
|
}
|
|
|
+```
|
|
|
|
|
|
-func createUser(db *sql.DB, user User) error {
|
|
|
- _, err := db.Exec("INSERT INTO users (name, age) VALUES (?, ?)", user.Name, user.Age)
|
|
|
- return err
|
|
|
-}
|
|
|
+### 3.3 数据库操作
|
|
|
+```go
|
|
|
+package main
|
|
|
|
|
|
-func getUserByID(db *sql.DB, id int) (User, error) {
|
|
|
- var user User
|
|
|
- err := db.QueryRow("SELECT id, name, age FROM users WHERE id = ?", id).Scan(&user.ID, &user.Name, &user.Age)
|
|
|
- return user, err
|
|
|
+import (
|
|
|
+ "database/sql"
|
|
|
+ "fmt"
|
|
|
+ "log"
|
|
|
+ "time"
|
|
|
+
|
|
|
+ _ "github.com/go-sql-driver/mysql"
|
|
|
+ _ "github.com/lib/pq"
|
|
|
+ _ "github.com/mattn/go-sqlite3"
|
|
|
+)
|
|
|
+
|
|
|
+// 用户模型
|
|
|
+type User struct {
|
|
|
+ ID int `json:"id"`
|
|
|
+ Name string `json:"name"`
|
|
|
+ Email string `json:"email"`
|
|
|
+ Age int `json:"age"`
|
|
|
+ CreatedAt time.Time `json:"created_at"`
|
|
|
+ UpdatedAt time.Time `json:"updated_at"`
|
|
|
}
|
|
|
-```
|
|
|
|
|
|
-### 3.4 JSON/XML数据处理
|
|
|
-```go
|
|
|
-// JSON序列化与反序列化
|
|
|
-type Config struct {
|
|
|
- Host string `json:"host"`
|
|
|
- Port int `json:"port"`
|
|
|
+// 数据库配置
|
|
|
+type DBConfig struct {
|
|
|
+ Driver string
|
|
|
+ Host string
|
|
|
+ Port int
|
|
|
+ Username string
|
|
|
+ Password string
|
|
|
+ Database string
|
|
|
}
|
|
|
|
|
|
-func saveConfig(config Config) error {
|
|
|
- data, err := json.Marshal(config)
|
|
|
+// MySQL连接
|
|
|
+func connectMySQL(config DBConfig) (*sql.DB, error) {
|
|
|
+ dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?parseTime=true",
|
|
|
+ config.Username, config.Password, config.Host, config.Port, config.Database)
|
|
|
+
|
|
|
+ db, err := sql.Open("mysql", dsn)
|
|
|
if err != nil {
|
|
|
- return err
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+
|
|
|
+ // 测试连接
|
|
|
+ if err := db.Ping(); err != nil {
|
|
|
+ return nil, err
|
|
|
}
|
|
|
- return os.WriteFile("config.json", data, 0644)
|
|
|
+
|
|
|
+ // 设置连接池
|
|
|
+ db.SetMaxOpenConns(25)
|
|
|
+ db.SetMaxIdleConns(5)
|
|
|
+ db.SetConnMaxLifetime(5 * time.Minute)
|
|
|
+
|
|
|
+ return db, nil
|
|
|
}
|
|
|
|
|
|
-func loadConfig() (Config, error) {
|
|
|
- data, err := os.ReadFile("config.json")
|
|
|
+// PostgreSQL连接
|
|
|
+func connectPostgreSQL(config DBConfig) (*sql.DB, error) {
|
|
|
+ dsn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable",
|
|
|
+ config.Host, config.Port, config.Username, config.Password, config.Database)
|
|
|
+
|
|
|
+ db, err := sql.Open("postgres", dsn)
|
|
|
if err != nil {
|
|
|
- return Config{}, err
|
|
|
+ return nil, err
|
|
|
}
|
|
|
|
|
|
- var config Config
|
|
|
- err = json.Unmarshal(data, &config)
|
|
|
- return config, err
|
|
|
+ // 测试连接
|
|
|
+ if err := db.Ping(); err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+
|
|
|
+ // 设置连接池
|
|
|
+ db.SetMaxOpenConns(25)
|
|
|
+ db.SetMaxIdleConns(5)
|
|
|
+ db.SetConnMaxLifetime(5 * time.Minute)
|
|
|
+
|
|
|
+ return db, nil
|
|
|
}
|
|
|
-```
|
|
|
|
|
|
-### 3.5 测试与调试
|
|
|
-```go
|
|
|
-// 单元测试
|
|
|
-func TestAdd(t *testing.T) {
|
|
|
- result := add(2, 3)
|
|
|
- if result != 5 {
|
|
|
- t.Errorf("Expected 5, got %d", result)
|
|
|
+// SQLite连接
|
|
|
+func connectSQLite(dbPath string) (*sql.DB, error) {
|
|
|
+ db, err := sql.Open("sqlite3", dbPath)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+
|
|
|
+ // 测试连接
|
|
|
+ if err := db.Ping(); err != nil {
|
|
|
+ return nil, err
|
|
|
}
|
|
|
+
|
|
|
+ return db, nil
|
|
|
}
|
|
|
|
|
|
-// 基准测试
|
|
|
-func BenchmarkAdd(b *testing.B) {
|
|
|
- for i := 0; i < b.N; i++ {
|
|
|
- add(1, 2)
|
|
|
+// 创建用户表
|
|
|
+func createUserTable(db *sql.DB, driver string) error {
|
|
|
+ var query string
|
|
|
+
|
|
|
+ switch driver {
|
|
|
+ case "mysql":
|
|
|
+ query = `
|
|
|
+ CREATE TABLE IF NOT EXISTS users (
|
|
|
+ id INT AUTO_INCREMENT PRIMARY KEY,
|
|
|
+ name VARCHAR(100) NOT NULL,
|
|
|
+ email VARCHAR(100) UNIQUE NOT NULL,
|
|
|
+ age INT,
|
|
|
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
|
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
|
|
|
+ )`
|
|
|
+ case "postgres":
|
|
|
+ query = `
|
|
|
+ CREATE TABLE IF NOT EXISTS users (
|
|
|
+ id SERIAL PRIMARY KEY,
|
|
|
+ name VARCHAR(100) NOT NULL,
|
|
|
+ email VARCHAR(100) UNIQUE NOT NULL,
|
|
|
+ age INT,
|
|
|
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
|
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
|
+ )`
|
|
|
+ case "sqlite3":
|
|
|
+ query = `
|
|
|
+ CREATE TABLE IF NOT EXISTS users (
|
|
|
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
|
+ name TEXT NOT NULL,
|
|
|
+ email TEXT UNIQUE NOT NULL,
|
|
|
+ age INTEGER,
|
|
|
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
|
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
|
+ )`
|
|
|
+ default:
|
|
|
+ return fmt.Errorf("不支持的数据库驱动: %s", driver)
|
|
|
}
|
|
|
+
|
|
|
+ _, err := db.Exec(query)
|
|
|
+ return err
|
|
|
}
|
|
|
|
|
|
-// 性能分析
|
|
|
-func profileCPU() {
|
|
|
- f, err := os.Create("cpu.prof")
|
|
|
+// 创建用户
|
|
|
+func createUser(db *sql.DB, user User) (int, error) {
|
|
|
+ query := "INSERT INTO users (name, email, age) VALUES (?, ?, ?)"
|
|
|
+
|
|
|
+ result, err := db.Exec(query, user.Name, user.Email, user.Age)
|
|
|
if err != nil {
|
|
|
- log.Fatal(err)
|
|
|
+ return 0, err
|
|
|
}
|
|
|
- defer f.Close()
|
|
|
|
|
|
- pprof.StartCPUProfile(f)
|
|
|
- defer pprof.StopCPUProfile()
|
|
|
+ id, err := result.LastInsertId()
|
|
|
+ if err != nil {
|
|
|
+ return 0, err
|
|
|
+ }
|
|
|
|
|
|
- // 执行需要分析的代码
|
|
|
+ return int(id), nil
|
|
|
}
|
|
|
-```
|
|
|
-
|
|
|
-## 练习项目
|
|
|
|
|
|
-### 项目1:文件备份工具
|
|
|
-```go
|
|
|
-// 实现增量备份功能
|
|
|
-type BackupManager struct {
|
|
|
- sourceDir string
|
|
|
- backupDir string
|
|
|
+// 根据ID获取用户
|
|
|
+func getUserByID(db *sql.DB, id int) (User, error) {
|
|
|
+ var user User
|
|
|
+ query := "SELECT id, name, email, age, created_at, updated_at FROM users WHERE id = ?"
|
|
|
+
|
|
|
+ err := db.QueryRow(query, id).Scan(
|
|
|
+ &user.ID, &user.Name, &user.Email, &user.Age, &user.CreatedAt, &user.UpdatedAt)
|
|
|
+
|
|
|
+ return user, err
|
|
|
}
|
|
|
|
|
|
-func (bm *BackupManager) Backup() error
|
|
|
-func (bm *BackupManager) Restore(backupName string) error
|
|
|
-func (bm *BackupManager) ListBackups() ([]string, error)
|
|
|
-```
|
|
|
-
|
|
|
-### 项目2:Web爬虫
|
|
|
-```go
|
|
|
-// 并发爬取网页内容
|
|
|
-type Crawler struct {
|
|
|
- visited map[string]bool
|
|
|
- mutex sync.Mutex
|
|
|
+// 获取所有用户
|
|
|
+func getAllUsers(db *sql.DB) ([]User, error) {
|
|
|
+ query := "SELECT id, name, email, age, created_at, updated_at FROM users ORDER BY id"
|
|
|
+
|
|
|
+ rows, err := db.Query(query)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ defer rows.Close()
|
|
|
+
|
|
|
+ var users []User
|
|
|
+ for rows.Next() {
|
|
|
+ var user User
|
|
|
+ err := rows.Scan(
|
|
|
+ &user.ID, &user.Name, &user.Email, &user.Age, &user.CreatedAt, &user.UpdatedAt)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ users = append(users, user)
|
|
|
+ }
|
|
|
+
|
|
|
+ if err = rows.Err(); err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+
|
|
|
+ return users, nil
|
|
|
}
|
|
|
|
|
|
-func (c *Crawler) Crawl(url string, depth int) ([]string, error)
|
|
|
-func (c *Crawler) extractLinks(html string) []string
|
|
|
-```
|
|
|
-
|
|
|
-### 项目3:数据库CRUD应用
|
|
|
-```go
|
|
|
-// 完整的用户管理系统
|
|
|
-type UserService struct {
|
|
|
- db *sql.DB
|
|
|
+// 更新用户
|
|
|
+func updateUser(db *sql.DB, user User) error {
|
|
|
+ query := "UPDATE users SET name = ?, email = ?, age = ?, updated_at = ? WHERE id = ?"
|
|
|
+
|
|
|
+ _, err := db.Exec(query, user.Name, user.Email, user.Age, time.Now(), user.ID)
|
|
|
+ return err
|
|
|
}
|
|
|
|
|
|
-func (us *UserService) CreateUser(user User) error
|
|
|
-func (us *UserService) GetUser(id int) (User, error)
|
|
|
-func (us *UserService) UpdateUser(user User) error
|
|
|
-func (us *UserService) DeleteUser(id int) error
|
|
|
-func (us *UserService) ListUsers() ([]User, error)
|
|
|
+// 删除用户
|
|
|
+func deleteUser(db *sql.DB, id int) error {
|
|
|
+ query := "DELETE FROM users WHERE id = ?"
|
|
|
+
|
|
|
+ _, err := db.Exec(query, id)
|
|
|
+ return err
|
|
|
+}
|
|
|
+
|
|
|
+// 事务示例
|
|
|
+func transferUsers(db *sql.DB, fromID, toID int) error {
|
|
|
+ tx, err := db.Begin()
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ defer func() {
|
|
|
+ if p := recover(); p != nil {
|
|
|
+ tx.Rollback()
|
|
|
+ panic(p) // 重新抛出panic
|
|
|
+ } else if err != nil {
|
|
|
+ tx.Rollback()
|
|
|
+ } else {
|
|
|
+ err = tx.Commit()
|
|
|
+ }
|
|
|
+ }()
|
|
|
+
|
|
|
+ // 获取源用户
|
|
|
+ fromUser, err := getUserByID(db, fromID)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取目标用户
|
|
|
+ toUser, err := getUserByID(db, toID)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ // 交换年龄
|
|
|
+ fromUser.Age, toUser.Age = toUser.Age, fromUser.Age
|
|
|
+
|
|
|
+ // 更新用户
|
|
|
+ if err := updateUser(db, fromUser); err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ if err := updateUser(db, toUser); err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+func main() {
|
|
|
+ // 使用SQLite作为示例(无需额外安装)
|
|
|
+ db, err := connectSQLite("./test.db")
|
|
|
+ if err != nil {
|
|
|
+ log.Fatal(err)
|
|
|
+ }
|
|
|
+ defer db.Close()
|
|
|
+
|
|
|
+ // 创建表
|
|
|
+ if err := createUserTable(db, "sqlite3"); err != nil {
|
|
|
+ log.Fatal(err)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 创建用户
|
|
|
+ users := []User{
|
|
|
+ {Name: "张三", Email: "[email protected]", Age: 25},
|
|
|
+ {Name: "李四", Email: "[email protected]", Age: 30},
|
|
|
+ {Name: "王五", Email: "[email protected]", Age: 28},
|
|
|
+ }
|
|
|
+
|
|
|
+ for _, user := range users {
|
|
|
+ id, err := createUser(db, user)
|
|
|
+ if err != nil {
|
|
|
+ log.Printf("创建用户失败: %v", err)
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ fmt.Printf("创建用户成功,ID: %d\n", id)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取所有用户
|
|
|
+ allUsers, err := getAllUsers(db)
|
|
|
+ if err != nil {
|
|
|
+ log.Fatal(err)
|
|
|
+ }
|
|
|
+
|
|
|
+ fmt.Println("\n所有用户:")
|
|
|
+ for _, user := range allUsers {
|
|
|
+ fmt.Printf("ID: %d, 姓名: %s, 邮箱: %s, 年龄: %d\n",
|
|
|
+ user.ID, user.Name, user.Email, user.Age)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 更新用户
|
|
|
+ if len(allUsers) > 0 {
|
|
|
+ user := allUsers[0]
|
|
|
+ user.Age = 35
|
|
|
+ if err := updateUser(db, user); err != nil {
|
|
|
+ log.Printf("更新用户失败: %v", err)
|
|
|
+ } else {
|
|
|
+ fmt.Printf("\n用户 %s 年龄已更新为 %d\n", user.Name, user.Age)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 事务示例
|
|
|
+ if len(allUsers) >= 2 {
|
|
|
+ fromID := allUsers[0].ID
|
|
|
+ toID := allUsers[1].ID
|
|
|
+
|
|
|
+ fmt.Printf("\n交换用户 %d 和 %d 的年龄\n", fromID, toID)
|
|
|
+ if err := transferUsers(db, fromID, toID); err != nil {
|
|
|
+ log.Printf("事务失败: %v", err)
|
|
|
+ } else {
|
|
|
+ fmt.Println("事务成功")
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 再次获取所有用户
|
|
|
+ allUsers, err = getAllUsers(db)
|
|
|
+ if err != nil {
|
|
|
+ log.Fatal(err)
|
|
|
+ }
|
|
|
+
|
|
|
+ fmt.Println("\n更新后的用户:")
|
|
|
+ for _, user := range allUsers {
|
|
|
+ fmt.Printf("ID: %d, 姓名: %s, 邮箱: %s, 年龄: %d\n",
|
|
|
+ user.ID, user.Name, user.Email, user.Age)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 删除用户
|
|
|
+ if len(allUsers) > 0 {
|
|
|
+ user := allUsers[0]
|
|
|
+ if err := deleteUser(db, user.ID); err != nil {
|
|
|
+ log.Printf("删除用户失败: %v", err)
|
|
|
+ } else {
|
|
|
+ fmt.Printf("\n用户 %s 已删除\n", user.Name)
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### 3.4 JSON/XML数据处理
|
|
|
+```go
|
|
|
+package main
|
|
|
+
|
|
|
+import (
|
|
|
+ "encoding/json"
|
|
|
+ "encoding/xml"
|
|
|
+ "fmt"
|
|
|
+ "io"
|
|
|
+ "os"
|
|
|
+ "reflect"
|
|
|
+ "time"
|
|
|
+)
|
|
|
+
|
|
|
+// JSON序列化与反序列化
|
|
|
+type Config struct {
|
|
|
+ Host string `json:"host"`
|
|
|
+ Port int `json:"port"`
|
|
|
+ Debug bool `json:"debug"`
|
|
|
+ Database struct {
|
|
|
+ Name string `json:"name"`
|
|
|
+ User string `json:"user"`
|
|
|
+ Password string `json:"password"`
|
|
|
+ } `json:"database"`
|
|
|
+}
|
|
|
+
|
|
|
+// 用户结构体
|
|
|
+type User struct {
|
|
|
+ ID int `json:"id" xml:"id"`
|
|
|
+ Name string `json:"name" xml:"name"`
|
|
|
+ Email string `json:"email" xml:"email"`
|
|
|
+ Age int `json:"age" xml:"age"`
|
|
|
+ CreatedAt time.Time `json:"created_at" xml:"created_at"`
|
|
|
+}
|
|
|
+
|
|
|
+// 用户列表结构体(用于XML)
|
|
|
+type UserList struct {
|
|
|
+ XMLName xml.Name `xml:"users"`
|
|
|
+ Users []User `xml:"user"`
|
|
|
+}
|
|
|
+
|
|
|
+// 保存配置为JSON
|
|
|
+func saveConfig(config Config, filename string) error {
|
|
|
+ data, err := json.MarshalIndent(config, "", " ")
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ return os.WriteFile(filename, data, 0644)
|
|
|
+}
|
|
|
+
|
|
|
+// 从JSON加载配置
|
|
|
+func loadConfig(filename string) (Config, error) {
|
|
|
+ data, err := os.ReadFile(filename)
|
|
|
+ if err != nil {
|
|
|
+ return Config{}, err
|
|
|
+ }
|
|
|
+
|
|
|
+ var config Config
|
|
|
+ err = json.Unmarshal(data, &config)
|
|
|
+ return config, err
|
|
|
+}
|
|
|
+
|
|
|
+// 保存用户为JSON
|
|
|
+func saveUsersToJSON(users []User, filename string) error {
|
|
|
+ data, err := json.MarshalIndent(users, "", " ")
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ return os.WriteFile(filename, data, 0644)
|
|
|
+}
|
|
|
+
|
|
|
+// 从JSON加载用户
|
|
|
+func loadUsersFromJSON(filename string) ([]User, error) {
|
|
|
+ data, err := os.ReadFile(filename)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+
|
|
|
+ var users []User
|
|
|
+ err = json.Unmarshal(data, &users)
|
|
|
+ return users, err
|
|
|
+}
|
|
|
+
|
|
|
+// 保存用户为XML
|
|
|
+func saveUsersToXML(users []User, filename string) error {
|
|
|
+ userList := UserList{Users: users}
|
|
|
+ data, err := xml.MarshalIndent(userList, "", " ")
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ return os.WriteFile(filename, data, 0644)
|
|
|
+}
|
|
|
+
|
|
|
+// 从XML加载用户
|
|
|
+func loadUsersFromXML(filename string) ([]User, error) {
|
|
|
+ data, err := os.ReadFile(filename)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+
|
|
|
+ var userList UserList
|
|
|
+ err = xml.Unmarshal(data, &userList)
|
|
|
+ return userList.Users, err
|
|
|
+}
|
|
|
+
|
|
|
+// 流式JSON处理
|
|
|
+func streamJSONFile(filename string) error {
|
|
|
+ file, err := os.Open(filename)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ defer file.Close()
|
|
|
+
|
|
|
+ decoder := json.NewDecoder(file)
|
|
|
+
|
|
|
+ // 读取开头的 [
|
|
|
+ token, err := decoder.Token()
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ if token != json.Delim('[') {
|
|
|
+ return fmt.Errorf("期望JSON数组开始,得到: %v", token)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 读取数组中的每个对象
|
|
|
+ for decoder.More() {
|
|
|
+ var user User
|
|
|
+ err := decoder.Decode(&user)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ fmt.Printf("用户: %+v\n", user)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 读取结尾的 ]
|
|
|
+ token, err = decoder.Token()
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ if token != json.Delim(']') {
|
|
|
+ return fmt.Errorf("期望JSON数组结束,得到: %v", token)
|
|
|
+ }
|
|
|
+
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+// 流式XML处理
|
|
|
+func streamXMLFile(filename string) error {
|
|
|
+ file, err := os.Open(filename)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ defer file.Close()
|
|
|
+
|
|
|
+ decoder := xml.NewDecoder(file)
|
|
|
+
|
|
|
+ for {
|
|
|
+ token, err := decoder.Token()
|
|
|
+ if err == io.EOF {
|
|
|
+ break
|
|
|
+ }
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ switch se := token.(type) {
|
|
|
+ case xml.StartElement:
|
|
|
+ if se.Name.Local == "user" {
|
|
|
+ var user User
|
|
|
+ err := decoder.DecodeElement(&user, &se)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ fmt.Printf("用户: %+v\n", user)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+// 反射获取结构体标签
|
|
|
+func printStructTags(obj interface{}) {
|
|
|
+ v := reflect.ValueOf(obj)
|
|
|
+ t := reflect.TypeOf(obj)
|
|
|
+
|
|
|
+ if t.Kind() == reflect.Ptr {
|
|
|
+ t = t.Elem()
|
|
|
+ v = v.Elem()
|
|
|
+ }
|
|
|
+
|
|
|
+ if t.Kind() != reflect.Struct {
|
|
|
+ fmt.Println("不是结构体")
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ for i := 0; i < t.NumField(); i++ {
|
|
|
+ field := t.Field(i)
|
|
|
+ jsonTag := field.Tag.Get("json")
|
|
|
+ xmlTag := field.Tag.Get("xml")
|
|
|
+
|
|
|
+ fmt.Printf("字段: %s, 类型: %v, JSON标签: %s, XML标签: %s\n",
|
|
|
+ field.Name, field.Type, jsonTag, xmlTag)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func main() {
|
|
|
+ // 创建示例配置
|
|
|
+ config := Config{
|
|
|
+ Host: "localhost",
|
|
|
+ Port: 8080,
|
|
|
+ Debug: true,
|
|
|
+ }
|
|
|
+ config.Database.Name = "mydb"
|
|
|
+ config.Database.User = "admin"
|
|
|
+ config.Database.Password = "password"
|
|
|
+
|
|
|
+ // 保存配置
|
|
|
+ err := saveConfig(config, "config.json")
|
|
|
+ if err != nil {
|
|
|
+ fmt.Printf("保存配置失败: %v\n", err)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // 加载配置
|
|
|
+ loadedConfig, err := loadConfig("config.json")
|
|
|
+ if err != nil {
|
|
|
+ fmt.Printf("加载配置失败: %v\n", err)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ fmt.Printf("加载的配置: %+v\n", loadedConfig)
|
|
|
+
|
|
|
+ // 创建示例用户
|
|
|
+ users := []User{
|
|
|
+ {ID: 1, Name: "张三", Email: "[email protected]", Age: 25, CreatedAt: time.Now()},
|
|
|
+ {ID: 2, Name: "李四", Email: "[email protected]", Age: 30, CreatedAt: time.Now()},
|
|
|
+ {ID: 3, Name: "王五", Email: "[email protected]", Age: 28, CreatedAt: time.Now()},
|
|
|
+ }
|
|
|
+
|
|
|
+ // 保存用户为JSON
|
|
|
+ err = saveUsersToJSON(users, "users.json")
|
|
|
+ if err != nil {
|
|
|
+ fmt.Printf("保存用户JSON失败: %v\n", err)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // 保存用户为XML
|
|
|
+ err = saveUsersToXML(users, "users.xml")
|
|
|
+ if err != nil {
|
|
|
+ fmt.Printf("保存用户XML失败: %v\n", err)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // 从JSON加载用户
|
|
|
+ loadedUsers, err := loadUsersFromJSON("users.json")
|
|
|
+ if err != nil {
|
|
|
+ fmt.Printf("加载用户JSON失败: %v\n", err)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ fmt.Println("\n从JSON加载的用户:")
|
|
|
+ for _, user := range loadedUsers {
|
|
|
+ fmt.Printf("ID: %d, 姓名: %s, 邮箱: %s, 年龄: %d\n",
|
|
|
+ user.ID, user.Name, user.Email, user.Age)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 从XML加载用户
|
|
|
+ loadedUsersFromXML, err := loadUsersFromXML("users.xml")
|
|
|
+ if err != nil {
|
|
|
+ fmt.Printf("加载用户XML失败: %v\n", err)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ fmt.Println("\n从XML加载的用户:")
|
|
|
+ for _, user := range loadedUsersFromXML {
|
|
|
+ fmt.Printf("ID: %d, 姓名: %s, 邮箱: %s, 年龄: %d\n",
|
|
|
+ user.ID, user.Name, user.Email, user.Age)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 流式处理JSON
|
|
|
+ fmt.Println("\n流式处理JSON:")
|
|
|
+ err = streamJSONFile("users.json")
|
|
|
+ if err != nil {
|
|
|
+ fmt.Printf("流式处理JSON失败: %v\n", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 流式处理XML
|
|
|
+ fmt.Println("\n流式处理XML:")
|
|
|
+ err = streamXMLFile("users.xml")
|
|
|
+ if err != nil {
|
|
|
+ fmt.Printf("流式处理XML失败: %v\n", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 打印结构体标签
|
|
|
+ fmt.Println("\nUser结构体标签:")
|
|
|
+ printStructTags(User{})
|
|
|
+
|
|
|
+ // 清理文件
|
|
|
+ os.Remove("config.json")
|
|
|
+ os.Remove("users.json")
|
|
|
+ os.Remove("users.xml")
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### 3.5 测试与调试
|
|
|
+```go
|
|
|
+package main
|
|
|
+
|
|
|
+import (
|
|
|
+ "fmt"
|
|
|
+ "math"
|
|
|
+ "os"
|
|
|
+ "runtime"
|
|
|
+ "runtime/pprof"
|
|
|
+ "sort"
|
|
|
+ "testing"
|
|
|
+ "time"
|
|
|
+)
|
|
|
+
|
|
|
+// 被测试的函数
|
|
|
+func add(a, b int) int {
|
|
|
+ return a + b
|
|
|
+}
|
|
|
+
|
|
|
+func multiply(a, b int) int {
|
|
|
+ return a * b
|
|
|
+}
|
|
|
+
|
|
|
+func divide(a, b float64) (float64, error) {
|
|
|
+ if b == 0 {
|
|
|
+ return 0, fmt.Errorf("division by zero")
|
|
|
+ }
|
|
|
+ return a / b, nil
|
|
|
+}
|
|
|
+
|
|
|
+// 计算斐波那契数列
|
|
|
+func fibonacci(n int) int {
|
|
|
+ if n <= 1 {
|
|
|
+ return n
|
|
|
+ }
|
|
|
+ return fibonacci(n-1) + fibonacci(n-2)
|
|
|
+}
|
|
|
+
|
|
|
+// 优化的斐波那契数列
|
|
|
+func fibonacciOptimized(n int) int {
|
|
|
+ if n <= 1 {
|
|
|
+ return n
|
|
|
+ }
|
|
|
+
|
|
|
+ a, b := 0, 1
|
|
|
+ for i := 2; i <= n; i++ {
|
|
|
+ a, b = b, a+b
|
|
|
+ }
|
|
|
+ return b
|
|
|
+}
|
|
|
+
|
|
|
+// 排序函数
|
|
|
+func bubbleSort(arr []int) []int {
|
|
|
+ n := len(arr)
|
|
|
+ result := make([]int, n)
|
|
|
+ copy(result, arr)
|
|
|
+
|
|
|
+ for i := 0; i < n-1; i++ {
|
|
|
+ for j := 0; j < n-i-1; j++ {
|
|
|
+ if result[j] > result[j+1] {
|
|
|
+ result[j], result[j+1] = result[j+1], result[j]
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return result
|
|
|
+}
|
|
|
+
|
|
|
+// 单元测试
|
|
|
+func TestAdd(t *testing.T) {
|
|
|
+ tests := []struct {
|
|
|
+ a, b int
|
|
|
+ expected int
|
|
|
+ }{
|
|
|
+ {1, 2, 3},
|
|
|
+ {-1, 1, 0},
|
|
|
+ {0, 0, 0},
|
|
|
+ {100, 200, 300},
|
|
|
+ }
|
|
|
+
|
|
|
+ for _, test := range tests {
|
|
|
+ result := add(test.a, test.b)
|
|
|
+ if result != test.expected {
|
|
|
+ t.Errorf("add(%d, %d) = %d; expected %d", test.a, test.b, result, test.expected)
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func TestMultiply(t *testing.T) {
|
|
|
+ tests := []struct {
|
|
|
+ a, b int
|
|
|
+ expected int
|
|
|
+ }{
|
|
|
+ {2, 3, 6},
|
|
|
+ {-2, 3, -6},
|
|
|
+ {0, 5, 0},
|
|
|
+ {10, 10, 100},
|
|
|
+ }
|
|
|
+
|
|
|
+ for _, test := range tests {
|
|
|
+ result := multiply(test.a, test.b)
|
|
|
+ if result != test.expected {
|
|
|
+ t.Errorf("multiply(%d, %d) = %d; expected %d", test.a, test.b, result, test.expected)
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func TestDivide(t *testing.T) {
|
|
|
+ // 测试正常情况
|
|
|
+ result, err := divide(10, 2)
|
|
|
+ if err != nil {
|
|
|
+ t.Errorf("divide(10, 2) returned error: %v", err)
|
|
|
+ }
|
|
|
+ if math.Abs(result-5.0) > 1e-9 {
|
|
|
+ t.Errorf("divide(10, 2) = %f; expected 5.0", result)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 测试除零错误
|
|
|
+ _, err = divide(10, 0)
|
|
|
+ if err == nil {
|
|
|
+ t.Error("divide(10, 0) should return an error")
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func TestFibonacci(t *testing.T) {
|
|
|
+ tests := []struct {
|
|
|
+ n, expected int
|
|
|
+ }{
|
|
|
+ {0, 0},
|
|
|
+ {1, 1},
|
|
|
+ {2, 1},
|
|
|
+ {3, 2},
|
|
|
+ {4, 3},
|
|
|
+ {5, 5},
|
|
|
+ {10, 55},
|
|
|
+ }
|
|
|
+
|
|
|
+ for _, test := range tests {
|
|
|
+ result := fibonacci(test.n)
|
|
|
+ if result != test.expected {
|
|
|
+ t.Errorf("fibonacci(%d) = %d; expected %d", test.n, result, test.expected)
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func TestFibonacciOptimized(t *testing.T) {
|
|
|
+ tests := []struct {
|
|
|
+ n, expected int
|
|
|
+ }{
|
|
|
+ {0, 0},
|
|
|
+ {1, 1},
|
|
|
+ {2, 1},
|
|
|
+ {3, 2},
|
|
|
+ {4, 3},
|
|
|
+ {5, 5},
|
|
|
+ {10, 55},
|
|
|
+ {20, 6765},
|
|
|
+ }
|
|
|
+
|
|
|
+ for _, test := range tests {
|
|
|
+ result := fibonacciOptimized(test.n)
|
|
|
+ if result != test.expected {
|
|
|
+ t.Errorf("fibonacciOptimized(%d) = %d; expected %d", test.n, result, test.expected)
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func TestBubbleSort(t *testing.T) {
|
|
|
+ tests := []struct {
|
|
|
+ input []int
|
|
|
+ expected []int
|
|
|
+ }{
|
|
|
+ {[]int{5, 3, 8, 4, 2}, []int{2, 3, 4, 5, 8}},
|
|
|
+ {[]int{1, 2, 3, 4, 5}, []int{1, 2, 3, 4, 5}},
|
|
|
+ {[]int{5, 4, 3, 2, 1}, []int{1, 2, 3, 4, 5}},
|
|
|
+ {[]int{}, []int{}},
|
|
|
+ {[]int{42}, []int{42}},
|
|
|
+ }
|
|
|
+
|
|
|
+ for _, test := range tests {
|
|
|
+ result := bubbleSort(test.input)
|
|
|
+ if !equal(result, test.expected) {
|
|
|
+ t.Errorf("bubbleSort(%v) = %v; expected %v", test.input, result, test.expected)
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 辅助函数:比较两个整数切片是否相等
|
|
|
+func equal(a, b []int) bool {
|
|
|
+ if len(a) != len(b) {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ for i := range a {
|
|
|
+ if a[i] != b[i] {
|
|
|
+ return false
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return true
|
|
|
+}
|
|
|
+
|
|
|
+// 基准测试
|
|
|
+func BenchmarkAdd(b *testing.B) {
|
|
|
+ for i := 0; i < b.N; i++ {
|
|
|
+ add(1, 2)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func BenchmarkMultiply(b *testing.B) {
|
|
|
+ for i := 0; i < b.N; i++ {
|
|
|
+ multiply(123, 456)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func BenchmarkFibonacci(b *testing.B) {
|
|
|
+ for i := 0; i < b.N; i++ {
|
|
|
+ fibonacci(10)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func BenchmarkFibonacciOptimized(b *testing.B) {
|
|
|
+ for i := 0; i < b.N; i++ {
|
|
|
+ fibonacciOptimized(10)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func BenchmarkBubbleSort(b *testing.B) {
|
|
|
+ // 创建测试数据
|
|
|
+ data := make([]int, 100)
|
|
|
+ for i := range data {
|
|
|
+ data[i] = 100 - i // 逆序数组
|
|
|
+ }
|
|
|
+
|
|
|
+ b.ResetTimer()
|
|
|
+ for i := 0; i < b.N; i++ {
|
|
|
+ bubbleSort(data)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func BenchmarkSort(b *testing.B) {
|
|
|
+ // 创建测试数据
|
|
|
+ data := make([]int, 100)
|
|
|
+ for i := range data {
|
|
|
+ data[i] = 100 - i // 逆序数组
|
|
|
+ }
|
|
|
+
|
|
|
+ b.ResetTimer()
|
|
|
+ for i := 0; i < b.N; i++ {
|
|
|
+ // 复制数据以避免修改原始数据
|
|
|
+ testData := make([]int, len(data))
|
|
|
+ copy(testData, data)
|
|
|
+ sort.Ints(testData)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 性能分析
|
|
|
+func profileCPU(filename string, fn func()) error {
|
|
|
+ f, err := os.Create(filename)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ defer f.Close()
|
|
|
+
|
|
|
+ if err := pprof.StartCPUProfile(f); err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ defer pprof.StopCPUProfile()
|
|
|
+
|
|
|
+ fn()
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+// 内存分析
|
|
|
+func profileMemory(filename string, fn func()) error {
|
|
|
+ f, err := os.Create(filename)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ defer f.Close()
|
|
|
+
|
|
|
+ runtime.GC() // 获取最新的GC数据
|
|
|
+
|
|
|
+ if err := pprof.WriteHeapProfile(f); err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ fn()
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+// 示例函数
|
|
|
+func ExampleAdd() {
|
|
|
+ result := add(2, 3)
|
|
|
+ fmt.Println(result)
|
|
|
+ // Output: 5
|
|
|
+}
|
|
|
+
|
|
|
+func ExampleFibonacci() {
|
|
|
+ fmt.Println(fibonacci(5))
|
|
|
+ // Output: 5
|
|
|
+}
|
|
|
+
|
|
|
+// 测试主函数
|
|
|
+func main() {
|
|
|
+ // 性能分析示例
|
|
|
+ fmt.Println("开始性能分析...")
|
|
|
+
|
|
|
+ // CPU性能分析
|
|
|
+ err := profileCPU("cpu.prof", func() {
|
|
|
+ // 执行一些计算密集型任务
|
|
|
+ for i := 0; i < 1000; i++ {
|
|
|
+ fibonacciOptimized(20)
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ fmt.Printf("CPU性能分析失败: %v\n", err)
|
|
|
+ } else {
|
|
|
+ fmt.Println("CPU性能分析完成,结果保存到 cpu.prof")
|
|
|
+ }
|
|
|
+
|
|
|
+ // 内存性能分析
|
|
|
+ err = profileMemory("mem.prof", func() {
|
|
|
+ // 创建一些数据
|
|
|
+ data := make([][]int, 100)
|
|
|
+ for i := range data {
|
|
|
+ data[i] = make([]int, 1000)
|
|
|
+ for j := range data[i] {
|
|
|
+ data[i][j] = i * j
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ fmt.Printf("内存性能分析失败: %v\n", err)
|
|
|
+ } else {
|
|
|
+ fmt.Println("内存性能分析完成,结果保存到 mem.prof")
|
|
|
+ }
|
|
|
+
|
|
|
+ // 运行基准测试
|
|
|
+ fmt.Println("\n运行基准测试...")
|
|
|
+ result := testing.Benchmark(BenchmarkFibonacci)
|
|
|
+ fmt.Printf("fibonacci(10) 基准测试结果: %s\n", result)
|
|
|
+
|
|
|
+ result = testing.Benchmark(BenchmarkFibonacciOptimized)
|
|
|
+ fmt.Printf("fibonacciOptimized(10) 基准测试结果: %s\n", result)
|
|
|
+
|
|
|
+ // 比较两种斐波那契实现的性能
|
|
|
+ n := 30
|
|
|
+ start := time.Now()
|
|
|
+ result1 := fibonacci(n)
|
|
|
+ duration1 := time.Since(start)
|
|
|
+
|
|
|
+ start = time.Now()
|
|
|
+ result2 := fibonacciOptimized(n)
|
|
|
+ duration2 := time.Since(start)
|
|
|
+
|
|
|
+ fmt.Printf("\n斐波那契数列第%d项:\n", n)
|
|
|
+ fmt.Printf("递归实现: %d, 耗时: %v\n", result1, duration1)
|
|
|
+ fmt.Printf("优化实现: %d, 耗时: %v\n", result2, duration2)
|
|
|
+ fmt.Printf("性能提升: %.2fx\n", float64(duration1)/float64(duration2))
|
|
|
+
|
|
|
+ // 清理分析文件
|
|
|
+ os.Remove("cpu.prof")
|
|
|
+ os.Remove("mem.prof")
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+## 练习项目
|
|
|
+
|
|
|
+### 项目1:文件备份工具
|
|
|
+```go
|
|
|
+package main
|
|
|
+
|
|
|
+import (
|
|
|
+ "archive/zip"
|
|
|
+ "bufio"
|
|
|
+ "crypto/md5"
|
|
|
+ "fmt"
|
|
|
+ "io"
|
|
|
+ "os"
|
|
|
+ "path/filepath"
|
|
|
+ "sort"
|
|
|
+ "strings"
|
|
|
+ "time"
|
|
|
+)
|
|
|
+
|
|
|
+// 文件信息
|
|
|
+type FileInfo struct {
|
|
|
+ Path string
|
|
|
+ Size int64
|
|
|
+ ModTime time.Time
|
|
|
+ Hash string
|
|
|
+ IsDir bool
|
|
|
+}
|
|
|
+
|
|
|
+// 备份信息
|
|
|
+type BackupInfo struct {
|
|
|
+ Name string
|
|
|
+ Timestamp time.Time
|
|
|
+ Files []FileInfo
|
|
|
+ Size int64
|
|
|
+}
|
|
|
+
|
|
|
+// 备份管理器
|
|
|
+type BackupManager struct {
|
|
|
+ sourceDir string
|
|
|
+ backupDir string
|
|
|
+ indexFile string
|
|
|
+}
|
|
|
+
|
|
|
+// 创建新的备份管理器
|
|
|
+func NewBackupManager(sourceDir, backupDir string) *BackupManager {
|
|
|
+ return &BackupManager{
|
|
|
+ sourceDir: sourceDir,
|
|
|
+ backupDir: backupDir,
|
|
|
+ indexFile: filepath.Join(backupDir, "index.json"),
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 获取文件哈希
|
|
|
+func getFileHash(filePath string) (string, error) {
|
|
|
+ file, err := os.Open(filePath)
|
|
|
+ if err != nil {
|
|
|
+ return "", err
|
|
|
+ }
|
|
|
+ defer file.Close()
|
|
|
+
|
|
|
+ hash := md5.New()
|
|
|
+ if _, err := io.Copy(hash, file); err != nil {
|
|
|
+ return "", err
|
|
|
+ }
|
|
|
+
|
|
|
+ return fmt.Sprintf("%x", hash.Sum(nil)), nil
|
|
|
+}
|
|
|
+
|
|
|
+// 扫描目录获取文件信息
|
|
|
+func (bm *BackupManager) scanDirectory() ([]FileInfo, error) {
|
|
|
+ var files []FileInfo
|
|
|
+
|
|
|
+ err := filepath.Walk(bm.sourceDir, func(path string, info os.FileInfo, err error) error {
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取相对路径
|
|
|
+ relPath, err := filepath.Rel(bm.sourceDir, path)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ // 跳过备份目录本身
|
|
|
+ if strings.HasPrefix(relPath, bm.backupDir) {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+
|
|
|
+ fileHash := ""
|
|
|
+ if !info.IsDir() {
|
|
|
+ fileHash, err = getFileHash(path)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ files = append(files, FileInfo{
|
|
|
+ Path: relPath,
|
|
|
+ Size: info.Size(),
|
|
|
+ ModTime: info.ModTime(),
|
|
|
+ Hash: fileHash,
|
|
|
+ IsDir: info.IsDir(),
|
|
|
+ })
|
|
|
+
|
|
|
+ return nil
|
|
|
+ })
|
|
|
+
|
|
|
+ return files, err
|
|
|
+}
|
|
|
+
|
|
|
+// 创建备份
|
|
|
+func (bm *BackupManager) Backup() error {
|
|
|
+ // 确保备份目录存在
|
|
|
+ if err := os.MkdirAll(bm.backupDir, 0755); err != nil {
|
|
|
+ return fmt.Errorf("创建备份目录失败: %v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 扫描源目录
|
|
|
+ files, err := bm.scanDirectory()
|
|
|
+ if err != nil {
|
|
|
+ return fmt.Errorf("扫描目录失败: %v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 创建备份名称
|
|
|
+ timestamp := time.Now().Format("20060102_150405")
|
|
|
+ backupName := fmt.Sprintf("backup_%s.zip", timestamp)
|
|
|
+ backupPath := filepath.Join(bm.backupDir, backupName)
|
|
|
+
|
|
|
+ // 创建ZIP文件
|
|
|
+ zipFile, err := os.Create(backupPath)
|
|
|
+ if err != nil {
|
|
|
+ return fmt.Errorf("创建备份文件失败: %v", err)
|
|
|
+ }
|
|
|
+ defer zipFile.Close()
|
|
|
+
|
|
|
+ zipWriter := zip.NewWriter(zipFile)
|
|
|
+ defer zipWriter.Close()
|
|
|
+
|
|
|
+ var totalSize int64
|
|
|
+
|
|
|
+ // 添加文件到ZIP
|
|
|
+ for _, file := range files {
|
|
|
+ if file.IsDir {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+
|
|
|
+ // 打开源文件
|
|
|
+ sourcePath := filepath.Join(bm.sourceDir, file.Path)
|
|
|
+ sourceFile, err := os.Open(sourcePath)
|
|
|
+ if err != nil {
|
|
|
+ return fmt.Errorf("打开源文件失败: %v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 在ZIP中创建文件
|
|
|
+ header, err := zip.FileInfoHeader(fileInfoFromPath(sourcePath))
|
|
|
+ if err != nil {
|
|
|
+ sourceFile.Close()
|
|
|
+ return fmt.Errorf("创建ZIP头失败: %v", err)
|
|
|
+ }
|
|
|
+ header.Name = file.Path
|
|
|
+ header.Method = zip.Deflate
|
|
|
+
|
|
|
+ writer, err := zipWriter.CreateHeader(header)
|
|
|
+ if err != nil {
|
|
|
+ sourceFile.Close()
|
|
|
+ return fmt.Errorf("在ZIP中创建文件失败: %v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 复制文件内容
|
|
|
+ _, err = io.Copy(writer, sourceFile)
|
|
|
+ sourceFile.Close()
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ return fmt.Errorf("复制文件内容失败: %v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ totalSize += file.Size
|
|
|
+ }
|
|
|
+
|
|
|
+ // 保存备份信息
|
|
|
+ backupInfo := BackupInfo{
|
|
|
+ Name: backupName,
|
|
|
+ Timestamp: time.Now(),
|
|
|
+ Files: files,
|
|
|
+ Size: totalSize,
|
|
|
+ }
|
|
|
+
|
|
|
+ if err := bm.saveBackupInfo(backupInfo); err != nil {
|
|
|
+ return fmt.Errorf("保存备份信息失败: %v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ fmt.Printf("备份完成: %s (大小: %.2f MB)\n", backupName, float64(totalSize)/1024/1024)
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+// 从路径创建文件信息
|
|
|
+func fileInfoFromPath(path string) os.FileInfo {
|
|
|
+ info, _ := os.Stat(path)
|
|
|
+ return info
|
|
|
+}
|
|
|
+
|
|
|
+// 保存备份信息
|
|
|
+func (bm *BackupManager) saveBackupInfo(info BackupInfo) error {
|
|
|
+ // 这里简化处理,实际应用中可以使用JSON或其他格式
|
|
|
+ indexFile, err := os.OpenFile(bm.indexFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ defer indexFile.Close()
|
|
|
+
|
|
|
+ writer := bufio.NewWriter(indexFile)
|
|
|
+ defer writer.Flush()
|
|
|
+
|
|
|
+ line := fmt.Sprintf("%s|%s|%d\n", info.Name, info.Timestamp.Format(time.RFC3339), info.Size)
|
|
|
+ _, err = writer.WriteString(line)
|
|
|
+ return err
|
|
|
+}
|
|
|
+
|
|
|
+// 列出所有备份
|
|
|
+func (bm *BackupManager) ListBackups() ([]BackupInfo, error) {
|
|
|
+ file, err := os.Open(bm.indexFile)
|
|
|
+ if err != nil {
|
|
|
+ if os.IsNotExist(err) {
|
|
|
+ return []BackupInfo{}, nil
|
|
|
+ }
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ defer file.Close()
|
|
|
+
|
|
|
+ var backups []BackupInfo
|
|
|
+ scanner := bufio.NewScanner(file)
|
|
|
+
|
|
|
+ for scanner.Scan() {
|
|
|
+ line := scanner.Text()
|
|
|
+ parts := strings.Split(line, "|")
|
|
|
+ if len(parts) != 3 {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+
|
|
|
+ timestamp, err := time.Parse(time.RFC3339, parts[1])
|
|
|
+ if err != nil {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+
|
|
|
+ var size int64
|
|
|
+ fmt.Sscanf(parts[2], "%d", &size)
|
|
|
+
|
|
|
+ backups = append(backups, BackupInfo{
|
|
|
+ Name: parts[0],
|
|
|
+ Timestamp: timestamp,
|
|
|
+ Size: size,
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ // 按时间戳排序
|
|
|
+ sort.Slice(backups, func(i, j int) bool {
|
|
|
+ return backups[i].Timestamp.After(backups[j].Timestamp)
|
|
|
+ })
|
|
|
+
|
|
|
+ return backups, nil
|
|
|
+}
|
|
|
+
|
|
|
+// 恢复备份
|
|
|
+func (bm *BackupManager) Restore(backupName string) error {
|
|
|
+ backupPath := filepath.Join(bm.backupDir, backupName)
|
|
|
+
|
|
|
+ // 打开ZIP文件
|
|
|
+ reader, err := zip.OpenReader(backupPath)
|
|
|
+ if err != nil {
|
|
|
+ return fmt.Errorf("打开备份文件失败: %v", err)
|
|
|
+ }
|
|
|
+ defer reader.Close()
|
|
|
+
|
|
|
+ // 解压文件
|
|
|
+ for _, file := range reader.File {
|
|
|
+ path := filepath.Join(bm.sourceDir, file.Name)
|
|
|
+
|
|
|
+ // 创建目录
|
|
|
+ if file.FileInfo().IsDir() {
|
|
|
+ if err := os.MkdirAll(path, file.FileInfo().Mode()); err != nil {
|
|
|
+ return fmt.Errorf("创建目录失败: %v", err)
|
|
|
+ }
|
|
|
+ continue
|
|
|
+ }
|
|
|
+
|
|
|
+ // 确保父目录存在
|
|
|
+ if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
|
|
|
+ return fmt.Errorf("创建父目录失败: %v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 打开ZIP中的文件
|
|
|
+ fileReader, err := file.Open()
|
|
|
+ if err != nil {
|
|
|
+ return fmt.Errorf("打开ZIP中的文件失败: %v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 创建目标文件
|
|
|
+ targetFile, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.FileInfo().Mode())
|
|
|
+ if err != nil {
|
|
|
+ fileReader.Close()
|
|
|
+ return fmt.Errorf("创建目标文件失败: %v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 复制文件内容
|
|
|
+ _, err = io.Copy(targetFile, fileReader)
|
|
|
+ fileReader.Close()
|
|
|
+ targetFile.Close()
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ return fmt.Errorf("复制文件内容失败: %v", err)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ fmt.Printf("恢复完成: %s\n", backupName)
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+// 增量备份
|
|
|
+func (bm *BackupManager) IncrementalBackup() error {
|
|
|
+ // 获取最新的备份信息
|
|
|
+ backups, err := bm.ListBackups()
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ if len(backups) == 0 {
|
|
|
+ // 如果没有备份,执行完整备份
|
|
|
+ return bm.Backup()
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取当前文件信息
|
|
|
+ currentFiles, err := bm.scanDirectory()
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ // 这里简化处理,实际应用中需要比较文件哈希和修改时间
|
|
|
+ // 只备份有变化的文件
|
|
|
+ var changedFiles []FileInfo
|
|
|
+ for _, file := range currentFiles {
|
|
|
+ if file.IsDir {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+
|
|
|
+ // 简化处理:备份最近修改的文件
|
|
|
+ if time.Since(file.ModTime) < 24*time.Hour {
|
|
|
+ changedFiles = append(changedFiles, file)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if len(changedFiles) == 0 {
|
|
|
+ fmt.Println("没有文件需要备份")
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+
|
|
|
+ // 创建增量备份
|
|
|
+ timestamp := time.Now().Format("20060102_150405")
|
|
|
+ backupName := fmt.Sprintf("incremental_%s.zip", timestamp)
|
|
|
+ backupPath := filepath.Join(bm.backupDir, backupName)
|
|
|
+
|
|
|
+ // 创建ZIP文件
|
|
|
+ zipFile, err := os.Create(backupPath)
|
|
|
+ if err != nil {
|
|
|
+ return fmt.Errorf("创建备份文件失败: %v", err)
|
|
|
+ }
|
|
|
+ defer zipFile.Close()
|
|
|
+
|
|
|
+ zipWriter := zip.NewWriter(zipFile)
|
|
|
+ defer zipWriter.Close()
|
|
|
+
|
|
|
+ var totalSize int64
|
|
|
+
|
|
|
+ // 添加变化的文件到ZIP
|
|
|
+ for _, file := range changedFiles {
|
|
|
+ sourcePath := filepath.Join(bm.sourceDir, file.Path)
|
|
|
+ sourceFile, err := os.Open(sourcePath)
|
|
|
+ if err != nil {
|
|
|
+ return fmt.Errorf("打开源文件失败: %v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ header, err := zip.FileInfoHeader(fileInfoFromPath(sourcePath))
|
|
|
+ if err != nil {
|
|
|
+ sourceFile.Close()
|
|
|
+ return fmt.Errorf("创建ZIP头失败: %v", err)
|
|
|
+ }
|
|
|
+ header.Name = file.Path
|
|
|
+ header.Method = zip.Deflate
|
|
|
+
|
|
|
+ writer, err := zipWriter.CreateHeader(header)
|
|
|
+ if err != nil {
|
|
|
+ sourceFile.Close()
|
|
|
+ return fmt.Errorf("在ZIP中创建文件失败: %v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ _, err = io.Copy(writer, sourceFile)
|
|
|
+ sourceFile.Close()
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ return fmt.Errorf("复制文件内容失败: %v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ totalSize += file.Size
|
|
|
+ }
|
|
|
+
|
|
|
+ // 保存备份信息
|
|
|
+ backupInfo := BackupInfo{
|
|
|
+ Name: backupName,
|
|
|
+ Timestamp: time.Now(),
|
|
|
+ Files: changedFiles,
|
|
|
+ Size: totalSize,
|
|
|
+ }
|
|
|
+
|
|
|
+ if err := bm.saveBackupInfo(backupInfo); err != nil {
|
|
|
+ return fmt.Errorf("保存备份信息失败: %v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ fmt.Printf("增量备份完成: %s (文件数: %d, 大小: %.2f MB)\n",
|
|
|
+ backupName, len(changedFiles), float64(totalSize)/1024/1024)
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+func main() {
|
|
|
+ if len(os.Args) < 3 {
|
|
|
+ fmt.Println("使用方法:")
|
|
|
+ fmt.Println(" 完整备份: go run backup.go backup <源目录> <备份目录>")
|
|
|
+ fmt.Println(" 增量备份: go run backup.go incremental <源目录> <备份目录>")
|
|
|
+ fmt.Println(" 恢复备份: go run backup.go restore <源目录> <备份目录> <备份文件名>")
|
|
|
+ fmt.Println(" 列出备份: go run backup.go list <备份目录>")
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ command := os.Args[1]
|
|
|
+
|
|
|
+ switch command {
|
|
|
+ case "backup":
|
|
|
+ if len(os.Args) < 4 {
|
|
|
+ fmt.Println("请指定源目录和备份目录")
|
|
|
+ return
|
|
|
+ }
|
|
|
+ sourceDir := os.Args[2]
|
|
|
+ backupDir := os.Args[3]
|
|
|
+
|
|
|
+ manager := NewBackupManager(sourceDir, backupDir)
|
|
|
+ if err := manager.Backup(); err != nil {
|
|
|
+ fmt.Printf("备份失败: %v\n", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ case "incremental":
|
|
|
+ if len(os.Args) < 4 {
|
|
|
+ fmt.Println("请指定源目录和备份目录")
|
|
|
+ return
|
|
|
+ }
|
|
|
+ sourceDir := os.Args[2]
|
|
|
+ backupDir := os.Args[3]
|
|
|
+
|
|
|
+ manager := NewBackupManager(sourceDir, backupDir)
|
|
|
+ if err := manager.IncrementalBackup(); err != nil {
|
|
|
+ fmt.Printf("增量备份失败: %v\n", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ case "restore":
|
|
|
+ if len(os.Args) < 5 {
|
|
|
+ fmt.Println("请指定源目录、备份目录和备份文件名")
|
|
|
+ return
|
|
|
+ }
|
|
|
+ sourceDir := os.Args[2]
|
|
|
+ backupDir := os.Args[3]
|
|
|
+ backupName := os.Args[4]
|
|
|
+
|
|
|
+ manager := NewBackupManager(sourceDir, backupDir)
|
|
|
+ if err := manager.Restore(backupName); err != nil {
|
|
|
+ fmt.Printf("恢复失败: %v\n", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ case "list":
|
|
|
+ if len(os.Args) < 3 {
|
|
|
+ fmt.Println("请指定备份目录")
|
|
|
+ return
|
|
|
+ }
|
|
|
+ backupDir := os.Args[2]
|
|
|
+
|
|
|
+ manager := NewBackupManager("", backupDir)
|
|
|
+ backups, err := manager.ListBackups()
|
|
|
+ if err != nil {
|
|
|
+ fmt.Printf("列出备份失败: %v\n", err)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ if len(backups) == 0 {
|
|
|
+ fmt.Println("没有找到备份")
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ fmt.Println("可用备份:")
|
|
|
+ for _, backup := range backups {
|
|
|
+ fmt.Printf("%s - %s (%.2f MB)\n",
|
|
|
+ backup.Name,
|
|
|
+ backup.Timestamp.Format("2006-01-02 15:04:05"),
|
|
|
+ float64(backup.Size)/1024/1024)
|
|
|
+ }
|
|
|
+
|
|
|
+ default:
|
|
|
+ fmt.Println("未知命令")
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### 项目2:Web爬虫
|
|
|
+```go
|
|
|
+package main
|
|
|
+
|
|
|
+import (
|
|
|
+ "fmt"
|
|
|
+ "io"
|
|
|
+ "net/http"
|
|
|
+ "net/url"
|
|
|
+ "os"
|
|
|
+ "regexp"
|
|
|
+ "strings"
|
|
|
+ "sync"
|
|
|
+ "time"
|
|
|
+)
|
|
|
+
|
|
|
+// 网页信息
|
|
|
+type PageInfo struct {
|
|
|
+ URL string
|
|
|
+ Title string
|
|
|
+ Links []string
|
|
|
+ Error error
|
|
|
+}
|
|
|
+
|
|
|
+// 爬虫结构体
|
|
|
+type Crawler struct {
|
|
|
+ visited map[string]bool
|
|
|
+ mutex sync.Mutex
|
|
|
+ maxDepth int
|
|
|
+ delay time.Duration
|
|
|
+ userAgent string
|
|
|
+ resultChan chan PageInfo
|
|
|
+ wg sync.WaitGroup
|
|
|
+}
|
|
|
+
|
|
|
+// 创建新的爬虫
|
|
|
+func NewCrawler(maxDepth int, delay time.Duration) *Crawler {
|
|
|
+ return &Crawler{
|
|
|
+ visited: make(map[string]bool),
|
|
|
+ maxDepth: maxDepth,
|
|
|
+ delay: delay,
|
|
|
+ userAgent: "GoCrawler/1.0",
|
|
|
+ resultChan: make(chan PageInfo, 100),
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 提取网页标题
|
|
|
+func extractTitle(html string) string {
|
|
|
+ // 简单的正则表达式提取标题
|
|
|
+ titleRegex := regexp.MustCompile(`<title>(.*?)</title>`)
|
|
|
+ matches := titleRegex.FindStringSubmatch(html)
|
|
|
+ if len(matches) > 1 {
|
|
|
+ return strings.TrimSpace(matches[1])
|
|
|
+ }
|
|
|
+ return ""
|
|
|
+}
|
|
|
+
|
|
|
+// 提取链接
|
|
|
+func (c *Crawler) extractLinks(baseURL string, html string) []string {
|
|
|
+ // 提取所有href属性
|
|
|
+ linkRegex := regexp.MustCompile(`<a[^>]+href=["']([^"']+)["']`)
|
|
|
+ matches := linkRegex.FindAllStringSubmatch(html, -1)
|
|
|
+
|
|
|
+ var links []string
|
|
|
+ base, err := url.Parse(baseURL)
|
|
|
+ if err != nil {
|
|
|
+ return links
|
|
|
+ }
|
|
|
+
|
|
|
+ for _, match := range matches {
|
|
|
+ if len(match) > 1 {
|
|
|
+ href := match[1]
|
|
|
+
|
|
|
+ // 解析相对URL
|
|
|
+ parsedURL, err := url.Parse(href)
|
|
|
+ if err != nil {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+
|
|
|
+ absoluteURL := base.ResolveReference(parsedURL).String()
|
|
|
+
|
|
|
+ // 只保留同域名的链接
|
|
|
+ if parsedURL.Host == "" || parsedURL.Host == base.Host {
|
|
|
+ links = append(links, absoluteURL)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return links
|
|
|
+}
|
|
|
+
|
|
|
+// 获取网页内容
|
|
|
+func (c *Crawler) fetchPage(urlStr string) (string, error) {
|
|
|
+ // 检查是否已访问
|
|
|
+ c.mutex.Lock()
|
|
|
+ if c.visited[urlStr] {
|
|
|
+ c.mutex.Unlock()
|
|
|
+ return "", fmt.Errorf("URL已访问: %s", urlStr)
|
|
|
+ }
|
|
|
+ c.visited[urlStr] = true
|
|
|
+ c.mutex.Unlock()
|
|
|
+
|
|
|
+ // 创建请求
|
|
|
+ req, err := http.NewRequest("GET", urlStr, nil)
|
|
|
+ if err != nil {
|
|
|
+ return "", err
|
|
|
+ }
|
|
|
+
|
|
|
+ req.Header.Set("User-Agent", c.userAgent)
|
|
|
+
|
|
|
+ // 发送请求
|
|
|
+ client := &http.Client{Timeout: 10 * time.Second}
|
|
|
+ resp, err := client.Do(req)
|
|
|
+ if err != nil {
|
|
|
+ return "", err
|
|
|
+ }
|
|
|
+ defer resp.Body.Close()
|
|
|
+
|
|
|
+ // 检查状态码
|
|
|
+ if resp.StatusCode != http.StatusOK {
|
|
|
+ return "", fmt.Errorf("HTTP错误: %d", resp.StatusCode)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 读取内容
|
|
|
+ body, err := io.ReadAll(resp.Body)
|
|
|
+ if err != nil {
|
|
|
+ return "", err
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查内容类型
|
|
|
+ contentType := resp.Header.Get("Content-Type")
|
|
|
+ if !strings.Contains(contentType, "text/html") {
|
|
|
+ return "", fmt.Errorf("非HTML内容: %s", contentType)
|
|
|
+ }
|
|
|
+
|
|
|
+ return string(body), nil
|
|
|
+}
|
|
|
+
|
|
|
+// 爬取单个页面
|
|
|
+func (c *Crawler) crawlPage(urlStr string, depth int) {
|
|
|
+ defer c.wg.Done()
|
|
|
+
|
|
|
+ // 检查深度
|
|
|
+ if depth > c.maxDepth {
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // 延迟请求
|
|
|
+ time.Sleep(c.delay)
|
|
|
+
|
|
|
+ // 获取页面内容
|
|
|
+ html, err := c.fetchPage(urlStr)
|
|
|
+
|
|
|
+ // 提取信息
|
|
|
+ pageInfo := PageInfo{
|
|
|
+ URL: urlStr,
|
|
|
+ Error: err,
|
|
|
+ }
|
|
|
+
|
|
|
+ if err == nil {
|
|
|
+ pageInfo.Title = extractTitle(html)
|
|
|
+ pageInfo.Links = c.extractLinks(urlStr, html)
|
|
|
+
|
|
|
+ // 爬取链接页面
|
|
|
+ for _, link := range pageInfo.Links {
|
|
|
+ c.wg.Add(1)
|
|
|
+ go c.crawlPage(link, depth+1)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 发送结果
|
|
|
+ c.resultChan <- pageInfo
|
|
|
+}
|
|
|
+
|
|
|
+// 开始爬取
|
|
|
+func (c *Crawler) Crawl(startURL string) []PageInfo {
|
|
|
+ // 启动爬取
|
|
|
+ c.wg.Add(1)
|
|
|
+ go c.crawlPage(startURL, 0)
|
|
|
+
|
|
|
+ // 启动结果收集器
|
|
|
+ var results []PageInfo
|
|
|
+ done := make(chan struct{})
|
|
|
+
|
|
|
+ go func() {
|
|
|
+ for page := range c.resultChan {
|
|
|
+ results = append(results, page)
|
|
|
+ }
|
|
|
+ close(done)
|
|
|
+ }()
|
|
|
+
|
|
|
+ // 等待所有爬取完成
|
|
|
+ c.wg.Wait()
|
|
|
+ close(c.resultChan)
|
|
|
+
|
|
|
+ // 等待结果收集完成
|
|
|
+ <-done
|
|
|
+
|
|
|
+ return results
|
|
|
+}
|
|
|
+
|
|
|
+// 保存结果到文件
|
|
|
+func saveResults(results []PageInfo, filename string) error {
|
|
|
+ file, err := os.Create(filename)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ defer file.Close()
|
|
|
+
|
|
|
+ for _, page := range results {
|
|
|
+ if page.Error != nil {
|
|
|
+ fmt.Fprintf(file, "URL: %s\n错误: %v\n\n", page.URL, page.Error)
|
|
|
+ } else {
|
|
|
+ fmt.Fprintf(file, "URL: %s\n标题: %s\n链接数: %d\n\n",
|
|
|
+ page.URL, page.Title, len(page.Links))
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+// 打印统计信息
|
|
|
+func printStats(results []PageInfo) {
|
|
|
+ var successCount, errorCount int
|
|
|
+ var totalLinks int
|
|
|
+
|
|
|
+ for _, page := range results {
|
|
|
+ if page.Error != nil {
|
|
|
+ errorCount++
|
|
|
+ } else {
|
|
|
+ successCount++
|
|
|
+ totalLinks += len(page.Links)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ fmt.Printf("\n爬取统计:\n")
|
|
|
+ fmt.Printf("成功页面: %d\n", successCount)
|
|
|
+ fmt.Printf("失败页面: %d\n", errorCount)
|
|
|
+ fmt.Printf("总链接数: %d\n", totalLinks)
|
|
|
+ fmt.Printf("平均每页链接数: %.2f\n", float64(totalLinks)/float64(successCount))
|
|
|
+}
|
|
|
+
|
|
|
+func main() {
|
|
|
+ if len(os.Args) < 2 {
|
|
|
+ fmt.Println("使用方法: go run crawler.go <起始URL> [深度] [延迟(秒)]")
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ startURL := os.Args[1]
|
|
|
+ maxDepth := 2
|
|
|
+ delay := 1 * time.Second
|
|
|
+
|
|
|
+ if len(os.Args) >= 3 {
|
|
|
+ fmt.Sscanf(os.Args[2], "%d", &maxDepth)
|
|
|
+ }
|
|
|
+
|
|
|
+ if len(os.Args) >= 4 {
|
|
|
+ delaySec := 0
|
|
|
+ fmt.Sscanf(os.Args[3], "%d", &delaySec)
|
|
|
+ delay = time.Duration(delaySec) * time.Second
|
|
|
+ }
|
|
|
+
|
|
|
+ fmt.Printf("开始爬取: %s (深度: %d, 延迟: %v)\n", startURL, maxDepth, delay)
|
|
|
+
|
|
|
+ // 创建爬虫
|
|
|
+ crawler := NewCrawler(maxDepth, delay)
|
|
|
+
|
|
|
+ // 开始爬取
|
|
|
+ start := time.Now()
|
|
|
+ results := crawler.Crawl(startURL)
|
|
|
+ duration := time.Since(start)
|
|
|
+
|
|
|
+ // 打印结果
|
|
|
+ fmt.Printf("\n爬取完成,耗时: %v\n", duration)
|
|
|
+ printStats(results)
|
|
|
+
|
|
|
+ // 保存结果
|
|
|
+ filename := fmt.Sprintf("crawl_results_%s.txt", time.Now().Format("20060102_150405"))
|
|
|
+ if err := saveResults(results, filename); err != nil {
|
|
|
+ fmt.Printf("保存结果失败: %v\n", err)
|
|
|
+ } else {
|
|
|
+ fmt.Printf("结果已保存到: %s\n", filename)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 打印前几个结果
|
|
|
+ fmt.Println("\n前5个结果:")
|
|
|
+ count := 0
|
|
|
+ for _, page := range results {
|
|
|
+ if count >= 5 {
|
|
|
+ break
|
|
|
+ }
|
|
|
+
|
|
|
+ if page.Error != nil {
|
|
|
+ fmt.Printf("%d. %s - 错误: %v\n", count+1, page.URL, page.Error)
|
|
|
+ } else {
|
|
|
+ fmt.Printf("%d. %s - 标题: %s\n", count+1, page.URL, page.Title)
|
|
|
+ }
|
|
|
+ count++
|
|
|
+ }
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+### 项目3:数据库CRUD应用
|
|
|
+```go
|
|
|
+package main
|
|
|
+
|
|
|
+import (
|
|
|
+ "database/sql"
|
|
|
+ "fmt"
|
|
|
+ "log"
|
|
|
+ "os"
|
|
|
+ "strconv"
|
|
|
+ "strings"
|
|
|
+ "time"
|
|
|
+
|
|
|
+ _ "github.com/mattn/go-sqlite3"
|
|
|
+)
|
|
|
+
|
|
|
+// 用户模型
|
|
|
+type User struct {
|
|
|
+ ID int `json:"id"`
|
|
|
+ Name string `json:"name"`
|
|
|
+ Email string `json:"email"`
|
|
|
+ Age int `json:"age"`
|
|
|
+ Address string `json:"address"`
|
|
|
+ CreatedAt time.Time `json:"created_at"`
|
|
|
+ UpdatedAt time.Time `json:"updated_at"`
|
|
|
+}
|
|
|
+
|
|
|
+// 用户服务
|
|
|
+type UserService struct {
|
|
|
+ db *sql.DB
|
|
|
+}
|
|
|
+
|
|
|
+// 创建新的用户服务
|
|
|
+func NewUserService(dbPath string) (*UserService, error) {
|
|
|
+ db, err := sql.Open("sqlite3", dbPath)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+
|
|
|
+ // 测试连接
|
|
|
+ if err := db.Ping(); err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+
|
|
|
+ // 创建表
|
|
|
+ if err := createUserTable(db); err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+
|
|
|
+ return &UserService{db: db}, nil
|
|
|
+}
|
|
|
+
|
|
|
+// 创建用户表
|
|
|
+func createUserTable(db *sql.DB) error {
|
|
|
+ query := `
|
|
|
+ CREATE TABLE IF NOT EXISTS users (
|
|
|
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
|
+ name TEXT NOT NULL,
|
|
|
+ email TEXT UNIQUE NOT NULL,
|
|
|
+ age INTEGER,
|
|
|
+ address TEXT,
|
|
|
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
|
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
|
+ )`
|
|
|
+
|
|
|
+ _, err := db.Exec(query)
|
|
|
+ return err
|
|
|
+}
|
|
|
+
|
|
|
+// 创建用户
|
|
|
+func (us *UserService) CreateUser(user User) (int, error) {
|
|
|
+ query := `
|
|
|
+ INSERT INTO users (name, email, age, address)
|
|
|
+ VALUES (?, ?, ?, ?)
|
|
|
+ `
|
|
|
+
|
|
|
+ result, err := us.db.Exec(query, user.Name, user.Email, user.Age, user.Address)
|
|
|
+ if err != nil {
|
|
|
+ return 0, fmt.Errorf("创建用户失败: %v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ id, err := result.LastInsertId()
|
|
|
+ if err != nil {
|
|
|
+ return 0, fmt.Errorf("获取用户ID失败: %v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ return int(id), nil
|
|
|
+}
|
|
|
+
|
|
|
+// 根据ID获取用户
|
|
|
+func (us *UserService) GetUser(id int) (User, error) {
|
|
|
+ query := `
|
|
|
+ SELECT id, name, email, age, address, created_at, updated_at
|
|
|
+ FROM users
|
|
|
+ WHERE id = ?
|
|
|
+ `
|
|
|
+
|
|
|
+ var user User
|
|
|
+ err := us.db.QueryRow(query, id).Scan(
|
|
|
+ &user.ID, &user.Name, &user.Email, &user.Age,
|
|
|
+ &user.Address, &user.CreatedAt, &user.UpdatedAt)
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ if err == sql.ErrNoRows {
|
|
|
+ return User{}, fmt.Errorf("用户不存在")
|
|
|
+ }
|
|
|
+ return User{}, fmt.Errorf("获取用户失败: %v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ return user, nil
|
|
|
+}
|
|
|
+
|
|
|
+// 根据邮箱获取用户
|
|
|
+func (us *UserService) GetUserByEmail(email string) (User, error) {
|
|
|
+ query := `
|
|
|
+ SELECT id, name, email, age, address, created_at, updated_at
|
|
|
+ FROM users
|
|
|
+ WHERE email = ?
|
|
|
+ `
|
|
|
+
|
|
|
+ var user User
|
|
|
+ err := us.db.QueryRow(query, email).Scan(
|
|
|
+ &user.ID, &user.Name, &user.Email, &user.Age,
|
|
|
+ &user.Address, &user.CreatedAt, &user.UpdatedAt)
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ if err == sql.ErrNoRows {
|
|
|
+ return User{}, fmt.Errorf("用户不存在")
|
|
|
+ }
|
|
|
+ return User{}, fmt.Errorf("获取用户失败: %v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ return user, nil
|
|
|
+}
|
|
|
+
|
|
|
+// 更新用户
|
|
|
+func (us *UserService) UpdateUser(user User) error {
|
|
|
+ query := `
|
|
|
+ UPDATE users
|
|
|
+ SET name = ?, email = ?, age = ?, address = ?, updated_at = ?
|
|
|
+ WHERE id = ?
|
|
|
+ `
|
|
|
+
|
|
|
+ _, err := us.db.Exec(query, user.Name, user.Email, user.Age, user.Address, time.Now(), user.ID)
|
|
|
+ if err != nil {
|
|
|
+ return fmt.Errorf("更新用户失败: %v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+// 删除用户
|
|
|
+func (us *UserService) DeleteUser(id int) error {
|
|
|
+ query := `DELETE FROM users WHERE id = ?`
|
|
|
+
|
|
|
+ result, err := us.db.Exec(query, id)
|
|
|
+ if err != nil {
|
|
|
+ return fmt.Errorf("删除用户失败: %v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ rowsAffected, err := result.RowsAffected()
|
|
|
+ if err != nil {
|
|
|
+ return fmt.Errorf("获取影响行数失败: %v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ if rowsAffected == 0 {
|
|
|
+ return fmt.Errorf("用户不存在")
|
|
|
+ }
|
|
|
+
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+// 获取所有用户
|
|
|
+func (us *UserService) ListUsers() ([]User, error) {
|
|
|
+ query := `
|
|
|
+ SELECT id, name, email, age, address, created_at, updated_at
|
|
|
+ FROM users
|
|
|
+ ORDER BY id
|
|
|
+ `
|
|
|
+
|
|
|
+ rows, err := us.db.Query(query)
|
|
|
+ if err != nil {
|
|
|
+ return nil, fmt.Errorf("查询用户失败: %v", err)
|
|
|
+ }
|
|
|
+ defer rows.Close()
|
|
|
+
|
|
|
+ var users []User
|
|
|
+ for rows.Next() {
|
|
|
+ var user User
|
|
|
+ err := rows.Scan(
|
|
|
+ &user.ID, &user.Name, &user.Email, &user.Age,
|
|
|
+ &user.Address, &user.CreatedAt, &user.UpdatedAt)
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ return nil, fmt.Errorf("扫描用户数据失败: %v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ users = append(users, user)
|
|
|
+ }
|
|
|
+
|
|
|
+ if err = rows.Err(); err != nil {
|
|
|
+ return nil, fmt.Errorf("遍历用户数据失败: %v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ return users, nil
|
|
|
+}
|
|
|
+
|
|
|
+// 搜索用户
|
|
|
+func (us *UserService) SearchUsers(keyword string) ([]User, error) {
|
|
|
+ query := `
|
|
|
+ SELECT id, name, email, age, address, created_at, updated_at
|
|
|
+ FROM users
|
|
|
+ WHERE name LIKE ? OR email LIKE ? OR address LIKE ?
|
|
|
+ ORDER BY id
|
|
|
+ `
|
|
|
+
|
|
|
+ searchPattern := "%" + keyword + "%"
|
|
|
+
|
|
|
+ rows, err := us.db.Query(query, searchPattern, searchPattern, searchPattern)
|
|
|
+ if err != nil {
|
|
|
+ return nil, fmt.Errorf("搜索用户失败: %v", err)
|
|
|
+ }
|
|
|
+ defer rows.Close()
|
|
|
+
|
|
|
+ var users []User
|
|
|
+ for rows.Next() {
|
|
|
+ var user User
|
|
|
+ err := rows.Scan(
|
|
|
+ &user.ID, &user.Name, &user.Email, &user.Age,
|
|
|
+ &user.Address, &user.CreatedAt, &user.UpdatedAt)
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ return nil, fmt.Errorf("扫描用户数据失败: %v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ users = append(users, user)
|
|
|
+ }
|
|
|
+
|
|
|
+ if err = rows.Err(); err != nil {
|
|
|
+ return nil, fmt.Errorf("遍历用户数据失败: %v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ return users, nil
|
|
|
+}
|
|
|
+
|
|
|
+// 获取用户统计信息
|
|
|
+func (us *UserService) GetUserStats() (map[string]int, error) {
|
|
|
+ stats := make(map[string]int)
|
|
|
+
|
|
|
+ // 总用户数
|
|
|
+ var totalUsers int
|
|
|
+ err := us.db.QueryRow("SELECT COUNT(*) FROM users").Scan(&totalUsers)
|
|
|
+ if err != nil {
|
|
|
+ return nil, fmt.Errorf("获取总用户数失败: %v", err)
|
|
|
+ }
|
|
|
+ stats["total"] = totalUsers
|
|
|
+
|
|
|
+ // 按年龄段统计
|
|
|
+ ageGroups := []struct {
|
|
|
+ name string
|
|
|
+ query string
|
|
|
+ }{
|
|
|
+ {"young", "SELECT COUNT(*) FROM users WHERE age < 18"},
|
|
|
+ {"adult", "SELECT COUNT(*) FROM users WHERE age BETWEEN 18 AND 60"},
|
|
|
+ {"senior", "SELECT COUNT(*) FROM users WHERE age > 60"},
|
|
|
+ }
|
|
|
+
|
|
|
+ for _, group := range ageGroups {
|
|
|
+ var count int
|
|
|
+ err := us.db.QueryRow(group.query).Scan(&count)
|
|
|
+ if err != nil {
|
|
|
+ return nil, fmt.Errorf("获取%s用户数失败: %v", group.name, err)
|
|
|
+ }
|
|
|
+ stats[group.name] = count
|
|
|
+ }
|
|
|
+
|
|
|
+ return stats, nil
|
|
|
+}
|
|
|
+
|
|
|
+// 关闭数据库连接
|
|
|
+func (us *UserService) Close() error {
|
|
|
+ return us.db.Close()
|
|
|
+}
|
|
|
+
|
|
|
+// 显示菜单
|
|
|
+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("8. 退出")
|
|
|
+ fmt.Print("请选择操作: ")
|
|
|
+}
|
|
|
+
|
|
|
+// 读取用户输入
|
|
|
+func readInput(prompt string) string {
|
|
|
+ fmt.Print(prompt)
|
|
|
+ var input string
|
|
|
+ fmt.Scanln(&input)
|
|
|
+ return strings.TrimSpace(input)
|
|
|
+}
|
|
|
+
|
|
|
+// 读取整数输入
|
|
|
+func readIntInput(prompt string) int {
|
|
|
+ for {
|
|
|
+ input := readInput(prompt)
|
|
|
+ value, err := strconv.Atoi(input)
|
|
|
+ if err != nil {
|
|
|
+ fmt.Println("请输入有效的整数")
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ return value
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func main() {
|
|
|
+ // 创建用户服务
|
|
|
+ userService, err := NewUserService("./users.db")
|
|
|
+ if err != nil {
|
|
|
+ log.Fatalf("创建用户服务失败: %v", err)
|
|
|
+ }
|
|
|
+ defer userService.Close()
|
|
|
+
|
|
|
+ fmt.Println("欢迎使用用户管理系统")
|
|
|
+
|
|
|
+ for {
|
|
|
+ showMenu()
|
|
|
+ choice := readInput("")
|
|
|
+
|
|
|
+ switch choice {
|
|
|
+ case "1":
|
|
|
+ // 创建用户
|
|
|
+ name := readInput("姓名: ")
|
|
|
+ email := readInput("邮箱: ")
|
|
|
+ age := readIntInput("年龄: ")
|
|
|
+ address := readInput("地址: ")
|
|
|
+
|
|
|
+ user := User{
|
|
|
+ Name: name,
|
|
|
+ Email: email,
|
|
|
+ Age: age,
|
|
|
+ Address: address,
|
|
|
+ }
|
|
|
+
|
|
|
+ id, err := userService.CreateUser(user)
|
|
|
+ if err != nil {
|
|
|
+ fmt.Printf("创建用户失败: %v\n", err)
|
|
|
+ } else {
|
|
|
+ fmt.Printf("用户创建成功,ID: %d\n", id)
|
|
|
+ }
|
|
|
+
|
|
|
+ case "2":
|
|
|
+ // 查看用户
|
|
|
+ id := readIntInput("用户ID: ")
|
|
|
+ user, err := userService.GetUser(id)
|
|
|
+ if err != nil {
|
|
|
+ fmt.Printf("获取用户失败: %v\n", err)
|
|
|
+ } else {
|
|
|
+ fmt.Printf("ID: %d\n", user.ID)
|
|
|
+ fmt.Printf("姓名: %s\n", user.Name)
|
|
|
+ fmt.Printf("邮箱: %s\n", user.Email)
|
|
|
+ fmt.Printf("年龄: %d\n", user.Age)
|
|
|
+ fmt.Printf("地址: %s\n", user.Address)
|
|
|
+ fmt.Printf("创建时间: %s\n", user.CreatedAt.Format("2006-01-02 15:04:05"))
|
|
|
+ }
|
|
|
+
|
|
|
+ case "3":
|
|
|
+ // 更新用户
|
|
|
+ id := readIntInput("用户ID: ")
|
|
|
+
|
|
|
+ // 先获取现有用户信息
|
|
|
+ user, err := userService.GetUser(id)
|
|
|
+ if err != nil {
|
|
|
+ fmt.Printf("获取用户失败: %v\n", err)
|
|
|
+ continue
|
|
|
+ }
|
|
|
+
|
|
|
+ fmt.Println("当前用户信息:")
|
|
|
+ fmt.Printf("姓名: %s\n", user.Name)
|
|
|
+ fmt.Printf("邮箱: %s\n", user.Email)
|
|
|
+ fmt.Printf("年龄: %d\n", user.Age)
|
|
|
+ fmt.Printf("地址: %s\n", user.Address)
|
|
|
+
|
|
|
+ // 获取新信息
|
|
|
+ name := readInput("新姓名 (留空保持不变): ")
|
|
|
+ if name != "" {
|
|
|
+ user.Name = name
|
|
|
+ }
|
|
|
+
|
|
|
+ email := readInput("新邮箱 (留空保持不变): ")
|
|
|
+ if email != "" {
|
|
|
+ user.Email = email
|
|
|
+ }
|
|
|
+
|
|
|
+ ageInput := readInput("新年龄 (留空保持不变): ")
|
|
|
+ if ageInput != "" {
|
|
|
+ age, err := strconv.Atoi(ageInput)
|
|
|
+ if err == nil {
|
|
|
+ user.Age = age
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ address := readInput("新地址 (留空保持不变): ")
|
|
|
+ if address != "" {
|
|
|
+ user.Address = address
|
|
|
+ }
|
|
|
+
|
|
|
+ if err := userService.UpdateUser(user); err != nil {
|
|
|
+ fmt.Printf("更新用户失败: %v\n", err)
|
|
|
+ } else {
|
|
|
+ fmt.Println("用户更新成功")
|
|
|
+ }
|
|
|
+
|
|
|
+ case "4":
|
|
|
+ // 删除用户
|
|
|
+ id := readIntInput("用户ID: ")
|
|
|
+
|
|
|
+ // 确认删除
|
|
|
+ confirm := readInput(fmt.Sprintf("确定要删除ID为%d的用户吗? (y/n): ", id))
|
|
|
+ if strings.ToLower(confirm) == "y" {
|
|
|
+ if err := userService.DeleteUser(id); err != nil {
|
|
|
+ fmt.Printf("删除用户失败: %v\n", err)
|
|
|
+ } else {
|
|
|
+ fmt.Println("用户删除成功")
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ fmt.Println("取消删除")
|
|
|
+ }
|
|
|
+
|
|
|
+ case "5":
|
|
|
+ // 列出所有用户
|
|
|
+ users, err := userService.ListUsers()
|
|
|
+ if err != nil {
|
|
|
+ fmt.Printf("获取用户列表失败: %v\n", err)
|
|
|
+ continue
|
|
|
+ }
|
|
|
+
|
|
|
+ if len(users) == 0 {
|
|
|
+ fmt.Println("没有用户")
|
|
|
+ continue
|
|
|
+ }
|
|
|
+
|
|
|
+ fmt.Println("\n用户列表:")
|
|
|
+ fmt.Println("ID\t姓名\t邮箱\t年龄")
|
|
|
+ for _, user := range users {
|
|
|
+ fmt.Printf("%d\t%s\t%s\t%d\n", user.ID, user.Name, user.Email, user.Age)
|
|
|
+ }
|
|
|
+
|
|
|
+ case "6":
|
|
|
+ // 搜索用户
|
|
|
+ keyword := readInput("搜索关键词: ")
|
|
|
+ users, err := userService.SearchUsers(keyword)
|
|
|
+ if err != nil {
|
|
|
+ fmt.Printf("搜索用户失败: %v\n", err)
|
|
|
+ continue
|
|
|
+ }
|
|
|
+
|
|
|
+ if len(users) == 0 {
|
|
|
+ fmt.Println("没有找到匹配的用户")
|
|
|
+ continue
|
|
|
+ }
|
|
|
+
|
|
|
+ fmt.Printf("\n找到%d个匹配的用户:\n", len(users))
|
|
|
+ fmt.Println("ID\t姓名\t邮箱\t年龄")
|
|
|
+ for _, user := range users {
|
|
|
+ fmt.Printf("%d\t%s\t%s\t%d\n", user.ID, user.Name, user.Email, user.Age)
|
|
|
+ }
|
|
|
+
|
|
|
+ case "7":
|
|
|
+ // 用户统计
|
|
|
+ stats, err := userService.GetUserStats()
|
|
|
+ if err != nil {
|
|
|
+ fmt.Printf("获取用户统计失败: %v\n", err)
|
|
|
+ continue
|
|
|
+ }
|
|
|
+
|
|
|
+ fmt.Println("\n用户统计:")
|
|
|
+ fmt.Printf("总用户数: %d\n", stats["total"])
|
|
|
+ fmt.Printf("青少年用户 (<18岁): %d\n", stats["young"])
|
|
|
+ fmt.Printf("成年用户 (18-60岁): %d\n", stats["adult"])
|
|
|
+ fmt.Printf("老年用户 (>60岁): %d\n", stats["senior"])
|
|
|
+
|
|
|
+ case "8":
|
|
|
+ // 退出
|
|
|
+ fmt.Println("感谢使用用户管理系统,再见!")
|
|
|
+ return
|
|
|
+
|
|
|
+ default:
|
|
|
+ fmt.Println("无效的选择,请重新输入")
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
```
|
|
|
|
|
|
## 推荐资源
|