This commit is contained in:
2025-08-24 13:01:09 +08:00
parent 61e51ad014
commit f028913eb8
36 changed files with 10420 additions and 70 deletions

View File

@@ -1,38 +1,165 @@
# Web 服务器项目
一个简单的 HTTP Web 服务器,提供 RESTful API。
这是一个简单的 HTTP Web 服务器项目,演示了 Go 语言在网络编程、并发处理和 RESTful API 开发方面的应用
## 功能特性
- HTTP 服务器
- RESTful API 端点
## 项目特性
- HTTP 服务器基础功能
- RESTful API 设计
- JSON 数据处理
- 路由
- 路由
- 中间件支持
- 静态文件服务
- 并发请求处理
- 错误处理和日志记录
- 简单的用户管理系统
## API 端点
- `GET /` - 首页
- `GET /api/users` - 获取用户列表
- `POST /api/users` - 创建新用户
- `GET /api/users/{id}` - 获取特定用户
- `PUT /api/users/{id}` - 更新用户
- `DELETE /api/users/{id}` - 删除用户
## 项目结构
## 运行方法
```bash
cd 03-web-server
go run main.go
```
03-web-server/
├── README.md # 项目说明文档
├── main.go # 主程序入口
├── server/ # 服务器核心包
│ ├── server.go # HTTP 服务器
│ ├── router.go # 路由管理
│ ├── middleware.go # 中间件
│ └── handlers.go # 请求处理器
├── models/ # 数据模型
│ └── user.go # 用户模型
├── static/ # 静态文件
│ ├── index.html # 首页
│ ├── style.css # 样式文件
│ └── script.js # JavaScript 文件
├── data/ # 数据文件
│ └── users.json # 用户数据
└── server_test.go # 测试文件
```
服务器将在 http://localhost:8080 启动
## 运行方法
```bash
# 进入项目目录
cd 10-projects/03-web-server
# 运行程序
go run main.go
# 或者编译后运行
go build -o webserver main.go
./webserver
```
## API 接口
### 用户管理 API
- `GET /api/users` - 获取所有用户
- `GET /api/users/{id}` - 获取指定用户
- `POST /api/users` - 创建新用户
- `PUT /api/users/{id}` - 更新用户信息
- `DELETE /api/users/{id}` - 删除用户
### 其他接口
- `GET /` - 首页
- `GET /health` - 健康检查
- `GET /api/stats` - 服务器统计信息
- `GET /static/*` - 静态文件服务
## 使用示例
### 启动服务器
```bash
# 获取用户列表
$ go run main.go
🚀 服务器启动成功
📍 地址: http://localhost:8080
📊 健康检查: http://localhost:8080/health
📚 API文档: http://localhost:8080/api
```
### API 调用示例
```bash
# 获取所有用户
curl http://localhost:8080/api/users
# 创建新用户
curl -X POST http://localhost:8080/api/users \
-H "Content-Type: application/json" \
-d '{"name":"张三","email":"zhangsan@example.com"}'
```
-d '{"name":"张三","email":"zhangsan@example.com","age":25}'
# 获取指定用户
curl http://localhost:8080/api/users/1
# 更新用户信息
curl -X PUT http://localhost:8080/api/users/1 \
-H "Content-Type: application/json" \
-d '{"name":"张三","email":"zhangsan@gmail.com","age":26}'
# 删除用户
curl -X DELETE http://localhost:8080/api/users/1
# 健康检查
curl http://localhost:8080/health
# 服务器统计
curl http://localhost:8080/api/stats
```
### 响应示例
```json
// GET /api/users
{
"status": "success",
"data": [
{
"id": 1,
"name": "张三",
"email": "zhangsan@example.com",
"age": 25,
"created_at": "2024-01-01T10:00:00Z",
"updated_at": "2024-01-01T10:00:00Z"
}
],
"count": 1
}
// GET /health
{
"status": "healthy",
"timestamp": "2024-01-01T10:00:00Z",
"uptime": "1h30m45s",
"version": "1.0.0"
}
```
## 学习要点
这个项目综合运用了以下 Go 语言特性:
1. **HTTP 服务器**: 使用 `net/http` 包创建 Web 服务器
2. **路由管理**: 实现 RESTful 路由和参数解析
3. **JSON 处理**: 请求和响应的 JSON 序列化/反序列化
4. **中间件模式**: 日志记录、CORS、认证等中间件
5. **并发处理**: 利用 goroutine 处理并发请求
6. **错误处理**: HTTP 错误响应和日志记录
7. **文件操作**: 静态文件服务和数据持久化
8. **结构体和接口**: 数据模型和服务接口设计
9. **包管理**: 多包项目结构和依赖管理
10. **测试**: HTTP 服务器和 API 的测试
## 扩展建议
1. 添加用户认证和授权JWT
2. 实现数据库集成MySQL、PostgreSQL
3. 添加缓存支持Redis
4. 实现 WebSocket 支持
5. 添加 API 限流和熔断
6. 集成 Swagger API 文档
7. 添加配置文件支持
8. 实现优雅关闭
9. 添加监控和指标收集
10. 支持 HTTPS 和 HTTP/2

View File

@@ -0,0 +1,2 @@
# 这个文件用于保持 data 目录在 git 中被跟踪
# 实际的 users.json 文件会在程序运行时自动创建

View File

@@ -0,0 +1,5 @@
module webserver
go 1.19
require github.com/gorilla/mux v1.8.0

View File

@@ -0,0 +1,54 @@
/*
main.go - Web服务器主程序
这是一个简单的HTTP Web服务器演示了Go语言的网络编程应用
*/
package main
import (
"fmt"
"log"
"os"
"os/signal"
"syscall"
"webserver/server"
)
func main() {
// 创建服务器实例
srv := server.NewServer(":8080")
// 设置路由
srv.SetupRoutes()
// 启动信息
fmt.Println("🚀 Go Web服务器启动中...")
fmt.Println("📍 地址: http://localhost:8080")
fmt.Println("📊 健康检查: http://localhost:8080/health")
fmt.Println("📚 API文档: http://localhost:8080/api")
fmt.Println("按 Ctrl+C 停止服务器")
fmt.Println()
// 启动服务器在goroutine中
go func() {
if err := srv.Start(); err != nil {
log.Printf("❌ 服务器启动失败: %v", err)
os.Exit(1)
}
}()
// 等待中断信号
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
fmt.Println("\n🛑 正在关闭服务器...")
// 优雅关闭服务器
if err := srv.Shutdown(); err != nil {
log.Printf("❌ 服务器关闭失败: %v", err)
} else {
fmt.Println("✅ 服务器已安全关闭")
}
}

