“Controller→Service→DAO”三层架构

前言

后端架构中Controller-Service-DAO三层架构非常常见,因为之前学的C++,没有学Java的Spring Boot等框架,所以还不太了解。现在看了项目里很多代码都是基于这种架构,特地来学习一下。

各层核心职责和作用

这种架构基于单一职责原则设计,核心目标是拆分请求处理,业务逻辑和数据访问的职责。好处是降低代码耦合度,提升可维护性和可测试性。整个三层严格遵循 "单向依赖"(Controller 仅调用 Service,Service 仅调用 DAO,禁止反向或跨层调用)

Controller层

核心职责

接收前端请求,参数校验,调用Service,返回响应

作用

作为前端请求和后端业务逻辑的桥梁,不负责任何业务逻辑,只负责需求转发

价值

  1. 过滤无效参数,拦截无效请求,从而避免消耗Service层资源,减少不必要业务逻辑代码执行;
  2. 统一请求入口,减少前后端交互的复杂性
  3. 统一响应格式,且可以用用户友好度高的方式返回错误处理,避免暴露后端堆栈错误信息或者SQL错误信息等
  4. 防止xss攻击

Service层

核心职责

封装所有的业务逻辑,协调多个DAO

作用

后端的"大脑",是业务规则的实现地,但不直接操作数据库,而是通过DAO获取/修改数据

价值

  1. 将业务规则、计算、判断逻辑集中在Service层实现,实现高内聚;业务逻辑的变化之需要修改Service层
  2. 协调多个资源和多个DAO,可以实现复杂的业务流程

DAO层

核心职责

仅负责与数据库交互,实现数据的 CRUD(增删改查),不包含任何业务逻辑

作用

隔离 "业务逻辑" 与 "数据操作",当数据库(如 MySQL→PostgreSQL)或访问方式(如 JDBC→MyBatis)变化时,仅需修改 DAO 层,不影响 Service 和 Controller。


例子

下面我用 Go 语言实现一个极简的用户登录注册模块,展示 Controller→Service→DAO 三层架构的设计。这个示例包含用户注册和登录两个核心功能,清晰展示了各层的职责划分。

main.go

go 复制代码
package main

import (
	"net/http"
)

func main() {
	// 初始化各层实例
	userDAO := NewUserDAO()
	userService := NewUserService(userDAO)
	userController := NewUserController(userService)

	// 注册路由
	http.HandleFunc("/register", userController.Register)
	http.HandleFunc("/login", userController.Login)

	// 启动服务
	println("Server is running on :8080")
	http.ListenAndServe(":8080", nil)
}

Controller层

go 复制代码
package main

import (
	"encoding/json"
	"net/http"
	"strings"
)

// UserController 处理用户相关的HTTP请求
type UserController struct {
	userService *UserService
}

// NewUserController 创建UserController实例
func NewUserController(service *UserService) *UserController {
	return &UserController{
		userService: service,
	}
}

// Register 处理用户注册请求
func (c *UserController) Register(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")

	// 只允许POST方法
	if r.Method != http.MethodPost {
		w.WriteHeader(http.StatusMethodNotAllowed)
		json.NewEncoder(w).Encode(map[string]string{"error": "只允许POST方法"})
		return
	}

	// 解析请求参数
	var req struct {
		Username string `json:"username"`
		Password string `json:"password"`
	}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		w.WriteHeader(http.StatusBadRequest)
		json.NewEncoder(w).Encode(map[string]string{"error": "请求参数格式错误"})
		return
	}

	// 简单的参数校验
	if strings.TrimSpace(req.Username) == "" || strings.TrimSpace(req.Password) == "" {
		w.WriteHeader(http.StatusBadRequest)
		json.NewEncoder(w).Encode(map[string]string{"error": "用户名和密码不能为空"})
		return
	}

	// 调用Service层处理业务逻辑
	user, err := c.userService.Register(req.Username, req.Password)
	if err != nil {
		w.WriteHeader(http.StatusBadRequest)
		json.NewEncoder(w).Encode(map[string]string{"error": err.Error()})
		return
	}

	// 返回成功响应
	w.WriteHeader(http.StatusCreated)
	json.NewEncoder(w).Encode(map[string]interface{}{
		"message": "注册成功",
		"user":    user,
	})
}

