从零到部署:Spring Boot 入门实战,60分钟快速上手
嘿,大家好,我是老张,在一家中型互联网公司做后端开发。最近刚接手了一个小项目,需要用 Spring Boot 快速搭建一个服务来支撑前端的 API 需求。说实话,虽然以前也用过几次 Spring Boot,但都只是简单玩一玩,没有真正用于生产。这次正好是一个很好的练手机会,也让我重新系统地捋了一遍 Spring Boot 的核心使用方式。
这篇文章写给那些准备入门 Spring Boot,或者想要在短时间内上手实战的同学。我会结合我在这次项目中的真实经历,带大家完成从初始化到部署上线的一整套流程。整个过程控制在 60 分钟左右,希望对你也有帮助。
项目背景和需求说明

我们公司的产品部门最近需要快速上线一个内部工具系统,主要用于记录客服人员的工作日志,并提供基础的数据查询与导出功能。时间紧迫,两周内必须交付。作为技术负责人,我需要在最短的时间内完成这个项目的后端架构设计、接口实现以及部署上线。
技术选型方面,由于团队对 Java 比较熟悉,再加上这种轻量级的小项目非常适合用 Spring Boot 来快速构建,所以我们最终决定采用 Spring Boot + MySQL 的方案。
主要的功能点包括:
- 用户登录(基于 RBAC 模型)
- 工作日志增删改查
- 数据统计图表展示(简单聚合)
- Excel 导出日志功能
整体来看,功能不算复杂,但需要快速搭建、便于扩展、具备一定的性能表现。
技术挑战与初步规划

在接到这个项目的时候,我脑子里第一反应就是:“时间紧任务重,得把事情做简洁高效”。那我们需要解决的问题有以下几点:
- 快速搭建开发环境:不能浪费太多时间在配置上。
- 数据库结构设计:合理建模,避免后期修改带来的麻烦。
- 权限管理机制:如何快速集成用户认证?
- 代码组织清晰度:虽然时间紧,但也不能乱写一通,否则后续维护成本太高。
为了解决这些问题,我做了如下决策:
- 使用 Spring Initializr 快速生成项目骨架
- 基于 Spring Security 实现简单的 JWT 认证
- 使用 JPA 简化数据访问层开发
- 统一接口响应格式,提高前后端协作效率
- 使用 H2 内存数据库进行本地调试,线上切换 MySQL
- 结合 Swagger2 生成 API 文档
- 使用 Docker 容器化部署,提升上线效率
有了思路之后,就开始动手实践了。
开始动手:创建并启动 Spring Boot 项目

首先,我打开浏览器访问 https://start.spring.io/,这是 Spring 官方提供的项目生成器。
选择配置如下:
- Project: Maven
- Language: Java
- Spring Boot Version: 2.7.x (当前公司统一版本)
- Group: com.example
- Artifact: worklog-service
- Name: worklog-service
- Description: A simple work log service built with Spring Boot.
- Packaging: Jar
- Java Version: 11
依赖项选择这几个就够用了:
- Spring Web
- Spring Data JPA
- H2 Database
- Spring Security
- Lombok
点击 Generate 下载生成好的压缩包,解压后导入 IDEA。
项目目录结构
导入 IDE 后,你会看到一个典型的 Spring Boot 目录结构:
src/
├── main/
│ ├── java/
│ │ └── com.example.worklogservice/
│ │ ├── controller/
│ │ ├── model/
│ │ ├── repository/
│ │ ├── service/
│ │ ├── config/
│ │ └── WorklogServiceApplication.java
│ ├── resources/
│ ├── application.properties
│ └── data.sql (测试数据)
接下来,我们逐步填充各个模块的内容。
数据库设计与实体映射