View File

@@ -0,0 +1,293 @@
/*
user.go - 用户数据模型
定义了用户的数据结构和相关操作
*/
package models
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"strings"
"time"
)
// User 用户结构体
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"`
}
// UserStorage 用户存储
type UserStorage struct {
users []User
nextID int
filePath string
}
var storage *UserStorage
// init 初始化用户存储
func init() {
storage = &UserStorage{
users: make([]User, 0),
nextID: 1,
filePath: "data/users.json",
}
// 创建数据目录
os.MkdirAll("data", 0755)
// 加载现有数据
storage.load()
}
// Validate 验证用户数据
func (u *User) Validate() error {
if strings.TrimSpace(u.Name) == "" {
return fmt.Errorf("用户名不能为空")
}
if len(u.Name) > 50 {
return fmt.Errorf("用户名长度不能超过50个字符")
}
if strings.TrimSpace(u.Email) == "" {
return fmt.Errorf("邮箱不能为空")
}
if !isValidEmail(u.Email) {
return fmt.Errorf("邮箱格式无效")
}
if u.Age < 0 || u.Age > 150 {
return fmt.Errorf("年龄必须在0-150之间")
}
return nil
}
// isValidEmail 简单的邮箱格式验证
func isValidEmail(email string) bool {
return strings.Contains(email, "@") && strings.Contains(email, ".")
}
// GetAllUsers 获取所有用户
func GetAllUsers() ([]User, error) {
return storage.users, nil
}
// GetUserByID 根据ID获取用户
func GetUserByID(id int) (*User, error) {
for _, user := range storage.users {
if user.ID == id {
return &user, nil
}
}
return nil, fmt.Errorf("用户不存在")
}
// CreateUser 创建新用户
func CreateUser(user User) (*User, error) {
// 检查邮箱是否已存在
for _, existingUser := range storage.users {
if existingUser.Email == user.Email {
return nil, fmt.Errorf("邮箱已存在")
}
}
// 设置用户信息
user.ID = storage.nextID
user.CreatedAt = time.Now()
user.UpdatedAt = time.Now()
// 添加到存储
storage.users = append(storage.users, user)
storage.nextID++
// 保存到文件
if err := storage.save(); err != nil {
return nil, err
}
return &user, nil
}
// UpdateUser 更新用户信息
func UpdateUser(user User) (*User, error) {
// 查找用户
for i, existingUser := range storage.users {
if existingUser.ID == user.ID {
// 检查邮箱是否被其他用户使用
for _, otherUser := range storage.users {
if otherUser.ID != user.ID && otherUser.Email == user.Email {
return nil, fmt.Errorf("邮箱已被其他用户使用")
}
}
// 保留创建时间,更新其他信息
user.CreatedAt = existingUser.CreatedAt
user.UpdatedAt = time.Now()
// 更新用户
storage.users[i] = user
// 保存到文件
if err := storage.save(); err != nil {
return nil, err
}
return &user, nil
}
}
return nil, fmt.Errorf("用户不存在")
}
// DeleteUser 删除用户
func DeleteUser(id int) error {
// 查找并删除用户
for i, user := range storage.users {
if user.ID == id {
// 从切片中删除用户
storage.users = append(storage.users[:i], storage.users[i+1:]...)
// 保存到文件
return storage.save()
}
}
return fmt.Errorf("用户不存在")
}
// load 从文件加载用户数据
func (s *UserStorage) load() error {
// 检查文件是否存在
if _, err := os.Stat(s.filePath); os.IsNotExist(err) {
// 文件不存在,创建示例数据
s.createSampleData()
return s.save()
}
// 读取文件
data, err := ioutil.ReadFile(s.filePath)
if err != nil {
return err
}
// 解析JSON
if err := json.Unmarshal(data, &s.users); err != nil {
return err
}
// 更新下一个ID
maxID := 0
for _, user := range s.users {
if user.ID > maxID {
maxID = user.ID
}
}
s.nextID = maxID + 1
return nil
}
// save 保存用户数据到文件
func (s *UserStorage) save() error {
data, err := json.MarshalIndent(s.users, "", " ")
if err != nil {
return err
}
return ioutil.WriteFile(s.filePath, data, 0644)
}
// createSampleData 创建示例数据
func (s *UserStorage) createSampleData() {
now := time.Now()
sampleUsers := []User{
{
ID: 1,
Name: "张三",
Email: "zhangsan@example.com",
Age: 25,
CreatedAt: now,
UpdatedAt: now,
},
{
ID: 2,
Name: "李四",
Email: "lisi@example.com",
Age: 30,
CreatedAt: now,
UpdatedAt: now,
},
{
ID: 3,
Name: "王五",
Email: "wangwu@example.com",
Age: 28,
CreatedAt: now,
UpdatedAt: now,
},
}
s.users = sampleUsers
s.nextID = 4
}
// GetUserCount 获取用户总数
func GetUserCount() int {
return len(storage.users)
}
// SearchUsers 搜索用户
func SearchUsers(keyword string) []User {
var results []User
keyword = strings.ToLower(keyword)
for _, user := range storage.users {
if strings.Contains(strings.ToLower(user.Name), keyword) ||
strings.Contains(strings.ToLower(user.Email), keyword) {
results = append(results, user)
}
}
return results
}
// GetUsersByAge 根据年龄范围获取用户
func GetUsersByAge(minAge, maxAge int) []User {
var results []User
for _, user := range storage.users {
if user.Age >= minAge && user.Age <= maxAge {
results = append(results, user)
}
}
return results
}
// GetRecentUsers 获取最近创建的用户
func GetRecentUsers(limit int) []User {
if limit <= 0 || limit > len(storage.users) {
limit = len(storage.users)
}
// 简单实现:返回最后创建的用户
// 实际应用中应该按创建时间排序
start := len(storage.users) - limit
if start < 0 {
start = 0
}
return storage.users[start:]
}

