外观
后端快速入门
2026-04-01
本文以新建一个业务微服务为目标,从零搭建标准的 Nebula 多模块 Gradle 项目,完成首个 CRUD 接口。
全程约 30 分钟,读完即可独立开发新服务。
目录
一、环境准备
| 工具 | 版本要求 | 说明 |
|---|---|---|
| JDK | 17+ | 推荐 Eclipse Temurin 17 |
| Gradle | 8.12+ | 通过 Wrapper 自动下载,无需手动安装 |
| IntelliJ IDEA | Ultimate 2024.1+ | 必须安装 Lombok 插件 |
| MySQL | 8.0+ | 本地开发库 |
| Redis | 6.0+ | 本地开发缓存 |
确认网络可以访问内网 Nexus 私服:http://10.10.1.130:8081(VPN 或内网环境)。
二、创建多模块项目结构
Nebula 业务服务统一采用 四模块结构,以 my-service 为例:
my-service/
├── settings.gradle # 声明项目名和所有子模块
├── build.gradle # 根配置(插件、通用依赖)
├── gradle.properties # 版本变量、私服地址
├── gradle/wrapper/
│ └── gradle-wrapper.properties # 固定 Gradle 版本
│
├── my-service-api/ # 对外契约(RPC 接口、DTO、常量)
│ └── build.gradle
│
├── my-service-core/ # 业务实现(Controller/Service/Mapper/Entity)
│ └── build.gradle
│
├── my-service-server/ # 可运行服务(启动类、配置文件、Flyway 脚本)
│ └── build.gradle
│
└── my-service-starter/ # Spring Boot 自动装配入口
└── build.gradle在 IDEA 中创建:File → New → Project → Gradle,项目名填 my-service,JDK 选 17,然后手动创建四个子目录,每个目录内新建空的 build.gradle。
三、配置 Gradle 构建
3.1 gradle/wrapper/gradle-wrapper.properties
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip3.2 gradle.properties
# ==================== 项目版本 ====================
projectVersion=0.1.0-SNAPSHOT
# ==================== Nebula 框架版本 ====================
nebulaBootVersion=0.1.18-SNAPSHOT
nebulaAuthVersion=0.1.6-SNAPSHOT
# ==================== 私服地址(内网 Nexus) ====================
nebula.private.url=http://10.10.1.130:8081/repository/nebula-public/
# ==================== Gradle 构建优化 ====================
org.gradle.parallel=true
org.gradle.caching=true
org.gradle.daemon=true
org.gradle.jvmargs=-Xmx2g -XX:+HeapDumpOnOutOfMemoryError私服账号密码通过环境变量注入(
NEXUS_USERNAME/NEXUS_PASSWORD),或在~/.gradle/gradle.properties中本地配置,不要提交到代码仓库。
3.3 settings.gradle
rootProject.name = 'my-service'
include 'my-service-api'
include 'my-service-core'
include 'my-service-server'
include 'my-service-starter'3.4 根 build.gradle
buildscript {
repositories {
mavenLocal()
maven {
url "http://10.10.1.130:8081/repository/nebula-public/"
allowInsecureProtocol = true
credentials {
username = findProperty('nebula.publish.username') ?: System.getenv('NEXUS_USERNAME') ?: ''
password = findProperty('nebula.publish.password') ?: System.getenv('NEXUS_PASSWORD') ?: ''
}
}
maven { url "https://maven.aliyun.com/repository/public/" }
}
dependencies {
classpath "com.huida.nebula.support:nebula-boot-plugin-gradle:${nebulaBootVersion}"
}
}
allprojects {
group = 'com.huida.your-team'
version projectVersion
// 应用 Nebula 标准化构建插件(自动配置仓库、BOM、发布等)
apply plugin: 'com.huida.nebula.plugin.boot'
nebula {
nebulaVersion = nebulaBootVersion
enableDependencyManagement = true
enableRepositoryConfiguration = true
enablePrivateRepo = true
}
// 引入 Nebula Boot BOM,所有 Starter 无需写版本号
dependencyManagement {
imports {
mavenBom "com.huida.nebula.boot:nebula-boot-dependencies:${nebulaBootVersion}"
}
}
// HTTP 仓库兼容处理
repositories.all { repo ->
try {
if (repo.url?.toString()?.startsWith('http://')) {
repo.setAllowInsecureProtocol(true)
}
} catch (ignored) {}
}
}3.5 my-service-api/build.gradle
dependencies {
// 框架核心(R<T>、异常体系、分页模型等)
api "com.huida.nebula:nebula-framework-core:${nebulaBootVersion}"
// Feign 接口注解(仅编译时,运行时由调用方提供)
compileOnly 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.springframework.cloud:spring-cloud-starter-openfeign'
compileOnly 'org.springframework.boot:spring-boot-starter-validation'
}3.6 my-service-core/build.gradle
dependencies {
// API 模块(含 DTO 和 Feign 接口)
api project(':my-service-api')
// Web 层(Controller 注解、全局异常、统一响应)
api "com.huida.nebula.boot:nebula-boot-starter-web:${nebulaBootVersion}"
// 持久层(MyBatis-Plus + BaseEntity + 自动填充)
api "com.huida.nebula.boot:nebula-boot-starter-mybatis:${nebulaBootVersion}"
// Redis(NebulaCacheHelper + NebulaLockHelper)
implementation "com.huida.nebula.boot:nebula-boot-starter-redis:${nebulaBootVersion}"
// 多租户(TenantContext + SQL 自动过滤)
api "com.huida.nebula.boot:nebula-boot-starter-tenant:${nebulaBootVersion}"
// 参数校验
api 'org.springframework.boot:spring-boot-starter-validation'
// Swagger 文档
api "com.huida.nebula.boot:nebula-boot-starter-swagger:${nebulaBootVersion}"
}3.7 my-service-starter/build.gradle
dependencies {
api project(':my-service-core')
}3.8 my-service-server/build.gradle
apply plugin: 'org.springframework.boot'
bootJar {
enabled = true
archiveFileName = "my-service-server.jar"
}
jar { enabled = false }
dependencies {
// Starter 间接引入所有业务逻辑
implementation project(':my-service-starter')
// 链路追踪(HTTP/定时任务/MQ 自动注入 TraceId)
implementation "com.huida.nebula.boot:nebula-boot-starter-trace:${nebulaBootVersion}"
// 数据库驱动
runtimeOnly 'com.mysql:mysql-connector-j'
}四、编写第一个接口
以「示例管理」为例,演示完整的 CRUD 流程。
4.1 建表 Flyway 脚本
在 my-service-server/src/main/resources/db/migration/ 下创建:
V1.0.0__init_t_demo.sql
CREATE TABLE IF NOT EXISTS `t_demo`
(
-- ========== 公共字段(BaseEntity 11 字段,顺序固定) ==========
`id` BIGINT NOT NULL COMMENT '主键(雪花算法)',
`tenant_id` BIGINT DEFAULT NULL COMMENT '租户ID',
`create_user_id` BIGINT DEFAULT NULL COMMENT '创建人ID',
`creator` VARCHAR(128) DEFAULT NULL COMMENT '创建人用户名',
`create_time` DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '创建时间',
`modify_user_id` BIGINT DEFAULT NULL COMMENT '最后修改人ID',
`updater` VARCHAR(128) DEFAULT NULL COMMENT '最后修改人用户名',
`modify_time` DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6) COMMENT '最后修改时间',
`delete_flag` INT NOT NULL DEFAULT 0 COMMENT '逻辑删除(0:未删除 1:已删除)',
`remark` VARCHAR(500) DEFAULT NULL COMMENT '备注',
`custom_fields` TEXT DEFAULT NULL COMMENT '自定义扩展字段(JSON)',
-- ========== 业务字段 ==========
`demo_name` VARCHAR(100) NOT NULL COMMENT '示例名称',
`demo_code` VARCHAR(50) NOT NULL COMMENT '示例编码(租户内唯一)',
`status` INT NOT NULL DEFAULT 0 COMMENT '状态(0:正常 1:禁用)',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_tenant_demo_code` (`tenant_id`, `demo_code`),
KEY `idx_status` (`status`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT ='示例表';重要:
remark和custom_fields是高频遗漏字段,务必包含。
4.2 实体类(DemoDO)
// my-service-core/src/main/java/com/huida/myservice/demo/entity/DemoDO.java
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("t_demo")
@Schema(description = "示例实体")
public class DemoDO extends BaseEntity {
@Schema(description = "示例名称")
private String demoName;
@Schema(description = "示例编码")
private String demoCode;
@Schema(description = "状态(0:正常 1:禁用)")
private Integer status;
}4.3 Mapper
@Mapper
public interface DemoMapper extends BaseMapper<DemoDO> { }@Repository
@RequiredArgsConstructor
public class DemoMapperExt {
private final DemoMapper demoMapper;
public PageResult<DemoDO> pageQuery(DemoPageParamVO query) {
LambdaQueryWrapper<DemoDO> wrapper = new LambdaQueryWrapper<DemoDO>()
.like(StringUtils.hasText(query.getDemoName()), DemoDO::getDemoName, query.getDemoName())
.eq(query.getStatus() != null, DemoDO::getStatus, query.getStatus())
.orderByDesc(DemoDO::getCreateTime);
Long count = demoMapper.selectCount(wrapper);
if (count == null || count == 0) return PageResult.empty(query);
Page<DemoDO> page = new Page<>(query.getPageNo(), query.getPageSize(), false);
return PageResult.of(demoMapper.selectPage(page, wrapper).getRecords(), count, query);
}
}4.4 VO 类
// 分页入参
@Data
@EqualsAndHashCode(callSuper = true)
public class DemoPageParamVO extends PageParam {
private String demoName;
private Integer status;
}
// 新增/修改入参
@Data
public class DemoSaveParamVO implements Serializable {
public interface UpdateGroup {}
@NotNull(groups = UpdateGroup.class, message = "修改时 id 不能为空")
private Long id;
@NotBlank(message = "名称不能为空")
@Schema(description = "示例名称", requiredMode = Schema.RequiredMode.REQUIRED)
private String demoName;
@NotBlank(message = "编码不能为空")
private String demoCode;
public boolean isNew() { return id == null; }
}
// 列表响应
@Data
public class DemoRespVO implements Serializable {
private Long id;
private String demoName;
private String demoCode;
private Integer status;
private String creator;
private LocalDateTime createTime;
private LocalDateTime modifyTime;
}4.5 MapStruct 转换
@Mapper(config = BaseMapperConfig.class)
public interface DemoConvert {
DemoConvert INSTANCE = Mappers.getMapper(DemoConvert.class);
DemoRespVO doToRespVO(DemoDO demoDO);
DemoDO saveParamToDo(DemoSaveParamVO saveParam);
void saveParamMergeToDo(DemoSaveParamVO saveParam, @MappingTarget DemoDO demoDO);
}4.6 Service
public interface IDemoService extends BaseService<DemoDO> {
PageResult<DemoRespVO> pageDemo(DemoPageParamVO query);
Long createDemo(DemoSaveParamVO saveParam);
void updateDemo(DemoSaveParamVO saveParam);
void deleteDemo(List<Long> ids);
}@Slf4j
@Service
@RequiredArgsConstructor
public class DemoServiceImpl extends BaseServiceImpl<DemoMapper, DemoDO>
implements IDemoService {
private final DemoMapperExt demoMapperExt;
@Override
public PageResult<DemoRespVO> pageDemo(DemoPageParamVO query) {
return demoMapperExt.pageQuery(query).convert(DemoConvert.INSTANCE::doToRespVO);
}
@Override
@Transactional(rollbackFor = Exception.class)
public Long createDemo(DemoSaveParamVO saveParam) {
DemoDO demoDO = DemoConvert.INSTANCE.saveParamToDo(saveParam);
save(demoDO);
log.info("新增示例成功,id={}", demoDO.getId());
return demoDO.getId();
}
@Override
@Transactional(rollbackFor = Exception.class)
public void updateDemo(DemoSaveParamVO saveParam) {
DemoDO demoDO = getByIdOrThrow(saveParam.getId());
DemoConvert.INSTANCE.saveParamMergeToDo(saveParam, demoDO);
updateById(demoDO);
log.info("修改示例成功,id={}", saveParam.getId());
}
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteDemo(List<Long> ids) {
if (ids == null || ids.isEmpty()) return;
removeByIds(ids);
log.info("删除示例成功,ids={}", ids);
}
}4.7 Controller
@Tag(name = "示例管理")
@Validated
@RestController
@RequestMapping("/my/demo")
@RequiredArgsConstructor
public class DemoController {
private final IDemoService demoService;
@Operation(summary = "分页查询")
@PostMapping("/page")
public R<PageResult<DemoRespVO>> pageDemo(@RequestBody DemoPageParamVO query) {
return R.ok(demoService.pageDemo(query));
}
@Operation(summary = "新增")
@PostMapping
public R<Long> createDemo(@Valid @RequestBody DemoSaveParamVO saveParam) {
return R.ok(demoService.createDemo(saveParam));
}
@Operation(summary = "修改")
@PutMapping
public R<Void> updateDemo(
@Validated(DemoSaveParamVO.UpdateGroup.class) @RequestBody DemoSaveParamVO saveParam) {
demoService.updateDemo(saveParam);
return R.ok();
}
@Operation(summary = "批量删除")
@DeleteMapping
public R<Void> deleteDemo(@RequestBody List<Long> ids) {
demoService.deleteDemo(ids);
return R.ok();
}
}4.8 启动类
// my-service-server/src/main/java/com/huida/myservice/MyServiceApplication.java
@SpringBootApplication
public class MyServiceApplication {
public static void main(String[] args) {
SpringApplication.run(MyServiceApplication.class, args);
}
}五、配置数据库与启动服务
在 my-service-server/src/main/resources/config/ 目录下创建两个配置文件:
application.yaml(主配置,不含环境差异)
spring:
application:
name: my-service
profiles:
active: dev
nebula:
security:
jwt:
algorithm: HMAC
secret: my-service-dev-secret-please-change-me-32c
flyway:
enabled: true
swagger:
title: "My Service API"
version: "1.0.0"
knife4j:
enable: trueapplication-dev.yaml(开发环境配置)
logging:
config: classpath:log4j/log4j2-dev.xml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/my_service_dev?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
username: root
password: your_password
data:
redis:
host: localhost
port: 6379
database: 0
nebula:
tenant:
enabled: true
isolation-mode: FIELD启动: 在 IDEA 中运行 MyServiceApplication,设置 Active profiles: dev。
六、验证接口
服务启动后,访问 Swagger 文档:
http://localhost:8080/doc.html测试新增接口:
curl -X POST http://localhost:8080/my/demo \
-H "Content-Type: application/json" \
-d '{"demoName": "测试示例", "demoCode": "DEMO001"}'预期响应:
{
"code": 200,
"message": "操作成功",
"traceId": "2036344300517855232",
"data": 1001
}下一步
- 阅读 开发规范总览,了解命名规范、分层原则和禁止事项
- 阅读 完整 CRUD 代码示例,获取可直接复制的完整模板
- 阅读 nebula-boot 使用指南,了解各 Starter 的详细用法
