项目初始化

This commit is contained in:
2025-12-10 19:03:24 +08:00
commit 04d920aa92
14 changed files with 909 additions and 0 deletions

119
README.md Normal file
View File

@@ -0,0 +1,119 @@
# RelationshipManagement-backend
基于 Gin 框架的分层架构后端项目
## 项目结构
```
RelationshipManagement-backend/
├── main.go # 应用入口
├── go.mod # Go 模块依赖
├── config/ # 配置层
│ └── config.go # 配置管理
├── model/ # 数据模型层
│ └── user.go # 用户模型
├── dao/ # 数据访问层 (Data Access Object)
│ ├── db.go # 数据库连接
│ └── user_dao.go # 用户数据访问
├── service/ # 业务逻辑层
│ └── user_service.go # 用户服务
├── handler/ # 处理器层 (Controller)
│ └── user_handler.go # 用户处理器
├── router/ # 路由层
│ └── router.go # 路由配置
└── middleware/ # 中间件
├── cors.go # 跨域中间件
├── logger.go # 日志中间件
└── recovery.go # 恢复中间件
```
## 架构说明
### 分层架构
1. **Router 层** (`router/`)
- 负责路由配置和请求分发
- 将 HTTP 请求路由到对应的 Handler
2. **Handler 层** (`handler/`)
- 处理 HTTP 请求和响应
- 参数验证和绑定
- 调用 Service 层处理业务逻辑
3. **Service 层** (`service/`)
- 业务逻辑处理
- 数据验证和业务规则
- 调用 DAO 层进行数据操作
4. **DAO 层** (`dao/`)
- 数据访问对象
- 数据库 CRUD 操作
- 数据库连接管理
5. **Model 层** (`model/`)
- 数据模型定义
- 数据库表结构映射
6. **Config 层** (`config/`)
- 配置管理
- 环境变量读取
7. **Middleware 层** (`middleware/`)
- 全局中间件
- CORS、日志、错误恢复等
## 环境配置
通过环境变量配置(可选,有默认值):
- `SERVER_PORT`: 服务器端口(默认: 8080
- `GIN_MODE`: Gin 模式debug/release/test默认: debug
- `DB_HOST`: 数据库主机(默认: localhost
- `DB_PORT`: 数据库端口(默认: 3306
- `DB_USER`: 数据库用户(默认: root
- `DB_PASSWORD`: 数据库密码(默认: 空)
- `DB_NAME`: 数据库名称(默认: relationship_db
## 运行项目
1. 安装依赖:
```bash
go mod tidy
```
2. 配置数据库(可选,修改环境变量或代码中的默认值)
3. 运行项目:
```bash
go run main.go
```
4. 访问健康检查接口:
```
GET http://localhost:8080/health
```
## API 接口
### 用户相关接口
- `POST /api/users` - 创建用户
- `GET /api/users` - 获取用户列表(支持分页)
- `GET /api/users/:id` - 根据ID获取用户
- `PUT /api/users/:id` - 更新用户
- `DELETE /api/users/:id` - 删除用户
## 依赖说明
- **gin**: Web 框架
- **gorm**: ORM 框架
- **mysql**: MySQL 数据库驱动
- **logrus**: 日志库
## 开发建议
1. 添加新的业务模块时,按照相同的分层结构创建文件
2.`dao/db.go``AutoMigrate()` 函数中添加新模型的自动迁移
3. 根据实际需求调整中间件和配置
4. 生产环境建议使用配置文件(如 YAML、JSON替代环境变量

52
config/config.go Normal file
View File

@@ -0,0 +1,52 @@
package config
import (
"os"
)
// Config 应用配置
type Config struct {
Server ServerConfig
DB DBConfig
}
// ServerConfig 服务器配置
type ServerConfig struct {
Port string
Mode string // debug, release, test
}
// DBConfig 数据库配置
type DBConfig struct {
Host string
Port string
User string
Password string
DBName string
}
// LoadConfig 加载配置
func LoadConfig() *Config {
return &Config{
Server: ServerConfig{
Port: getEnv("SERVER_PORT", "8080"),
Mode: getEnv("GIN_MODE", "debug"),
},
DB: DBConfig{
Host: getEnv("DB_HOST", "localhost"),
Port: getEnv("DB_PORT", "3306"),
User: getEnv("DB_USER", "root"),
Password: getEnv("DB_PASSWORD", ""),
DBName: getEnv("DB_NAME", "relationship_db"),
},
}
}
// getEnv 获取环境变量,如果不存在则返回默认值
func getEnv(key, defaultValue string) string {
if value := os.Getenv(key); value != "" {
return value
}
return defaultValue
}

53
dao/db.go Normal file
View File

@@ -0,0 +1,53 @@
package dao
import (
"RelationshipManagement-backend/config"
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
var DB *gorm.DB
// InitDB 初始化数据库连接
func InitDB(cfg *config.Config) error {
dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local",
cfg.DB.User,
cfg.DB.Password,
cfg.DB.Host,
cfg.DB.Port,
cfg.DB.DBName,
)
var err error
DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{
Logger: logger.Default.LogMode(logger.Info),
})
if err != nil {
return fmt.Errorf("failed to connect database: %w", err)
}
// 自动迁移(开发环境使用,生产环境建议使用迁移工具)
// AutoMigrate()
return nil
}
// AutoMigrate 自动迁移数据库表
func AutoMigrate() error {
// 在这里添加需要自动迁移的模型
// return DB.AutoMigrate(&model.User{})
return nil
}
// CloseDB 关闭数据库连接
func CloseDB() error {
sqlDB, err := DB.DB()
if err != nil {
return err
}
return sqlDB.Close()
}

69
dao/user_dao.go Normal file
View File

@@ -0,0 +1,69 @@
package dao
import (
"RelationshipManagement-backend/model"
"gorm.io/gorm"
)
// UserDAO 用户数据访问对象
type UserDAO struct {
db *gorm.DB
}
// NewUserDAO 创建用户DAO实例
func NewUserDAO(db *gorm.DB) *UserDAO {
return &UserDAO{db: db}
}
// Create 创建用户
func (dao *UserDAO) Create(user *model.User) error {
return dao.db.Create(user).Error
}
// GetByID 根据ID获取用户
func (dao *UserDAO) GetByID(id uint) (*model.User, error) {
var user model.User
err := dao.db.First(&user, id).Error
if err != nil {
return nil, err
}
return &user, nil
}
// GetByUsername 根据用户名获取用户
func (dao *UserDAO) GetByUsername(username string) (*model.User, error) {
var user model.User
err := dao.db.Where("username = ?", username).First(&user).Error
if err != nil {
return nil, err
}
return &user, nil
}
// GetByEmail 根据邮箱获取用户
func (dao *UserDAO) GetByEmail(email string) (*model.User, error) {
var user model.User
err := dao.db.Where("email = ?", email).First(&user).Error
if err != nil {
return nil, err
}
return &user, nil
}
// Update 更新用户
func (dao *UserDAO) Update(user *model.User) error {
return dao.db.Save(user).Error
}
// Delete 删除用户
func (dao *UserDAO) Delete(id uint) error {
return dao.db.Delete(&model.User{}, id).Error
}
// List 获取用户列表
func (dao *UserDAO) List(offset, limit int) ([]*model.User, error) {
var users []*model.User
err := dao.db.Offset(offset).Limit(limit).Find(&users).Error
return users, err
}

42
go.mod Normal file
View File

@@ -0,0 +1,42 @@
module RelationshipManagement-backend
go 1.25.4
require (
github.com/gin-gonic/gin v1.10.0
github.com/sirupsen/logrus v1.9.3
gorm.io/driver/mysql v1.5.7
gorm.io/gorm v1.25.12
)
require (
github.com/bytedance/sonic v1.11.6 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.20.0 // indirect
github.com/go-sql-driver/mysql v1.7.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
golang.org/x/arch v0.8.0 // indirect
golang.org/x/crypto v0.23.0 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/text v0.15.0 // indirect
google.golang.org/protobuf v1.34.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

103
go.sum Normal file
View File

@@ -0,0 +1,103 @@
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo=
gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM=
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8=
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=

217
handler/user_handler.go Normal file
View File

@@ -0,0 +1,217 @@
package handler
import (
"RelationshipManagement-backend/model"
"RelationshipManagement-backend/service"
"net/http"
"strconv"
"github.com/gin-gonic/gin"
)
// UserHandler 用户处理器
type UserHandler struct {
userService *service.UserService
}
// NewUserHandler 创建用户处理器实例
func NewUserHandler(userService *service.UserService) *UserHandler {
return &UserHandler{userService: userService}
}
// CreateUser 创建用户
// @Summary 创建用户
// @Description 创建新用户
// @Tags 用户
// @Accept json
// @Produce json
// @Param user body model.User true "用户信息"
// @Success 200 {object} map[string]interface{}
// @Failure 400 {object} map[string]interface{}
// @Router /api/users [post]
func (h *UserHandler) CreateUser(c *gin.Context) {
var user model.User
if err := c.ShouldBindJSON(&user); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
"message": "参数错误",
"error": err.Error(),
})
return
}
if err := h.userService.CreateUser(&user); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
"message": "创建用户失败",
"error": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"message": "创建成功",
"data": user,
})
}
// GetUserByID 根据ID获取用户
// @Summary 获取用户
// @Description 根据ID获取用户信息
// @Tags 用户
// @Accept json
// @Produce json
// @Param id path int true "用户ID"
// @Success 200 {object} map[string]interface{}
// @Failure 400 {object} map[string]interface{}
// @Router /api/users/{id} [get]
func (h *UserHandler) GetUserByID(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
"message": "无效的用户ID",
})
return
}
user, err := h.userService.GetUserByID(uint(id))
if err != nil {
c.JSON(http.StatusNotFound, gin.H{
"code": 404,
"message": "用户不存在",
"error": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"message": "获取成功",
"data": user,
})
}
// ListUsers 获取用户列表
// @Summary 获取用户列表
// @Description 分页获取用户列表
// @Tags 用户
// @Accept json
// @Produce json
// @Param page query int false "页码" default(1)
// @Param pageSize query int false "每页数量" default(10)
// @Success 200 {object} map[string]interface{}
// @Router /api/users [get]
func (h *UserHandler) ListUsers(c *gin.Context) {
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("pageSize", "10"))
users, total, err := h.userService.ListUsers(page, pageSize)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"code": 500,
"message": "获取用户列表失败",
"error": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"message": "获取成功",
"data": gin.H{
"list": users,
"total": total,
"page": page,
"pageSize": pageSize,
},
})
}
// UpdateUser 更新用户
// @Summary 更新用户
// @Description 更新用户信息
// @Tags 用户
// @Accept json
// @Produce json
// @Param id path int true "用户ID"
// @Param user body model.User true "用户信息"
// @Success 200 {object} map[string]interface{}
// @Failure 400 {object} map[string]interface{}
// @Router /api/users/{id} [put]
func (h *UserHandler) UpdateUser(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
"message": "无效的用户ID",
})
return
}
var user model.User
if err := c.ShouldBindJSON(&user); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
"message": "参数错误",
"error": err.Error(),
})
return
}
user.ID = uint(id)
if err := h.userService.UpdateUser(&user); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
"message": "更新用户失败",
"error": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"message": "更新成功",
"data": user,
})
}
// DeleteUser 删除用户
// @Summary 删除用户
// @Description 根据ID删除用户
// @Tags 用户
// @Accept json
// @Produce json
// @Param id path int true "用户ID"
// @Success 200 {object} map[string]interface{}
// @Failure 400 {object} map[string]interface{}
// @Router /api/users/{id} [delete]
func (h *UserHandler) DeleteUser(c *gin.Context) {
idStr := c.Param("id")
id, err := strconv.ParseUint(idStr, 10, 32)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
"message": "无效的用户ID",
})
return
}
if err := h.userService.DeleteUser(uint(id)); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
"message": "删除用户失败",
"error": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"code": 200,
"message": "删除成功",
})
}

