“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:"-"` // 密码不返回给前端
}
相关推荐
AI绘画哇哒哒17 小时前
【建议收藏!】35岁后端血泪忠告,这3类人别硬转Agent(过来人亲述)
java·人工智能·后端·ai·程序员·大模型·agent
唐青枫18 小时前
一个值多种形态:Zig union、Tagged Union 与内存布局实战
后端
卷无止境19 小时前
Windows 上丝滑开发 Python,并稳定构建 Docker 镜像
后端·python·docker
Nturmoils19 小时前
备份完不算完,先还原到临时库验一遍
后端
Csvn19 小时前
📊 SQL 入门 Day 22:视图与物化视图
后端·sql
Csvn19 小时前
🐍 Day 4: Python 控制流 — 条件、循环与推导式的艺术
后端·python
2601_9537208220 小时前
【计算机毕业设计】基于Vue与Spring Boot的高校兼职信息服务平台设计与实现
spring boot·后端·课程设计
再吃一根胡萝卜21 小时前
用 Rust 写一个桌面悬浮图标:为什么它比 Python 更适合 AI 桌面工具?
后端
IT_陈寒1 天前
Vue的computed属性把我坑惨了,原来我一直用错姿势
前端·人工智能·后端
evans在进步1 天前
Spring Boot 核心机制详解:可执行 JAR、CORS、静态资源与配置绑定
spring boot·后端·jar