View File

@@ -0,0 +1,369 @@
/*
handlers.go - 请求处理器
实现了各种HTTP请求的处理逻辑
*/
package server
import (
"encoding/json"
"net/http"
"runtime"
"strconv"
"time"
"webserver/models"
"github.com/gorilla/mux"
)
// Response 通用响应结构
type Response struct {
Status string `json:"status"`
Message string `json:"message,omitempty"`
Data interface{} `json:"data,omitempty"`
Error string `json:"error,omitempty"`
Count int `json:"count,omitempty"`
}
// HealthResponse 健康检查响应
type HealthResponse struct {
Status string `json:"status"`
Timestamp string `json:"timestamp"`
Uptime string `json:"uptime"`
Version string `json:"version"`
}
// StatsResponse 统计信息响应
type StatsResponse struct {
Status string `json:"status"`
Timestamp string `json:"timestamp"`
Uptime string `json:"uptime"`
Version string `json:"version"`
GoVersion string `json:"go_version"`
NumCPU int `json:"num_cpu"`
NumGoroutine int `json:"num_goroutine"`
MemStats runtime.MemStats `json:"mem_stats"`
}
var (
startTime = time.Now()
version = "1.0.0"
)
// HomeHandler 首页处理器
func HomeHandler(w http.ResponseWriter, r *http.Request) {
html := `
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Go Web服务器</title>
<style>
body { font-family: Arial, sans-serif; margin: 40px; background-color: #f5f5f5; }
.container { max-width: 800px; margin: 0 auto; background: white; padding: 30px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
h1 { color: #333; text-align: center; }
.api-list { background: #f8f9fa; padding: 20px; border-radius: 5px; margin: 20px 0; }
.api-item { margin: 10px 0; padding: 10px; background: white; border-left: 4px solid #007bff; }
.method { font-weight: bold; color: #007bff; }
.endpoint { font-family: monospace; background: #e9ecef; padding: 2px 6px; border-radius: 3px; }
.description { color: #666; margin-top: 5px; }
</style>
</head>
<body>
<div class="container">
<h1>🚀 Go Web服务器</h1>
<p>欢迎使用Go语言编写的简单Web服务器这个项目演示了HTTP服务器、RESTful API、JSON处理等功能。</p>
<h2>📚 API接口</h2>
<div class="api-list">
<div class="api-item">
<span class="method">GET</span> <span class="endpoint">/health</span>
<div class="description">健康检查</div>
</div>
<div class="api-item">
<span class="method">GET</span> <span class="endpoint">/api/stats</span>
<div class="description">服务器统计信息</div>
</div>
<div class="api-item">
<span class="method">GET</span> <span class="endpoint">/api/users</span>
<div class="description">获取所有用户</div>
</div>
<div class="api-item">
<span class="method">POST</span> <span class="endpoint">/api/users</span>
<div class="description">创建新用户</div>
</div>
<div class="api-item">
<span class="method">GET</span> <span class="endpoint">/api/users/{id}</span>
<div class="description">获取指定用户</div>
</div>
<div class="api-item">
<span class="method">PUT</span> <span class="endpoint">/api/users/{id}</span>
<div class="description">更新用户信息</div>
</div>
<div class="api-item">
<span class="method">DELETE</span> <span class="endpoint">/api/users/{id}</span>
<div class="description">删除用户</div>
</div>
</div>
<h2>🛠️ 使用示例</h2>
<pre style="background: #f8f9fa; padding: 15px; border-radius: 5px; overflow-x: auto;">
# 获取所有用户
curl http://localhost:8080/api/users
# 创建新用户
curl -X POST http://localhost:8080/api/users \
-H "Content-Type: application/json" \
-d '{"name":"张三","email":"zhangsan@example.com","age":25}'
# 健康检查
curl http://localhost:8080/health
</pre>
</div>
</body>
</html>`
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.WriteHeader(http.StatusOK)
w.Write([]byte(html))
}
// HealthHandler 健康检查处理器
func HealthHandler(w http.ResponseWriter, r *http.Request) {
uptime := time.Since(startTime)
response := HealthResponse{
Status: "healthy",
Timestamp: time.Now().Format(time.RFC3339),
Uptime: uptime.String(),
Version: version,
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(response)
}
// StatsHandler 统计信息处理器
func StatsHandler(w http.ResponseWriter, r *http.Request) {
uptime := time.Since(startTime)
var memStats runtime.MemStats
runtime.ReadMemStats(&memStats)
response := StatsResponse{
Status: "success",
Timestamp: time.Now().Format(time.RFC3339),
Uptime: uptime.String(),
Version: version,
GoVersion: runtime.Version(),
NumCPU: runtime.NumCPU(),
NumGoroutine: runtime.NumGoroutine(),
MemStats: memStats,
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(response)
}
// GetUsersHandler 获取所有用户
func GetUsersHandler(w http.ResponseWriter, r *http.Request) {
users, err := models.GetAllUsers()
if err != nil {
sendErrorResponse(w, "获取用户列表失败", http.StatusInternalServerError)
return
}
response := Response{
Status: "success",
Data: users,
Count: len(users),
}
sendJSONResponse(w, response, http.StatusOK)
}
// GetUserHandler 获取指定用户
func GetUserHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id, err := strconv.Atoi(vars["id"])
if err != nil {
sendErrorResponse(w, "无效的用户ID", http.StatusBadRequest)
return
}
user, err := models.GetUserByID(id)
if err != nil {
sendErrorResponse(w, "用户不存在", http.StatusNotFound)
return
}
response := Response{
Status: "success",
Data: user,
}
sendJSONResponse(w, response, http.StatusOK)
}
// CreateUserHandler 创建新用户
func CreateUserHandler(w http.ResponseWriter, r *http.Request) {
var user models.User
if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
sendErrorResponse(w, "无效的JSON数据", http.StatusBadRequest)
return
}
// 验证用户数据
if err := user.Validate(); err != nil {
sendErrorResponse(w, err.Error(), http.StatusBadRequest)
return
}
// 创建用户
createdUser, err := models.CreateUser(user)
if err != nil {
sendErrorResponse(w, "创建用户失败", http.StatusInternalServerError)
return
}
response := Response{
Status: "success",
Message: "用户创建成功",
Data: createdUser,
}
sendJSONResponse(w, response, http.StatusCreated)
}
// UpdateUserHandler 更新用户信息
func UpdateUserHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id, err := strconv.Atoi(vars["id"])
if err != nil {
sendErrorResponse(w, "无效的用户ID", http.StatusBadRequest)
return
}
var user models.User
if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
sendErrorResponse(w, "无效的JSON数据", http.StatusBadRequest)
return
}
// 设置用户ID
user.ID = id
// 验证用户数据
if err := user.Validate(); err != nil {
sendErrorResponse(w, err.Error(), http.StatusBadRequest)
return
}
// 更新用户
updatedUser, err := models.UpdateUser(user)
if err != nil {
sendErrorResponse(w, "更新用户失败", http.StatusInternalServerError)
return
}
response := Response{
Status: "success",
Message: "用户更新成功",
Data: updatedUser,
}
sendJSONResponse(w, response, http.StatusOK)
}
// DeleteUserHandler 删除用户
func DeleteUserHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id, err := strconv.Atoi(vars["id"])
if err != nil {
sendErrorResponse(w, "无效的用户ID", http.StatusBadRequest)
return
}
if err := models.DeleteUser(id); err != nil {
sendErrorResponse(w, "删除用户失败", http.StatusInternalServerError)
return
}
response := Response{
Status: "success",
Message: "用户删除成功",
}
sendJSONResponse(w, response, http.StatusOK)
}
// APIDocHandler API文档处理器
func APIDocHandler(w http.ResponseWriter, r *http.Request) {
doc := map[string]interface{}{
"title": "Go Web服务器 API",
"version": version,
"description": "一个简单的RESTful API服务器",
"endpoints": map[string]interface{}{
"health": map[string]string{
"method": "GET",
"path": "/health",
"description": "健康检查",
},
"stats": map[string]string{
"method": "GET",
"path": "/api/stats",
"description": "服务器统计信息",
},
"users": map[string]interface{}{
"list": map[string]string{
"method": "GET",
"path": "/api/users",
"description": "获取所有用户",
},
"get": map[string]string{
"method": "GET",
"path": "/api/users/{id}",
"description": "获取指定用户",
},
"create": map[string]string{
"method": "POST",
"path": "/api/users",
"description": "创建新用户",
},
"update": map[string]string{
"method": "PUT",
"path": "/api/users/{id}",
"description": "更新用户信息",
},
"delete": map[string]string{
"method": "DELETE",
"path": "/api/users/{id}",
"description": "删除用户",
},
},
},
}
sendJSONResponse(w, doc, http.StatusOK)
}
// sendJSONResponse 发送JSON响应
func sendJSONResponse(w http.ResponseWriter, data interface{}, statusCode int) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(statusCode)
if err := json.NewEncoder(w).Encode(data); err != nil {
http.Error(w, "JSON编码失败", http.StatusInternalServerError)
}
}
// sendErrorResponse 发送错误响应
func sendErrorResponse(w http.ResponseWriter, message string, statusCode int) {
response := Response{
Status: "error",
Error: message,
}
sendJSONResponse(w, response, statusCode)
}

