Spring Boot MVC 项目最佳实践规范

本文档整理了项目开发中的核心规范和常用代码模板,方便编码时快速查阅。


一、命名规范

类型 命名格式 示例
Entity XxxEntityXxx UserTeam
DTO XxxDTOXxxRequest UserRegisterDTO
VO XxxVO UserVO
Enum XxxEnum GenderEnum
Service XxxService UserService
ServiceImpl XxxServiceImpl UserServiceImpl
Controller XxxController UserController
Mapper XxxMapper UserMapper

二、分层架构

┌─────────────┐
│  Controller │ → 参数接收、非空校验、触发DTO校验、调用Service
├─────────────┤
│   Service   │ → 业务逻辑、业务校验、事务管理、调用Mapper
├─────────────┤
│   Mapper    │ → 数据库操作(CRUD)
├─────────────┤
│   Entity    │ → 数据库表映射
├─────────────┤
│    DTO      │ → 请求数据封装、字段校验
├─────────────┤
│     VO      │ → 响应数据封装、脱敏处理
└─────────────┘

职责边界

  • Controller:只做参数接收和结果返回,不写业务逻辑
  • Service:核心业务逻辑,事务控制在此层
  • Mapper:纯数据库操作,不含业务判断

三、校验注解速查

注解 作用 示例
@NotNull 不能为 null @NotNull Long id
@NotBlank 字符串非空且非空白 @NotBlank String name
@NotEmpty 集合/数组非空 @NotEmpty List<String> tags
@Size 长度/大小范围 @Size(min=1, max=10)
@Min / @Max 数值范围 @Min(0) @Max(100)
@Email 邮箱格式 @Email String email
@Pattern 正则匹配 @Pattern(regexp="...")
@AssertTrue 自定义校验方法 @AssertTrue isValid()
@Valid 触发嵌套校验 @Valid AddressDTO address
@Validated 分组校验 @Validated(UpdateGroup.class)

使用示例

@Data
public class UserRegisterDTO {
    @NotBlank(message = "用户名不能为空")
    @Size(min = 2, max = 20, message = "用户名长度2-20字符")
    private String username;

    @NotBlank(message = "密码不能为空")
    @Size(min = 6, max = 20, message = "密码长度6-20字符")
    private String password;

    @Email(message = "邮箱格式不正确")
    private String email;
}

四、枚举设计规范

4.1 标准枚举模板

@Getter
@AllArgsConstructor
public enum GenderEnum {
    MALE(0, "男"),
    FEMALE(1, "女"),
    UNKNOWN(2, "未知");

    @EnumValue  // MyBatis-Plus:存储到数据库的字段
    private final Integer code;

    @JsonValue  // Jackson:序列化到 JSON 的字段
    private final String desc;

    /**
     * 根据 code 获取枚举(反序列化用)
     */
    @JsonCreator
    public static GenderEnum fromCode(Integer code) {
        if (code == null) return null;
        for (GenderEnum e : values()) {
            if (e.code.equals(code)) return e;
        }
        return null;
    }

    /**
     * 校验 code 是否有效
     */
    public static boolean isValid(Integer code) {
        return fromCode(code) != null;
    }
}

4.2 枚举设计要点

注解 作用
@EnumValue 标记存储到数据库的字段
@JsonValue 标记序列化到 JSON 的字段
@JsonCreator 标记反序列化的工厂方法

必备方法

  • fromCode() - 根据 code 获取枚举
  • isValid() - 验证 code 是否有效

五、异常处理规范

5.1 异常使用原则

做法 说明
✅ 定义专有 ErrorCode 为不同业务错误定义不同的错误码
✅ 添加上下文信息 抛出异常时提供具体信息
✅ 使用 ThrowUtils 简化异常抛出代码
✅ 区分错误类型 CLIENT(用户错误) vs SERVER(系统错误)
✅ 提前校验 在关键操作前进行参数和业务校验

