基本要求

使用gin+grom+mysql生成一个学习用的小web记账项目,简单先行,便于学习
暂无前端,使用postman测试接口?

数据库

用户表、消费表、统计表
不要外键

sql语句

-- 用户表
CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY COMMENT '用户ID',
    name VARCHAR(255) NOT NULL UNIQUE COMMENT '用户名(唯一)',
    password VARCHAR(255) NOT NULL COMMENT '密码(存储哈希值)',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
) COMMENT='用户信息表';

-- 消费记录表(使用数字分类)
CREATE TABLE expenses (
    id INT AUTO_INCREMENT PRIMARY KEY COMMENT '消费ID',
    user_id INT NOT NULL COMMENT '关联用户ID',
    note VARCHAR(255) NOT NULL COMMENT '消费说明',
    amount DECIMAL(10, 2) NOT NULL COMMENT '消费金额',
    category TINYINT NOT NULL COMMENT '消费分类: 1-餐饮, 2-交通, 3-娱乐, 4-购物, 5-住房, 6-其他',
    expense_date DATE NOT NULL COMMENT '消费日期',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
) COMMENT='消费记录表';

-- 统计表
CREATE TABLE statistics (
    id INT AUTO_INCREMENT PRIMARY KEY COMMENT '统计ID',
    user_id INT NOT NULL COMMENT '关联用户ID',
    day_total DECIMAL(10, 2) DEFAULT 0.00 COMMENT '当日总消费',
    week_total DECIMAL(10, 2) DEFAULT 0.00 COMMENT '本周总消费',
    month_total DECIMAL(10, 2) DEFAULT 0.00 COMMENT '本月总消费',
    year_total DECIMAL(10, 2) DEFAULT 0.00 COMMENT '本年总消费',
    last_updated DATE NOT NULL COMMENT '最后统计日期'
) COMMENT='用户消费统计表';

项目结构

DayCost
├── cmd/
│   └── main.go                 # 应用入口:初始化配置、路由和服务器
├── config/
│   └── config.go               # 配置加载(支持.env/env变量)
├── internal/
│   ├── model/                  # ORM模型定义
│   │   ├── user.go             # User模型 struct
│   │   ├── expense.go          # Expense模型 struct
│   │   └── statistic.go        # Statistic模型 struct
│   ├── handler/                # HTTP请求处理器
│   │   ├── user_handler.go     # 用户相关路由处理
│   │   ├── expense_handler.go  # 消费记录CRUD处理
│   │   └── stat_handler.go     # 统计数据处理
│   ├── repository/             # 数据库操作层
│   │   ├── user_repo.go        # 用户数据操作
│   │   ├── expense_repo.go     # 消费记录操作
│   │   └── stat_repo.go        # 统计数据操作
│   └── service/                # 业务逻辑层
│       ├── auth_service.go     # 认证相关逻辑
│       ├── expense_service.go  # 消费业务逻辑
│       └── stat_service.go     # 统计计算逻辑
├── pkg/
│   ├── database/
│   │   └── db.go               # 数据库连接初始化
│   └── util/
│       └── response.go         # 统一响应格式工具
├── routes/
│   └── router.go               # 路由定义和中间件注册
├── scripts/
│   └── init_db.sql             # 数据库初始化脚本
├── go.mod
├── go.sum
└── .env.example                # 环境变量示例文件

项目结构详细描述

main.go:程序入口、加载配置config-初始化数据库?、创建路由router、启动服务,
congig.go:配置,

internal:是什么意思,私有应用代码(外部项目无法导入)
model层:定义结构体
repository:封装crud
service:调用repository,复杂业务实现
handler:接收请求,返回响应,调用service层

pkg是什么意思
dasebase/db.go ,初始化数据库连接四大参数
util/response.go,统一响应体Result
router.go,路由

程序入口main.go

  • 初始化连接数据库,db.go
  • 初始化路由并启动服务,router.go
package main
import (
	"DayCost/pkg/database"
	"DayCost/routes"
	"log"
)
func main() {
	// 初始化数据库
	if err := database.InitDB(); err != nil {
		log.Fatalf("数据库初始化失败: %v", err)
	}
	// 创建路由
	r := routes.SetupRouter()
	// 启动服务
	if err := r.Run(":8080"); err != nil {
		log.Fatalf("服务启动失败: %v", err)
	}
}

数据库连接,生成一个连接好的DB对象

  • 数据库连接参数 - 后续可写在env环境中,这里直接写入
  • 数据库连接字符串dns
  • gorm.Open() //驱动+参数
package database

import (
	"fmt"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
)