View File

@@ -0,0 +1,147 @@
/*
middleware.go - 中间件
实现了各种HTTP中间件功能
*/
package server
import (
"log"
"net/http"
"runtime/debug"
"time"
)
// LoggingMiddleware 日志记录中间件
func LoggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
// 创建响应记录器来捕获状态码
recorder := &responseRecorder{
ResponseWriter: w,
statusCode: http.StatusOK,
}
// 调用下一个处理器
next.ServeHTTP(recorder, r)
// 记录请求日志
duration := time.Since(start)
log.Printf("[%s] %s %s %d %v",
r.Method,
r.RequestURI,
r.RemoteAddr,
recorder.statusCode,
duration,
)
})
}
// responseRecorder 响应记录器
type responseRecorder struct {
http.ResponseWriter
statusCode int
}
// WriteHeader 记录状态码
func (rr *responseRecorder) WriteHeader(code int) {
rr.statusCode = code
rr.ResponseWriter.WriteHeader(code)
}
// CORSMiddleware CORS中间件
func CORSMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 设置CORS头
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
// 处理预检请求
if r.Method == "OPTIONS" {
w.WriteHeader(http.StatusOK)
return
}
// 调用下一个处理器
next.ServeHTTP(w, r)
})
}
// RecoveryMiddleware 恢复中间件处理panic
func RecoveryMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
// 记录panic信息
log.Printf("❌ Panic recovered: %v\n%s", err, debug.Stack())
// 返回500错误
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}
}()
// 调用下一个处理器
next.ServeHTTP(w, r)
})
}
// AuthMiddleware 认证中间件(示例)
func AuthMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 检查Authorization头
token := r.Header.Get("Authorization")
if token == "" {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
// 这里可以添加实际的token验证逻辑
// 为了演示我们简单检查token是否为"Bearer valid-token"
if token != "Bearer valid-token" {
http.Error(w, "Invalid token", http.StatusUnauthorized)
return
}
// 调用下一个处理器
next.ServeHTTP(w, r)
})
}
// RateLimitMiddleware 限流中间件(简化版)
func RateLimitMiddleware(next http.Handler) http.Handler {
// 这里可以实现基于IP的限流逻辑
// 为了简化,我们只是一个示例框架
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 实际实现中,这里会检查请求频率
// 如果超过限制返回429状态码
// 调用下一个处理器
next.ServeHTTP(w, r)
})
}
// ContentTypeMiddleware 内容类型中间件
func ContentTypeMiddleware(contentType string) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", contentType)
next.ServeHTTP(w, r)
})
}
}
// SecurityMiddleware 安全头中间件
func SecurityMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 设置安全相关的HTTP头
w.Header().Set("X-Content-Type-Options", "nosniff")
w.Header().Set("X-Frame-Options", "DENY")
w.Header().Set("X-XSS-Protection", "1; mode=block")
w.Header().Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains")
// 调用下一个处理器
next.ServeHTTP(w, r)
})
}