5.2 正确示例

// ✅ 好的做法:专有错误码 + 上下文信息
throw new BusinessException(
    ErrorCode.USER_NOT_FOUND,
    "User with id " + userId + " does not exist"
);

// ✅ 好的做法:使用 ThrowUtils 简化
ThrowUtils.throwIfNull(user, ErrorCode.USER_NOT_FOUND);
ThrowUtils.throwIf(amount <= 0, ErrorCode.PARAMS_ERROR, "金额必须大于0");

// ❌ 不好的做法:通用错误码,无上下文
throw new BusinessException(ErrorCode.ERROR);

5.3 ThrowUtils 常用方法

// 条件判断
ThrowUtils.throwIf(condition, ErrorCode.XXX);
ThrowUtils.throwIf(condition, ErrorCode.XXX, "详细信息");

// 空值判断
ThrowUtils.throwIfNull(obj, ErrorCode.XXX);
User user = ThrowUtils.throwIfNull(userMapper.selectById(id), ErrorCode.USER_NOT_FOUND);

// 空字符串判断
ThrowUtils.throwIfBlank(str, ErrorCode.XXX);

六、权限校验

6.1 注解方式

注解 作用 示例
@Anonymous 允许匿名访问 @Anonymous @GetMapping("/public")
@RequiresPermission("xxx") 需要单个权限 @RequiresPermission("user:add")
@RequiresPermission(value={"a","b"}, logical=Logical.OR) 任一权限即可 满足 a 或 b
@RequiresPermission(value={"a","b"}, logical=Logical.AND) 需要所有权限 同时有 a 和 b
@RequiresRole("xxx") 需要角色 @RequiresRole("admin")

6.2 获取用户信息

// 可能为 null
LoginUserVO user = SecurityUtils.getLoginUser();
Long userId = SecurityUtils.getUserId();
String username = SecurityUtils.getUsername();

// 必须有值,否则抛异常
LoginUserVO user = SecurityUtils.requireLoginUser();
Long userId = SecurityUtils.requireUserId();

6.3 权限校验(失败抛异常)

SecurityUtils.checkPermission("user:add");                    // 单权限
SecurityUtils.checkPermission("user:add", "user:edit");       // 必须同时有
SecurityUtils.checkAnyPermission("user:add", "user:edit");    // 有任一即可
SecurityUtils.checkRole("admin");                             // 单角色
SecurityUtils.checkAnyRole("admin", "manager");               // 有任一即可

6.4 权限判断(返回 boolean)

boolean has = SecurityUtils.hasPermission("user:add");
boolean hasAny = SecurityUtils.hasAnyPermission("user:add", "user:edit");
boolean isAdmin = SecurityUtils.isAdmin();
boolean isSuperAdmin = SecurityUtils.isSuperAdmin();
boolean isLoggedIn = SecurityUtils.isAuthenticated();

6.5 数据级权限

// 只允许本人或管理员
SecurityUtils.checkSelfOrAdmin(userId);

// 只允许资源所有者
SecurityUtils.checkOwner(ownerId);

// 判断(不抛异常)
boolean can = SecurityUtils.isSelfOrAdmin(userId);

6.6 认证状态

SecurityUtils.requireAuthenticated();      // 必须已登录
SecurityUtils.requireNotAuthenticated();   // 必须未登录(用于登录接口)

七、配置管理

7.1 配置层级

优先级从低到高:
application.yml → application-{profile}.yml → .env.{profile}

7.2 环境变量映射规则

YAML 配置          →  环境变量
app.name           →  APP_NAME
app.jwt.secret     →  APP_JWT_SECRET
app.file.max-size  →  APP_FILE_MAX_SIZE

7.3 注入 AppProperties

方式 代码 适用场景
构造器注入(推荐) public MyService(AppProperties appProperties) Service、Controller
字段注入 @Autowired private AppProperties appProperties; 简单场景
方法参数 public void method(AppProperties config) 特殊场景
// ✅ 推荐:构造器注入
@Service
public class AuthService {
    private final AppProperties appProperties;