var DB *gorm.DB

type Config struct {
	Host     string
	Port     string
	User     string
	Password string
	DBName   string
}

func InitDB() error {
	// 简化配置(实际项目中应从环境变量读取)
	cfg := Config{
		Host:     "localhost",
		Port:     "3306",
		User:     "root",
		Password: "123456",
		DBName:   "daycost",
	}

	dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local",
		cfg.User, cfg.Password, cfg.Host, cfg.Port, cfg.DBName)

	var err error
	DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})  //数据库驱动 //
	if err != nil {
		return err
	}

	return nil
}

用户登录功能实现,数据层-curd层-server层-统一响应体-handle控制层-路由层

model/user.go

package model
import "time"

type User struct {
	ID        uint      `gorm:"primaryKey;autoIncrement"`
	Name      string    `gorm:"unique;not null"`
	Password  string    `gorm:"not null"`
	CreatedAt time.Time `gorm:"autoCreateTime"`
	UpdatedAt time.Time `gorm:"autoUpdateTime"`
}
数据类型和grom注解详解

Go与MySQL数据类型及GORM注解详解

repository/user_repo.go

在Gin或其他现代Web框架中,repository作为DAO层的命名源于领域驱动设计(DDD)模式。DDD将数据访问逻辑抽象为“仓储”(Repository),强调对领域对象的集合式操作,而非单纯数据库表操作。

package repository
import (
	"DayCost/internal/model"
	"DayCost/pkg/database"
)
// 用户仓库结构体
type UserRepository struct{}

// 根据用户名查询用户
func (r *UserRepository) FindUserByName(name string) (*model.User, error) {
    var user model.User
    // Where 条件查询 + First 获取第一条记录
    result := database.DB.Where("name = ?", name).First(&user)
    return &user, result.Error
}

// 创建用户
func (r *UserRepository) CreateUser(user *model.User) error {
    // Create 插入记录
    result := database.DB.Create(user)
    return result.Error
}
Gorm 完成 CRUD 操作

Gorm 完成 CRUD 操作

service/auth_service.go

package service

import (
	"DayCost/internal/model"
	"DayCost/internal/repository"
)

type AuthService struct {
	userRepo *repository.UserRepository
}

func NewAuthService() *AuthService {
	return &AuthService{
		userRepo: &repository.UserRepository{},
	}
}

func (s *AuthService) Register(user *model.User) error {
	return s.userRepo.CreateUser(user)
}

func (s *AuthService) Login(name, password string) (*model.User, bool) {
	user, err := s.userRepo.FindUserByName(name)
	if err != nil {
		return nil, false
	}

	// 简化密码验证(实际应使用bcrypt)
	if user.Password != password {
		return nil, false
	}

	return user, true
}

handle/user_handle.go

package handler

import (
	"DayCost/internal/service"
	"DayCost/pkg/util"

	"github.com/gin-gonic/gin"
)

type AuthHandler struct {
	authService *service.AuthService
}

func NewAuthHandler() *AuthHandler {
	return &AuthHandler{
		authService: service.NewAuthService(),
	}
}

func (h *AuthHandler) Login(c *gin.Context) {
	var req struct {
		Name     string `json:"name" binding:"required"`
		Password string `json:"password" binding:"required"`
	}

	if err := c.ShouldBindJSON(&req); err != nil {
		util.SendBadRequest(c, "无效的请求格式")
		return
	}

	user, ok := h.authService.Login(req.Name, req.Password)
	if !ok {
		util.SendUnauthorized(c, "用户名或密码错误")
		return
	}

	// 简化响应(实际应返回JWT token)
	util.SendSuccess(c, gin.H{
		"id":   user.ID,
		"name": user.Name,
	})
}

response.go

package util

import "github.com/gin-gonic/gin"

func SendSuccess(c *gin.Context, data interface{}) {
	c.JSON(200, gin.H{
		"code": 0,
		"msg":  "success",
		"data": data,
	})
}

func SendBadRequest(c *gin.Context, message string) {
	c.JSON(400, gin.H{
		"code": 400,
		"msg":  message,
	})
}

func SendUnauthorized(c *gin.Context, message string) {
	c.JSON(401, gin.H{
		"code": 401,
		"msg":  message,
	})
}

router.go

package routes

import (
	"DayCost/internal/handler"

	"github.com/gin-gonic/gin"
)

func SetupRouter() *gin.Engine {
	r := gin.Default()

	authHandler := handler.NewAuthHandler()

	// 用户路由
	userGroup := r.Group("/api/user")
	{
		userGroup.POST("/login", authHandler.Login)
	}

	return r
}

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