View File

@@ -0,0 +1,59 @@
/*
router.go - 路由管理
实现了HTTP路由和中间件管理功能
*/
package server
import (
"net/http"
"github.com/gorilla/mux"
)
// Router 路由器结构体
type Router struct {
*mux.Router
}
// NewRouter 创建新的路由器
func NewRouter() *Router {
return &Router{
Router: mux.NewRouter(),
}
}
// Use 添加中间件
func (r *Router) Use(middleware func(http.Handler) http.Handler) {
r.Router.Use(middleware)
}
// Methods 设置HTTP方法链式调用
func (r *Router) Methods(methods ...string) *mux.Route {
return r.Router.Methods(methods...)
}
// PathPrefix 路径前缀
func (r *Router) PathPrefix(tpl string) *mux.Router {
return r.Router.PathPrefix(tpl)
}
// Subrouter 创建子路由
func (r *Router) Subrouter() *mux.Router {
return r.Router.NewRoute().Subrouter()
}
// HandleFunc 处理函数路由
func (r *Router) HandleFunc(path string, f func(http.ResponseWriter, *http.Request)) *mux.Route {
return r.Router.HandleFunc(path, f)
}
// Handle 处理器路由
func (r *Router) Handle(path string, handler http.Handler) *mux.Route {
return r.Router.Handle(path, handler)
}
// ServeHTTP 实现http.Handler接口
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
r.Router.ServeHTTP(w, req)
}

View File

@@ -0,0 +1,94 @@
/*
server.go - HTTP服务器核心
实现了HTTP服务器的基本功能和生命周期管理
*/
package server
import (
"context"
"fmt"
"net/http"
"time"
)
// Server HTTP服务器结构体
type Server struct {
httpServer *http.Server
router *Router
addr string
}
// NewServer 创建新的服务器实例
func NewServer(addr string) *Server {
router := NewRouter()
return &Server{
httpServer: &http.Server{
Addr: addr,
Handler: router,
ReadTimeout: 15 * time.Second,
WriteTimeout: 15 * time.Second,
IdleTimeout: 60 * time.Second,
},
router: router,
addr: addr,
}
}
// SetupRoutes 设置路由
func (s *Server) SetupRoutes() {
// 添加中间件
s.router.Use(LoggingMiddleware)
s.router.Use(CORSMiddleware)
s.router.Use(RecoveryMiddleware)
// 静态文件服务
s.router.HandleFunc("/", HomeHandler).Methods("GET")
s.router.PathPrefix("/static/").Handler(
http.StripPrefix("/static/", http.FileServer(http.Dir("./static/"))),
)
// 健康检查
s.router.HandleFunc("/health", HealthHandler).Methods("GET")
// API路由
apiRouter := s.router.PathPrefix("/api").Subrouter()
// 用户管理API
apiRouter.HandleFunc("/users", GetUsersHandler).Methods("GET")
apiRouter.HandleFunc("/users", CreateUserHandler).Methods("POST")
apiRouter.HandleFunc("/users/{id:[0-9]+}", GetUserHandler).Methods("GET")
apiRouter.HandleFunc("/users/{id:[0-9]+}", UpdateUserHandler).Methods("PUT")
apiRouter.HandleFunc("/users/{id:[0-9]+}", DeleteUserHandler).Methods("DELETE")
// 服务器统计
apiRouter.HandleFunc("/stats", StatsHandler).Methods("GET")
// API文档
apiRouter.HandleFunc("", APIDocHandler).Methods("GET")
}
// Start 启动服务器
func (s *Server) Start() error {
fmt.Printf("✅ 服务器启动成功,监听地址: %s\n", s.addr)
return s.httpServer.ListenAndServe()
}
// Shutdown 优雅关闭服务器
func (s *Server) Shutdown() error {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
return s.httpServer.Shutdown(ctx)
}
// GetRouter 获取路由器
func (s *Server) GetRouter() *Router {
return s.router
}
// GetHTTPServer 获取HTTP服务器
func (s *Server) GetHTTPServer() *http.Server {
return s.httpServer
}

View File