    public AuthService(AppProperties appProperties) {
        this.appProperties = appProperties;
    }
}

7.4 配置项速查表

获取方式 返回类型 说明
appProperties.getName() String 应用名称
appProperties.getVersion() String 应用版本
appProperties.isDebug() boolean 是否调试模式
appProperties.isProduction() boolean 是否生产环境
appProperties.isDevelopment() boolean 是否开发环境

JWT 配置 appProperties.getJwt()

方法 返回类型 说明
.getSecret() String JWT 密钥
.getExpiration() long 过期时间(秒)
.getExpirationMs() long 过期时间(毫秒)
.getTokenPrefix() String Token 前缀,默认 Bearer
.getHeaderName() String Header 名称,默认 Authorization

安全配置 appProperties.getSecurity()

方法 返回类型 说明
.getPasswordSalt() String 密码静态盐值
.getBcryptStrength() int BCrypt 强度(4-31)

文件配置 appProperties.getFile()

方法 返回类型 说明
.getMaxSize() long 最大文件大小(字节)
.getMaxSizeMB() long 最大文件大小(MB)
.getAllowedFormats() String 允许格式(逗号分隔)
.getAllowedFormatArray() String[] 允许格式(数组)
.getUploadPath() String 上传路径

用户配置 appProperties.getUser()

方法 返回类型 说明
.getMaxPasswordRetry() int 密码最大重试次数
.getMaxLoginDevice() int 最大同时登录设备数
.getLockMinutes() int 账户锁定时间(分钟)

CORS 配置 appProperties.getCors()

方法 返回类型 说明
.getAllowedOrigins() String 允许的源(逗号分隔)
.getAllowedOriginsArray() String[] 允许的源(数组)

7.5 使用示例

// JWT 相关
String secret = appProperties.getJwt().getSecret();
long expirationMs = appProperties.getJwt().getExpirationMs();

// 文件校验
long maxSize = appProperties.getFile().getMaxSize();
if (file.getSize() > maxSize) {
    throw new BusinessException("文件不能超过 " + appProperties.getFile().getMaxSizeMB() + "MB");
}

// 环境判断
if (appProperties.isProduction()) {
    // 生产环境逻辑
}

// 密码加密
String salt = appProperties.getSecurity().getPasswordSalt();
int strength = appProperties.getSecurity().getBcryptStrength();

// CORS 配置
String[] origins = appProperties.getCors().getAllowedOriginsArray();

7.6 文件清单

文件 用途 是否提交 Git
application.yml 主配置,所有默认值
application-dev.yml 开发环境覆盖
application-prod.yml 生产环境覆盖
.env.example 环境变量模板
.env.dev 开发环境变量
.env.prod 生产环境变量

八、API文档规范

8.1 Controller 注解

@RestController
@RequestMapping("/user")
@Tag(name = "用户管理", description = "用户相关接口")
public class UserController {

    @GetMapping("/{id}")
    @Operation(summary = "获取用户详情", description = "根据ID获取用户信息")
    public Result<UserVO> getById(
            @Parameter(description = "用户ID") @PathVariable Long id) {
        // ...
    }
}

8.2 DTO/VO 注解

@Data
@Schema(description = "用户注册请求")
public class UserRegisterDTO {

    @Schema(description = "用户名", example = "zhangsan")
    @NotBlank(message = "用户名不能为空")
    private String username;

    @Schema(description = "密码", example = "123456")
    @NotBlank(message = "密码不能为空")
    private String password;
}

8.3 生产环境禁用

knife4j:
  enable: ${KNIFE4J_ENABLE:false}

九、快速检查清单

开发前检查

  • 是否定义了合适的 ErrorCode
  • DTO 是否添加了校验注解
  • 枚举是否包含 @EnumValue@JsonValuefromCode()