因为我们的项目涉及工作日志的记录,所以我先设计了几个关键表结构:
CREATE TABLE IF NOT EXISTS `user` (
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
`username` VARCHAR(50) NOT NULL UNIQUE,
`password` VARCHAR(100) NOT NULL,
`role` VARCHAR(20) NOT NULL DEFAULT 'USER'
);
CREATE TABLE IF NOT EXISTS `work_log` (
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
`user_id` BIGINT NOT NULL,
`content` TEXT NOT NULL,
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (`user_id`) REFERENCES `user`(`id`)
);
对应的实体类很简单,这里以 WorkLog 为例:
@Entity
@Data
public class WorkLog {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String content;
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "created_at", updatable = false)
private Date createdAt;
@ManyToOne
@OnDelete(name = "FK_WORK_LOG_USER")
@JoinColumn(name = "user_id")
private User user;
}
通过 JPA 注解,我们将 POJO 对象和数据库表建立起了联系。
接口设计与控制器编写

为了方便前后端协作,我约定所有返回值统一包装成如下的结构体:
public class ApiResponse<T> {
private boolean success;
private int code;
private String message;
private T data;
// 构造方法...
}
控制器示例如下:
@RestController
@RequestMapping("/api/logs")
@RequiredArgsConstructor
public class LogController {
private final WorkLogService workLogService;
@GetMapping
public ApiResponse<List<WorkLog>> getAllLogs() {
return ApiResponse.success(workLogService.findAll());
}
@PostMapping
public ApiResponse<WorkLog> createLog(@RequestBody CreateLogRequest request) {
return ApiResponse.success(workLogService.create(request));
}
}
这样既保证了接口的统一性,也让前端更容易处理异常情况。
用户认证机制:JWT 登录支持
虽然本次是内部系统,但为了安全性考虑,还是需要引入基本的认证机制。我们采用了 Spring Security + JWT 的方案。
核心配置步骤如下:
- 引入 JWT 依赖(Maven):
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
</dependency>
- 自定义 UserDetails 服务:
@Service
public class UserService implements UserDetailsService {
private final UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found."));
return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), getAuthorities(user.getRole()));
}
private Collection<? extends GrantedAuthority> getAuthorities(String role) {
return List.of(new SimpleGrantedAuthority("ROLE_" + role));
}
}
- 编写 JWT 工具类生成和解析 token:
@Component
public class JwtUtils {
private String jwtSecret = "secret_key";
private int jwtExpirationMs = 86400000; // 24h
public String generateJwtToken(Authentication authentication) {
UserDetailsImpl userPrincipal = (UserDetailsImpl) authentication.getPrincipal();
return Jwts.builder()
.setSubject((userPrincipal.getUsername()))
.setIssuedAt(new Date())
.setExpiration(new Date((new Date()).getTime() + jwtExpirationMs))
.signWith(SignatureAlgorithm.HS512, jwtSecret)
.compact();
}
public String getUserNameFromJwtToken(String token) {
return Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token).getBody().getSubject();
}
public boolean validateJwtToken(String authToken) {
try {
Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(authToken);
return true;
} catch (JwtException e) {
// 日志记录
}
return false;
}
}
- 配置 Spring Security:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final JwtEntryPoint unauthorizedHandler;
private final JwtRequestFilter jwtRequestFilter;
public SecurityConfig(JwtEntryPoint unauthorizedHandler, JwtRequestFilter jwtRequestFilter) {
this.unauthorizedHandler = unauthorizedHandler;
this.jwtRequestFilter = jwtRequestFilter;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler)
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated();
http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
}
踩过的坑:那些让人头疼的“细节问题”
别看上面写得很顺利,实际上我在实际开发过程中踩了不少坑,特别是第一次把这些组件整合起来的时候。下面我挑几个比较有代表性的说一说。
✳️ 启动报错:“Error creating bean with name ‘entityManagerFactory’”
原因分析:Spring Boot 默认使用 HikariCP 连接池,但在某些情况下如果无法正确加载驱动,就会抛出这个错误。
解决方案:
- 确保在
application.properties中正确配置了数据库连接信息; - 如果是运行时使用 MySQL,注意添加合适的 driver 依赖;
- 使用
spring.jpa.hibernate.ddl-auto=update避免建表问题。
✳️ JWT Token 校验失败,提示签名不匹配
原因分析:生成 Token 的密钥和校验时的密钥不一致,可能是大小写或拼写错误导致。
解决方式:
- 将密钥抽取为常量统一管理;
- 添加日志输出验证密钥是否一致;
- 使用单元测试验证 token 生成和解析流程是否正确。
✳️ 时间字段总是插入 null?
问题描述:我的 createdAt 字段设置的是自动插入当前时间,但每次保存时都是 null。
根源在于:
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "created_at", updatable = false)
private Date createdAt;
这段注解其实是无效的,只有配合正确的数据库默认值才有用。而我们之前使用的是 H2 数据库,其默认行为并不一样。后来改为在代码逻辑里手动设置当前时间为推荐做法。
接口文档怎么搞?Swagger2 上线!
为了让前后端对接更顺畅,我还集成了 Swagger2 自动生成 API 文档,配置也很简单:
- 添加依赖:
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>3.0.0</version>
</dependency>
- 创建 Swagger 配置类:
@Configuration
@EnableOpenApi
public class SwaggerConfig {
@Bean
public Docket api() {
return new Docket(DocumentationType.OAS_30)
.select()
.apis(RequestHandlerSelectors.basePackage("com.example.worklogservice.controller"))
.paths(PathSelectors.any())
.build()
.apiInfo(apiInfo());
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("WorkLog Service API")
.description("Simple RESTful API for internal tools.")
.version("1.0.0")
.build();
}
}
访问 /swagger-ui/index.html 就能看到漂亮的文档界面啦 ✨
生产部署怎么做?Docker + Nginx 是个好组合
既然能跑起来了,那就要准备部署上线了。这里我选择了 Docker 容器化打包,配合 Nginx 做反向代理,部署非常简单。
📦 打包镜像
创建 Dockerfile:
FROM openjdk:11-jdk-slim
COPY target/worklog-service.jar /app.jar
ENTRYPOINT ["java", "-jar", "/app.jar"]
然后执行:
mvn clean package
docker build -t worklog-service .
docker run -d --name worklog -p 8080:8080 worklog-service
🌐 Nginx 反代配置
server {
listen 80;
server_name logs.example.com;
location / {
proxy_pass http://localhost:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
最后域名绑定一下,就完成了!✨
总结与心得:为什么选择 Spring Boot?
从零开始搭建一个完整的后端服务,60分钟真的可以做到。回顾整个过程,我觉得 Spring Boot 最大的优势是它的易用性和生态成熟度。你几乎可以不用自己造轮子,就能完成认证、数据访问、接口文档这些基础又关键的部分。
同时,它对于新手也非常友好,很多配置项都有合理的默认值,降低了学习门槛。
但我也想提醒一下大家:
- 不要盲目追求快速开发,一定要注重代码结构和可维护性;
- 即便使用了框架,也要理解底层原理,否则遇到 bug 时无从下手;
- 多利用现有的开源社区资源,比如 Spring Security、Flyway、Lombok 等等,可以节省大量时间;
- 初期不要过度抽象,保持代码简洁清晰最重要。
给初学者的建议
如果你是刚开始接触 Spring Boot,可以从以下几点入手:
- 掌握基础知识:Java 基础 + Maven + Git + RESTful API。
- 跟着官方教程走一遍,然后再尝试自己搭一个 Demo。
- 多看别人写的项目结构,学会组织自己的代码。
- 遇到问题多搜索、多提问,Stack Overflow 和 GitHub Issues 是宝藏。
- 持续迭代,慢慢积累经验。
最后,我想说的是,Spring Boot 真的不是一个难学的技术栈,关键是你愿意花时间去理解和实践。希望我这篇实战经验分享能够帮到正在努力学习的你。
加油吧,程序员兄弟姐妹们 💪!
如果你喜欢这类文章,欢迎关注我的 GitHub 或者掘金账号,我会持续更新更多实战干货 🧑💻🔥

评论 0