@@ -0,0 +1,358 @@
/*
server_test.go - Web服务器测试文件
测试HTTP服务器和API的各种功能
*/
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"testing"
"webserver/models"
"webserver/server"
)
// TestHealthHandler 测试健康检查处理器
func TestHealthHandler(t *testing.T) {
req, err := http.NewRequest("GET", "/health", nil)
if err != nil {
t.Fatal(err)
}
rr := httptest.NewRecorder()
handler := http.HandlerFunc(server.HealthHandler)
handler.ServeHTTP(rr, req)
// 检查状态码
if status := rr.Code; status != http.StatusOK {
t.Errorf("处理器返回错误状态码: got %v want %v", status, http.StatusOK)
}
// 检查响应内容类型
expected := "application/json"
if ct := rr.Header().Get("Content-Type"); ct != expected {
t.Errorf("处理器返回错误内容类型: got %v want %v", ct, expected)
}
// 检查响应体
var response server.HealthResponse
if err := json.Unmarshal(rr.Body.Bytes(), &response); err != nil {
t.Errorf("无法解析响应JSON: %v", err)
}
if response.Status != "healthy" {
t.Errorf("期望状态为 'healthy', 实际为 '%s'", response.Status)
}
}
// TestGetUsersHandler 测试获取用户列表处理器
func TestGetUsersHandler(t *testing.T) {
req, err := http.NewRequest("GET", "/api/users", nil)
if err != nil {
t.Fatal(err)
}
rr := httptest.NewRecorder()
handler := http.HandlerFunc(server.GetUsersHandler)
handler.ServeHTTP(rr, req)
// 检查状态码
if status := rr.Code; status != http.StatusOK {
t.Errorf("处理器返回错误状态码: got %v want %v", status, http.StatusOK)
}
// 检查响应内容类型
expected := "application/json"
if ct := rr.Header().Get("Content-Type"); ct != expected {
t.Errorf("处理器返回错误内容类型: got %v want %v", ct, expected)
}
// 检查响应体
var response server.Response
if err := json.Unmarshal(rr.Body.Bytes(), &response); err != nil {
t.Errorf("无法解析响应JSON: %v", err)
}
if response.Status != "success" {
t.Errorf("期望状态为 'success', 实际为 '%s'", response.Status)
}
}
// TestCreateUserHandler 测试创建用户处理器
func TestCreateUserHandler(t *testing.T) {
user := models.User{
Name: "测试用户",
Email: "test@example.com",
Age: 25,
}
jsonData, err := json.Marshal(user)
if err != nil {
t.Fatal(err)
}
req, err := http.NewRequest("POST", "/api/users", bytes.NewBuffer(jsonData))
if err != nil {
t.Fatal(err)
}
req.Header.Set("Content-Type", "application/json")
rr := httptest.NewRecorder()
handler := http.HandlerFunc(server.CreateUserHandler)
handler.ServeHTTP(rr, req)
// 检查状态码
if status := rr.Code; status != http.StatusCreated {
t.Errorf("处理器返回错误状态码: got %v want %v", status, http.StatusCreated)
}
// 检查响应体
var response server.Response
if err := json.Unmarshal(rr.Body.Bytes(), &response); err != nil {
t.Errorf("无法解析响应JSON: %v", err)
}
if response.Status != "success" {
t.Errorf("期望状态为 'success', 实际为 '%s'", response.Status)
}
}
// TestCreateUserHandlerInvalidData 测试创建用户处理器(无效数据)
func TestCreateUserHandlerInvalidData(t *testing.T) {
// 测试空用户名
user := models.User{
Name: "",
Email: "test@example.com",
Age: 25,
}
jsonData, err := json.Marshal(user)
if err != nil {
t.Fatal(err)
}
req, err := http.NewRequest("POST", "/api/users", bytes.NewBuffer(jsonData))
if err != nil {
t.Fatal(err)
}
req.Header.Set("Content-Type", "application/json")
rr := httptest.NewRecorder()
handler := http.HandlerFunc(server.CreateUserHandler)
handler.ServeHTTP(rr, req)
// 检查状态码
if status := rr.Code; status != http.StatusBadRequest {
t.Errorf("处理器返回错误状态码: got %v want %v", status, http.StatusBadRequest)
}
// 检查响应体
var response server.Response
if err := json.Unmarshal(rr.Body.Bytes(), &response); err != nil {
t.Errorf("无法解析响应JSON: %v", err)
}
if response.Status != "error" {
t.Errorf("期望状态为 'error', 实际为 '%s'", response.Status)
}
}
// TestCreateUserHandlerInvalidJSON 测试创建用户处理器无效JSON
func TestCreateUserHandlerInvalidJSON(t *testing.T) {
invalidJSON := []byte(`{"name": "测试用户", "email": "test@example.com", "age":}`)
req, err := http.NewRequest("POST", "/api/users", bytes.NewBuffer(invalidJSON))
if err != nil {
t.Fatal(err)
}
req.Header.Set("Content-Type", "application/json")
rr := httptest.NewRecorder()
handler := http.HandlerFunc(server.CreateUserHandler)
handler.ServeHTTP(rr, req)
// 检查状态码
if status := rr.Code; status != http.StatusBadRequest {
t.Errorf("处理器返回错误状态码: got %v want %v", status, http.StatusBadRequest)
}
}
// TestStatsHandler 测试统计信息处理器
func TestStatsHandler(t *testing.T) {
req, err := http.NewRequest("GET", "/api/stats", nil)
if err != nil {
t.Fatal(err)
}
rr := httptest.NewRecorder()
handler := http.HandlerFunc(server.StatsHandler)
handler.ServeHTTP(rr, req)
// 检查状态码
if status := rr.Code; status != http.StatusOK {
t.Errorf("处理器返回错误状态码: got %v want %v", status, http.StatusOK)
}
// 检查响应体
var response server.StatsResponse
if err := json.Unmarshal(rr.Body.Bytes(), &response); err != nil {
t.Errorf("无法解析响应JSON: %v", err)
}
if response.Status != "success" {
t.Errorf("期望状态为 'success', 实际为 '%s'", response.Status)
}
if response.NumCPU <= 0 {
t.Error("CPU数量应该大于0")
}
if response.NumGoroutine <= 0 {
t.Error("Goroutine数量应该大于0")
}
}
// TestAPIDocHandler 测试API文档处理器
func TestAPIDocHandler(t *testing.T) {
req, err := http.NewRequest("GET", "/api", nil)
if err != nil {
t.Fatal(err)
}
rr := httptest.NewRecorder()
handler := http.HandlerFunc(server.APIDocHandler)
handler.ServeHTTP(rr, req)
// 检查状态码
if status := rr.Code; status != http.StatusOK {
t.Errorf("处理器返回错误状态码: got %v want %v", status, http.StatusOK)
}
// 检查响应内容类型
expected := "application/json"
if ct := rr.Header().Get("Content-Type"); ct != expected {
t.Errorf("处理器返回错误内容类型: got %v want %v", ct, expected)
}
// 检查响应体包含API文档信息
var doc map[string]interface{}
if err := json.Unmarshal(rr.Body.Bytes(), &doc); err != nil {
t.Errorf("无法解析响应JSON: %v", err)
}
if _, exists := doc["title"]; !exists {
t.Error("API文档应该包含title字段")
}
if _, exists := doc["endpoints"]; !exists {
t.Error("API文档应该包含endpoints字段")
}
}
// TestServer 测试完整的服务器
func TestServer(t *testing.T) {
// 创建服务器
srv := server.NewServer(":0") // 使用随机端口
srv.SetupRoutes()
// 创建测试服务器
testServer := httptest.NewServer(srv.GetRouter())
defer testServer.Close()
// 测试健康检查
resp, err := http.Get(testServer.URL + "/health")
if err != nil {
t.Fatalf("健康检查请求失败: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Errorf("健康检查返回错误状态码: got %v want %v", resp.StatusCode, http.StatusOK)
}
// 测试获取用户列表
resp, err = http.Get(testServer.URL + "/api/users")
if err != nil {
t.Fatalf("获取用户列表请求失败: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Errorf("获取用户列表返回错误状态码: got %v want %v", resp.StatusCode, http.StatusOK)
}
}
// TestMiddleware 测试中间件
func TestMiddleware(t *testing.T) {
// 测试CORS中间件
req, err := http.NewRequest("OPTIONS", "/api/users", nil)
if err != nil {
t.Fatal(err)
}
rr := httptest.NewRecorder()
// 创建带中间件的处理器
handler := server.CORSMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}))
handler.ServeHTTP(rr, req)
// 检查CORS头
if origin := rr.Header().Get("Access-Control-Allow-Origin"); origin != "*" {
t.Errorf("期望CORS Origin为 '*', 实际为 '%s'", origin)
}
if methods := rr.Header().Get("Access-Control-Allow-Methods"); methods == "" {
t.Error("应该设置Access-Control-Allow-Methods头")
}
}
// BenchmarkHealthHandler 健康检查处理器基准测试
func BenchmarkHealthHandler(b *testing.B) {
req, _ := http.NewRequest("GET", "/health", nil)
b.ResetTimer()
for i := 0; i < b.N; i++ {
rr := httptest.NewRecorder()
handler := http.HandlerFunc(server.HealthHandler)
handler.ServeHTTP(rr, req)
}
}
// BenchmarkGetUsersHandler 获取用户列表处理器基准测试
func BenchmarkGetUsersHandler(b *testing.B) {
req, _ := http.NewRequest("GET", "/api/users", nil)
b.ResetTimer()
for i := 0; i < b.N; i++ {
rr := httptest.NewRecorder()
handler := http.HandlerFunc(server.GetUsersHandler)
handler.ServeHTTP(rr, req)
}
}
// ExampleHealthHandler 健康检查处理器示例
func ExampleHealthHandler() {
req, _ := http.NewRequest("GET", "/health", nil)
rr := httptest.NewRecorder()
handler := http.HandlerFunc(server.HealthHandler)
handler.ServeHTTP(rr, req)
fmt.Printf("Status: %d", rr.Code)
// Output: Status: 200
}