35
main.go Normal file
View File

@@ -0,0 +1,35 @@
package main
import (
"RelationshipManagement-backend/config"
"RelationshipManagement-backend/dao"
"RelationshipManagement-backend/router"
"fmt"
"log"
"github.com/gin-gonic/gin"
)
func main() {
// 加载配置
cfg := config.LoadConfig()
// 设置Gin模式
gin.SetMode(cfg.Server.Mode)
// 初始化数据库
if err := dao.InitDB(cfg); err != nil {
log.Fatalf("数据库初始化失败: %v", err)
}
defer dao.CloseDB()
// 设置路由
r := router.SetupRouter()
// 启动服务器
addr := fmt.Sprintf(":%s", cfg.Server.Port)
log.Printf("服务器启动在端口 %s", cfg.Server.Port)
if err := r.Run(addr); err != nil {
log.Fatalf("服务器启动失败: %v", err)
}
}

23
middleware/cors.go Normal file
View File

@@ -0,0 +1,23 @@
package middleware
import (
"github.com/gin-gonic/gin"
)
// CORS 跨域中间件
func CORS() gin.HandlerFunc {
return func(c *gin.Context) {
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With")
c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT, DELETE")
if c.Request.Method == "OPTIONS" {
c.AbortWithStatus(204)
return
}
c.Next()
}
}