开发中检查

  • Controller 是否只做参数接收和结果返回
  • Service 是否添加了 @Transactional(写操作)
  • 是否使用 ThrowUtils 进行参数校验
  • 是否进行了数据级权限校验

提交前检查

  • 敏感配置是否在 .env 文件中
  • .env 文件是否在 .gitignore
  • API 文档注解是否完整
  • 生产环境是否禁用了 Knife4j

十、启动与部署

10.1 环境文件说明

文件 用途 提交 Git
.env.example 环境变量模板,包含所有配置项
.env.dev 开发环境配置
.env.prod 生产服务器配置
.env.prod.local 本地模拟生产环境(连接测试库等)

首次使用:复制模板并填写配置

cp .env.example .env.dev
cp .env.example .env.prod
cp .env.example .env.prod.local

10.2 本地开发启动

Windows(推荐使用启动脚本)

项目根目录/
├── start-dev.bat       # 开发环境启动
└── start-prod.bat      # 本地模拟生产环境启动

双击运行 start-dev.bat 或在命令行:

.\start-dev.bat

IDEA 启动配置

  1. Edit ConfigurationsAdd NewSpring Boot
  2. 配置如下:
配置项
Main class com.zwnsyw.zwwwspringbootbasetemplate.ZwwwSpringBootBaseTemplateApplication
Active profiles dev
Environment variables .env.dev 复制,或使用 EnvFile 插件

手动设置环境变量(IDEA):

APP_JWT_SECRET=your-secret-key;APP_SECURITY_PASSWORD_SALT=your-salt;DB_PASSWORD=xxx

使用 EnvFile 插件(推荐):

  1. 安装插件:File → Settings → Plugins → 搜索 "EnvFile"
  2. Run Configuration → EnvFile 标签 → 勾选 Enable → 添加 .env.dev

Maven 命令启动

# Windows - 需要先手动加载环境变量,或使用脚本
mvn spring-boot:run -Dspring-boot.run.profiles=dev

# Linux/Mac
export $(cat .env.dev | grep -v '^#' | xargs) && mvn spring-boot:run -Dspring-boot.run.profiles=dev

10.3 服务器部署

10.3.1 打包

# 跳过测试打包
mvn clean package -DskipTests

# 生成文件
target/zwww-springboot-base-template-0.0.1-SNAPSHOT.jar

10.3.2 上传部署文件

