“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:"-"` // 密码不返回给前端
}
相关推荐
Victor3567 小时前
https://editor.csdn.net/md/?articleId=139321571&spm=1011.2415.3001.9698
后端
Victor3567 小时前
Hibernate(89)如何在压力测试中使用Hibernate?
后端
灰子学技术9 小时前
go response.Body.close()导致连接异常处理
开发语言·后端·golang
Gogo81610 小时前
BigInt 与 Number 的爱恨情仇,为何大佬都劝你“能用 Number 就别用 BigInt”?
后端
fuquxiaoguang10 小时前
深入浅出:使用MDC构建SpringBoot全链路请求追踪系统
java·spring boot·后端·调用链分析
毕设源码_廖学姐10 小时前
计算机毕业设计springboot招聘系统网站 基于SpringBoot的在线人才对接平台 SpringBoot驱动的智能求职与招聘服务网
spring boot·后端·课程设计
野犬寒鸦12 小时前
从零起步学习并发编程 || 第六章:ReentrantLock与synchronized 的辨析及运用
java·服务器·数据库·后端·学习·算法
逍遥德12 小时前
如何学编程之01.理论篇.如何通过阅读代码来提高自己的编程能力?
前端·后端·程序人生·重构·软件构建·代码规范
MX_935913 小时前
Spring的bean工厂后处理器和Bean后处理器
java·后端·spring
程序员泠零澪回家种桔子14 小时前
Spring AI框架全方位详解
java·人工智能·后端·spring·ai·架构