View File

@@ -0,0 +1,101 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Go Web服务器</title>
<link rel="stylesheet" href="/static/style.css">
</head>
<body>
<div class="container">
<header>
<h1>🚀 Go Web服务器</h1>
<p>一个使用Go语言编写的简单HTTP服务器示例</p>
</header>
<main>
<section class="api-section">
<h2>📚 API接口测试</h2>
<div class="api-group">
<h3>用户管理</h3>
<div class="api-item">
<button onclick="getUsers()">获取所有用户</button>
<span class="method get">GET</span>
<span class="endpoint">/api/users</span>
</div>
<div class="api-item">
<button onclick="createUser()">创建用户</button>
<span class="method post">POST</span>
<span class="endpoint">/api/users</span>
</div>
<div class="api-item">
<input type="number" id="userId" placeholder="用户ID" min="1">
<button onclick="getUser()">获取用户</button>
<span class="method get">GET</span>
<span class="endpoint">/api/users/{id}</span>
</div>
<div class="api-item">
<button onclick="deleteUser()">删除用户</button>
<span class="method delete">DELETE</span>
<span class="endpoint">/api/users/{id}</span>
</div>
</div>
<div class="api-group">
<h3>系统信息</h3>
<div class="api-item">
<button onclick="getHealth()">健康检查</button>
<span class="method get">GET</span>
<span class="endpoint">/health</span>
</div>
<div class="api-item">
<button onclick="getStats()">服务器统计</button>
<span class="method get">GET</span>
<span class="endpoint">/api/stats</span>
</div>
</div>
</section>
<section class="form-section">
<h2>📝 创建用户表单</h2>
<form id="userForm">
<div class="form-group">
<label for="name">姓名:</label>
<input type="text" id="name" name="name" required>
</div>
<div class="form-group">
<label for="email">邮箱:</label>
<input type="email" id="email" name="email" required>
</div>
<div class="form-group">
<label for="age">年龄:</label>
<input type="number" id="age" name="age" min="0" max="150" required>
</div>
<button type="submit">创建用户</button>
</form>
</section>
<section class="response-section">
<h2>📄 响应结果</h2>
<pre id="response"></pre>
</section>
</main>
<footer>
<p>&copy; 2024 Go Web服务器示例项目</p>
</footer>
</div>
<script src="/static/script.js"></script>
</body>
</html>

View File