37
middleware/logger.go Normal file
View File

@@ -0,0 +1,37 @@
package middleware
import (
"time"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
)
// Logger 日志中间件
func Logger() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
path := c.Request.URL.Path
raw := c.Request.URL.RawQuery
c.Next()
latency := time.Since(start)
clientIP := c.ClientIP()
method := c.Request.Method
statusCode := c.Writer.Status()
if raw != "" {
path = path + "?" + raw
}
logrus.WithFields(logrus.Fields{
"status_code": statusCode,
"latency": latency,
"client_ip": clientIP,
"method": method,
"path": path,
}).Info("HTTP Request")
}
}

21
middleware/recovery.go Normal file
View File

@@ -0,0 +1,21 @@
package middleware
import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
)
// Recovery 恢复中间件
func Recovery() gin.HandlerFunc {
return gin.CustomRecovery(func(c *gin.Context, recovered interface{}) {
logrus.Errorf("Panic recovered: %v", recovered)
c.JSON(http.StatusInternalServerError, gin.H{
"code": 500,
"message": "服务器内部错误",
})
c.Abort()
})
}

19
model/user.go Normal file
View File

@@ -0,0 +1,19 @@
package model
import "time"
// User 用户模型
type User struct {
ID uint `json:"id" gorm:"primaryKey"`
Username string `json:"username" gorm:"uniqueIndex;not null"`
Email string `json:"email" gorm:"uniqueIndex;not null"`
Password string `json:"-" gorm:"not null"` // 密码不返回给前端
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// TableName 指定表名
func (User) TableName() string {
return "users"
}

50
router/router.go Normal file
View File

@@ -0,0 +1,50 @@
package router
import (
"RelationshipManagement-backend/handler"
"RelationshipManagement-backend/middleware"
"RelationshipManagement-backend/service"
"RelationshipManagement-backend/dao"
"github.com/gin-gonic/gin"
)
// SetupRouter 设置路由
func SetupRouter() *gin.Engine {
// 初始化依赖
userDAO := dao.NewUserDAO(dao.DB)
userService := service.NewUserService(userDAO)
userHandler := handler.NewUserHandler(userService)
r := gin.Default()
// 全局中间件
r.Use(middleware.CORS())
r.Use(middleware.Logger())
r.Use(middleware.Recovery())
// 健康检查
r.GET("/health", func(c *gin.Context) {
c.JSON(200, gin.H{
"status": "ok",
"message": "服务运行正常",
})
})
// API路由组
api := r.Group("/api")
{
// 用户相关路由
users := api.Group("/users")
{
users.POST("", userHandler.CreateUser)
users.GET("", userHandler.ListUsers)
users.GET("/:id", userHandler.GetUserByID)
users.PUT("/:id", userHandler.UpdateUser)
users.DELETE("/:id", userHandler.DeleteUser)
}
}
return r
}

69
service/user_service.go Normal file
View File

@@ -0,0 +1,69 @@
package service
import (
"RelationshipManagement-backend/dao"
"RelationshipManagement-backend/model"
"errors"
)
// UserService 用户服务
type UserService struct {
userDAO *dao.UserDAO
}
// NewUserService 创建用户服务实例
func NewUserService(userDAO *dao.UserDAO) *UserService {
return &UserService{userDAO: userDAO}
}
// CreateUser 创建用户
func (s *UserService) CreateUser(user *model.User) error {
// 检查用户名是否已存在
_, err := s.userDAO.GetByUsername(user.Username)
if err == nil {
return errors.New("用户名已存在")
}
// 检查邮箱是否已存在
_, err = s.userDAO.GetByEmail(user.Email)
if err == nil {
return errors.New("邮箱已存在")
}
// 创建用户
return s.userDAO.Create(user)
}
// GetUserByID 根据ID获取用户
func (s *UserService) GetUserByID(id uint) (*model.User, error) {
return s.userDAO.GetByID(id)
}
// GetUserByUsername 根据用户名获取用户
func (s *UserService) GetUserByUsername(username string) (*model.User, error) {
return s.userDAO.GetByUsername(username)
}
// UpdateUser 更新用户
func (s *UserService) UpdateUser(user *model.User) error {
return s.userDAO.Update(user)
}
// DeleteUser 删除用户
func (s *UserService) DeleteUser(id uint) error {
return s.userDAO.Delete(id)
}
// ListUsers 获取用户列表
func (s *UserService) ListUsers(page, pageSize int) ([]*model.User, int64, error) {
offset := (page - 1) * pageSize
users, err := s.userDAO.List(offset, pageSize)
if err != nil {
return nil, 0, err
}
// 获取总数这里简化处理实际应该从DAO获取
total := int64(len(users))
return users, total, nil
}