// Login 处理用户登录请求
func (c *UserController) Login(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")

	// 只允许POST方法
	if r.Method != http.MethodPost {
		w.WriteHeader(http.StatusMethodNotAllowed)
		json.NewEncoder(w).Encode(map[string]string{"error": "只允许POST方法"})
		return
	}

	// 解析请求参数
	var req struct {
		Username string `json:"username"`
		Password string `json:"password"`
	}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		w.WriteHeader(http.StatusBadRequest)
		json.NewEncoder(w).Encode(map[string]string{"error": "请求参数格式错误"})
		return
	}

	// 调用Service层处理业务逻辑
	user, err := c.userService.Login(req.Username, req.Password)
	if err != nil {
		w.WriteHeader(http.StatusUnauthorized)
		json.NewEncoder(w).Encode(map[string]string{"error": err.Error()})
		return
	}

	// 返回成功响应
	json.NewEncoder(w).Encode(map[string]interface{}{
		"message": "登录成功",
		"user":    user,
	})
}

Service层

go 复制代码
package main

import (
	"errors"
	"golang.org/x/crypto/bcrypt"
)

// UserService 处理用户相关的业务逻辑
type UserService struct {
	userDAO *UserDAO
}

// NewUserService 创建UserService实例
func NewUserService(dao *UserDAO) *UserService {
	return &UserService{
		userDAO: dao,
	}
}

// Register 处理用户注册业务逻辑
func (s *UserService) Register(username, password string) (*User, error) {
	// 检查用户名是否已存在
	existingUser, _ := s.userDAO.FindByUsername(username)
	if existingUser != nil {
		return nil, errors.New("用户名已存在")
	}

	// 密码加密
	hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
	if err != nil {
		return nil, errors.New("密码加密失败")
	}

	// 调用DAO层保存用户
	user := &User{
		Username: username,
		Password: string(hashedPassword),
	}
	return s.userDAO.Save(user)
}

// Login 处理用户登录业务逻辑
func (s *UserService) Login(username, password string) (*User, error) {
	// 调用DAO层查询用户
	user, err := s.userDAO.FindByUsername(username)
	if err != nil || user == nil {
		return nil, errors.New("用户名或密码错误")
	}

	// 验证密码
	err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password))
	if err != nil {
		return nil, errors.New("用户名或密码错误")
	}

	return user, nil
}

DAO层

go 复制代码
package main

import (
	"errors"
	"sync"
)

// UserDAO 处理用户数据的访问操作
type UserDAO struct {
	users  map[string]*User // 模拟数据库,key为用户名
	nextID int
	mu     sync.Mutex       // 保证并发安全
}

// NewUserDAO 创建UserDAO实例
func NewUserDAO() *UserDAO {
	return &UserDAO{
		users:  make(map[string]*User),
		nextID: 1,
	}
}

// Save 保存用户到数据存储
func (d *UserDAO) Save(user *User) (*User, error) {
	d.mu.Lock()
	defer d.mu.Unlock()

	// 设置ID
	user.ID = d.nextID
	d.nextID++

	// 保存用户
	d.users[user.Username] = user
	return user, nil
}

// FindByUsername 根据用户名查询用户
func (d *UserDAO) FindByUsername(username string) (*User, error) {
	d.mu.Lock()
	defer d.mu.Unlock()

	user, exists := d.users[username]
	if !exists {
		return nil, errors.New("用户不存在")
	}
	return user, nil
}
go 复制代码
package main

// User 用户模型
type User struct {
	ID       int    `json:"id"`
	Username string `json:"username"`
	Password string `json:"-"` // 密码不返回给前端
}
相关推荐
bobz9652 小时前
计算虚拟化的设计
后端
深圳蔓延科技2 小时前
Kafka的高性能之路
后端·kafka
Barcke2 小时前
深入浅出 Spring WebFlux:从核心原理到深度实战
后端
JuiceFS2 小时前
从 MLPerf Storage v2.0 看 AI 训练中的存储性能与扩展能力
运维·后端
大鸡腿同学3 小时前
Think with a farmer's mindset
后端
Moonbit3 小时前
用MoonBit开发一个C编译器
后端·编程语言·编译器
Reboot4 小时前
达梦数据库GROUP BY报错解决方法
后端
稻草人22224 小时前
java Excel 导出 ,如何实现八倍效率优化,以及代码分层,方法封装
后端·架构
掘金者阿豪4 小时前
打通KingbaseES与MyBatis:一篇详尽的Java数据持久化实践指南
前端·后端