@@ -0,0 +1,241 @@
// script.js - JavaScript 文件
// API 基础URL
const API_BASE = '/api';
// 响应显示元素
const responseElement = document.getElementById('response');
// 显示响应结果
function showResponse(data, isError = false) {
responseElement.textContent = JSON.stringify(data, null, 2);
responseElement.className = isError ? 'error' : 'success';
}
// 显示加载状态
function showLoading() {
responseElement.textContent = '加载中...';
responseElement.className = 'loading';
}
// API 请求封装
async function apiRequest(url, options = {}) {
try {
showLoading();
const response = await fetch(url, {
headers: {
'Content-Type': 'application/json',
...options.headers
},
...options
});
const data = await response.json();
if (!response.ok) {
showResponse(data, true);
return null;
}
showResponse(data);
return data;
} catch (error) {
showResponse({
error: '网络请求失败',
message: error.message
}, true);
return null;
}
}
// 获取所有用户
async function getUsers() {
await apiRequest(`${API_BASE}/users`);
}
// 获取指定用户
async function getUser() {
const userId = document.getElementById('userId').value;
if (!userId) {
showResponse({
error: '请输入用户ID'
}, true);
return;
}
await apiRequest(`${API_BASE}/users/${userId}`);
}
// 创建用户(使用按钮)
async function createUser() {
const userData = {
name: '测试用户',
email: `test${Date.now()}@example.com`,
age: Math.floor(Math.random() * 50) + 18
};
await apiRequest(`${API_BASE}/users`, {
method: 'POST',
body: JSON.stringify(userData)
});
}
// 删除用户
async function deleteUser() {
const userId = document.getElementById('userId').value;
if (!userId) {
showResponse({
error: '请输入用户ID'
}, true);
return;
}
if (!confirm(`确定要删除用户 ${userId} 吗?`)) {
return;
}
await apiRequest(`${API_BASE}/users/${userId}`, {
method: 'DELETE'
});
}
// 健康检查
async function getHealth() {
await apiRequest('/health');
}
// 获取服务器统计
async function getStats() {
await apiRequest(`${API_BASE}/stats`);
}
// 表单提交处理
document.getElementById('userForm').addEventListener('submit', async function(e) {
e.preventDefault();
const formData = new FormData(this);
const userData = {
name: formData.get('name'),
email: formData.get('email'),
age: parseInt(formData.get('age'))
};
// 验证数据
if (!userData.name || !userData.email || !userData.age) {
showResponse({
error: '请填写所有必填字段'
}, true);
return;
}
if (userData.age < 0 || userData.age > 150) {
showResponse({
error: '年龄必须在0-150之间'
}, true);
return;
}
const result = await apiRequest(`${API_BASE}/users`, {
method: 'POST',
body: JSON.stringify(userData)
});
if (result) {
// 清空表单
this.reset();
}
});
// 页面加载完成后的初始化
document.addEventListener('DOMContentLoaded', function() {
// 显示欢迎信息
showResponse({
message: '欢迎使用 Go Web服务器 API 测试页面!',
instructions: [
'点击上方按钮测试各种API接口',
'使用表单创建新用户',
'查看下方的响应结果'
]
});
// 自动获取用户列表
setTimeout(getUsers, 1000);
});
// 键盘快捷键
document.addEventListener('keydown', function(e) {
// Ctrl + Enter 快速创建用户
if (e.ctrlKey && e.key === 'Enter') {
createUser();
}
// Ctrl + R 刷新用户列表
if (e.ctrlKey && e.key === 'r') {
e.preventDefault();
getUsers();
}
});
// 工具函数:格式化时间
function formatTime(timestamp) {
return new Date(timestamp).toLocaleString('zh-CN');
}
// 工具函数:格式化文件大小
function formatBytes(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
// 工具函数:复制到剪贴板
async function copyToClipboard(text) {
try {
await navigator.clipboard.writeText(text);
console.log('已复制到剪贴板');
} catch (err) {
console.error('复制失败:', err);
}
}
// 添加复制响应结果的功能
responseElement.addEventListener('click', function() {
if (this.textContent && this.textContent !== '加载中...') {
copyToClipboard(this.textContent);
}
});
// 自动刷新功能(可选)
let autoRefresh = false;
let refreshInterval;
function toggleAutoRefresh() {
autoRefresh = !autoRefresh;
if (autoRefresh) {
refreshInterval = setInterval(getUsers, 5000);
console.log('自动刷新已启用');
} else {
clearInterval(refreshInterval);
console.log('自动刷新已禁用');
}
}
// 错误重试机制
async function retryRequest(requestFunc, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await requestFunc();
} catch (error) {
if (i === maxRetries - 1) {
throw error;
}
await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
}
}
}

View File

@@ -0,0 +1,271 @@
/* style.css - 样式文件 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
color: #333;
background-color: #f5f5f5;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
header {
text-align: center;
margin-bottom: 40px;
padding: 30px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 10px;
box-shadow: 0 4px 15px rgba(0,0,0,0.1);
}
header h1 {
font-size: 2.5em;
margin-bottom: 10px;
}
header p {
font-size: 1.2em;
opacity: 0.9;
}
main {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 30px;
margin-bottom: 40px;
}
section {
background: white;
padding: 30px;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.response-section {
grid-column: 1 / -1;
}
h2 {
color: #333;
margin-bottom: 20px;
font-size: 1.5em;
border-bottom: 2px solid #667eea;
padding-bottom: 10px;
}
h3 {
color: #555;
margin: 20px 0 15px 0;
font-size: 1.2em;
}
.api-group {
margin-bottom: 30px;
}
.api-item {
display: flex;
align-items: center;
gap: 15px;
margin-bottom: 15px;
padding: 15px;
background: #f8f9fa;
border-radius: 8px;
border-left: 4px solid #667eea;
}
.api-item button {
background: #667eea;
color: white;
border: none;
padding: 8px 16px;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
transition: background-color 0.3s;
min-width: 120px;
}
.api-item button:hover {
background: #5a6fd8;
}
.api-item input {
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 5px;
font-size: 14px;
width: 100px;
}
.method {
font-weight: bold;
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
text-transform: uppercase;
min-width: 60px;
text-align: center;
}
.method.get {
background: #28a745;
color: white;
}
.method.post {
background: #007bff;
color: white;
}
.method.delete {
background: #dc3545;
color: white;
}
.endpoint {
font-family: 'Courier New', monospace;
background: #e9ecef;
padding: 4px 8px;
border-radius: 4px;
font-size: 13px;
color: #495057;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: 500;
color: #555;
}
.form-group input {
width: 100%;
padding: 12px;
border: 1px solid #ddd;
border-radius: 5px;
font-size: 16px;
transition: border-color 0.3s;
}
.form-group input:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.2);
}
#userForm button {
background: #28a745;
color: white;
border: none;
padding: 12px 30px;
border-radius: 5px;
cursor: pointer;
font-size: 16px;
transition: background-color 0.3s;
width: 100%;
}
#userForm button:hover {
background: #218838;
}
#response {
background: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 5px;
padding: 20px;
font-family: 'Courier New', monospace;
font-size: 14px;
line-height: 1.4;
white-space: pre-wrap;
word-wrap: break-word;
max-height: 400px;
overflow-y: auto;
color: #495057;
}
footer {
text-align: center;
padding: 20px;
color: #666;
border-top: 1px solid #eee;
margin-top: 40px;
}
/* 响应式设计 */
@media (max-width: 768px) {
main {
grid-template-columns: 1fr;
gap: 20px;
}
.api-item {
flex-direction: column;
align-items: flex-start;
gap: 10px;
}
.api-item button {
width: 100%;
}
header h1 {
font-size: 2em;
}
.container {
padding: 10px;
}
}
/* 加载动画 */
.loading {
opacity: 0.6;
pointer-events: none;
}
.loading::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 20px;
height: 20px;
margin: -10px 0 0 -10px;
border: 2px solid #667eea;
border-radius: 50%;
border-top-color: transparent;
animation: spin 1s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
/* 成功和错误状态 */
.success {
color: #28a745;
}
.error {
color: #dc3545;
}