# 上传到服务器
scp target/*.jar user@server:/opt/app/
scp .env.prod user@server:/opt/app/.env
scp deploy.sh user@server:/opt/app/

10.3.3 服务器目录结构

/opt/app/
├── zwww-springboot-base-template.jar    # 应用 jar
├── .env                                  # 环境变量文件
├── deploy.sh                             # 部署脚本
├── logs/                                 # 日志目录
│   ├── app.log                          # 应用日志
│   └── error.log                        # 错误日志
└── backup/                              # 备份目录

10.3.4 部署脚本 deploy.sh

#!/bin/bash

# ============================================
#  Spring Boot 应用部署脚本
#  用法: ./deploy.sh [start|stop|restart|status]
# ============================================

APP_NAME="zwww-springboot-base-template"
APP_JAR="${APP_NAME}.jar"
APP_DIR="/opt/app"
LOG_DIR="${APP_DIR}/logs"
ENV_FILE="${APP_DIR}/.env"
PID_FILE="${APP_DIR}/${APP_NAME}.pid"

# JVM 参数
JAVA_OPTS="-Xms512m -Xmx1024m -XX:+UseG1GC"

# 确保日志目录存在
mkdir -p ${LOG_DIR}

# 加载环境变量
load_env() {
    if [ -f "${ENV_FILE}" ]; then
        echo "加载环境变量: ${ENV_FILE}"
        export $(cat ${ENV_FILE} | grep -v '^#' | grep -v '^$' | xargs)
    else
        echo "[错误] 环境变量文件不存在: ${ENV_FILE}"
        exit 1
    fi
}

# 获取 PID
get_pid() {
    if [ -f "${PID_FILE}" ]; then
        cat ${PID_FILE}
    else
        echo ""
    fi
}

# 检查是否运行中
is_running() {
    local pid=$(get_pid)
    if [ -n "${pid}" ] && ps -p ${pid} > /dev/null 2>&1; then
        return 0
    else
        return 1
    fi
}

# 启动
start() {
    if is_running; then
        echo "[警告] ${APP_NAME} 已在运行中 (PID: $(get_pid))"
        return 1
    fi

    load_env

    echo "启动 ${APP_NAME}..."
    cd ${APP_DIR}

    nohup java ${JAVA_OPTS} \
        -Dspring.profiles.active=prod \
        -jar ${APP_JAR} \
        > ${LOG_DIR}/app.log 2>&1 &

    echo $! > ${PID_FILE}

    sleep 3

    if is_running; then
        echo "[成功] ${APP_NAME} 已启动 (PID: $(get_pid))"
    else
        echo "[错误] ${APP_NAME} 启动失败,请检查日志"
        cat ${LOG_DIR}/app.log | tail -50
        return 1
    fi
}

# 停止
stop() {
    if ! is_running; then
        echo "[信息] ${APP_NAME} 未运行"
        return 0
    fi

    local pid=$(get_pid)
    echo "停止 ${APP_NAME} (PID: ${pid})..."

    kill ${pid}

    # 等待进程结束(最多30秒)
    local count=0
    while is_running && [ ${count} -lt 30 ]; do
        sleep 1
        count=$((count + 1))
        echo -n "."
    done
    echo ""

    if is_running; then
        echo "[警告] 进程未响应,强制终止..."
        kill -9 ${pid}
    fi

    rm -f ${PID_FILE}
    echo "[成功] ${APP_NAME} 已停止"
}

# 重启
restart() {
    stop
    sleep 2
    start
}

# 状态
status() {
    if is_running; then
        echo "[运行中] ${APP_NAME} (PID: $(get_pid))"
    else
        echo "[已停止] ${APP_NAME}"
    fi
}

# 查看日志
logs() {
    tail -f ${LOG_DIR}/app.log
}

# 主入口
case "$1" in
    start)
        start
        ;;
    stop)
        stop
        ;;
    restart)
        restart
        ;;
    status)
        status
        ;;
    logs)
        logs
        ;;
    *)
        echo "用法: $0 {start|stop|restart|status|logs}"
        exit 1
        ;;
esac

10.3.5 部署命令速查

操作 命令
启动 ./deploy.sh start
停止 ./deploy.sh stop
重启 ./deploy.sh restart
状态 ./deploy.sh status
查看日志 ./deploy.sh logs
实时日志 tail -f /opt/app/logs/app.log

10.4 Docker 部署

Dockerfile

FROM openjdk:17-jdk-slim

WORKDIR /app

COPY target/*.jar app.jar

EXPOSE 8080

ENTRYPOINT ["java", "-jar", "app.jar", "--spring.profiles.active=prod"]

构建与运行

# 构建镜像
docker build -t zwww-app:latest .

# 运行容器
docker run -d \
  --name zwww-app \
  --env-file .env.prod \
  -p 8080:8080 \
  zwww-app:latest

# 查看日志
docker logs -f zwww-app

10.5 常见问题

问题 原因 解决方案
环境变量未生效 未正确加载 .env 文件 使用启动脚本或 EnvFile 插件
端口被占用 8080 端口已使用 `netstat -ano
启动后立即退出 配置错误 查看日志 logs/app.log
数据库连接失败 配置或网络问题 检查 DB_HOST、DB_PORT、防火墙

附录:项目结构

src/main/java/com/zwnsyw/zwwwspringbootbasetemplate/
├── ZwwwSpringBootBaseTemplateApplication.java  # 启动类
│
├── config/                          # 配置类
│   ├── AppProperties.java           # 应用配置属性
│   ├── MyBatisPlusConfig.java       # MyBatis-Plus 配置
│   ├── Knife4jConfig.java           # Knife4j API文档配置
│   └── CorsConfig.java              # 跨域配置
│
├── controller/                      # 控制器层
│   ├── UserController.java
│   └── ConfigController.java
│
├── service/                         # 服务层接口
│   └── UserService.java
│
├── service/impl/                    # 服务层实现
│   └── UserServiceImpl.java
│
├── mapper/                          # MyBatis Mapper 接口
│   └── UserMapper.java
│
├── model/                           # 模型层
│   ├── entity/                      # 数据库实体
│   │   └── User.java
│   ├── dto/                         # 数据传输对象(请求)
│   │   ├── UserDTO.java
│   │   └── user/
│   │       ├── UserRegisterDTO.java
│   │       ├── UserLoginDTO.java
│   │       └── UserUpdateDTO.java
│   ├── vo/                          # 视图对象(响应)
│   │   └── UserVO.java
│   ├── query/                       # 查询对象
│   │   └── UserQueryDTO.java
│   └── enums/                       # 枚举类
│       ├── GenderEnum.java
│       ├── UserStatusEnum.java
│       └── UserRoleEnum.java
│
├── common/                          # 公共模块
│   ├── BaseResponse.java            # 统一响应体
│   ├── ErrorCode.java               # 错误码枚举
│   ├── ResultUtils.java             # 响应工具类
│   └── PageResult.java              # 分页结果
│
├── exception/                       # 异常处理
│   ├── BusinessException.java       # 业务异常
│   └── GlobalExceptionHandler.java  # 全局异常处理器
│
├── security/                        # 安全模块
│   ├── annotation/                  # 注解定义
│   │   ├── Anonymous.java           # 匿名访问
│   │   ├── RequiresPermission.java  # 权限校验
│   │   └── RequiresRole.java        # 角色校验
│   ├── config/                      # 安全配置
│   │   ├── SecurityConfig.java      # 安全配置
│   │   └── AnonymousUrlConfig.java  # 匿名URL配置
│   ├── context/                     # 安全上下文
│   │   └── SecurityContext.java     # 当前用户上下文
│   ├── enums/                       # 枚举
│   │   └── Logical.java             # 逻辑符枚举
│   ├── handler/                     # 权限处理器
│   │   └── PermissionHandler.java   # 权限校验逻辑
│   ├── interceptor/                 # 拦截器
│   │   └── AuthorizationInterceptor.java  # 鉴权拦截器
│   └── utils/                       # 安全工具类
│       └── SecurityUtils.java
│
├── utils/                           # 工具类
│   └── PasswordUtils.java           # 密码工具类
│
└── validation/                      # 自定义校验器
    └── groups/
        └── UpdateGroup.java         # 更新分组

src/main/resources/
├── application.yml                  # 主配置文件
├── application-dev.yml              # 开发环境配置
├── application-prod.yml             # 生产环境配置
└── mapper/                          # MyBatis XML 映射文件
    └── UserMapper.xml

db/
└── init.sql                         # 数据库初始化脚本

# 环境变量文件与启动脚本(根目录)
项目根目录/
├── start-dev.bat                       # 开发环境启动
├── start-prod.bat                      # 本地模拟生产环境启动
├── .env.example                        # 环境变量模板(提交Git)
├── .env.dev                            # 开发环境变量(不提交)
├── .env.prod                           # 生产环境变量(不提交)
└── .env.prod.local                     # 本地模拟生产环境(不提交)

项目分区导航:⬅️ 00-最佳实践 | 01-Spring Boot MVC 项目最佳实践规范 | ➡️ 02-SpringBoot MVC 分层最佳实践