层级 | 技术选型 |
---|---|
前端模板引擎 | Thymeleaf、Bootstrap |
控制层 | Spring Boot Web MVC |
服务层 | Spring @Service + AOP |
DAO 层 | Spring Data JPA + Hibernate |
数据库 | MySQL(可选 PostgreSQL) |
安全模块 | Spring Security + Session/Cookie |
配置管理 | application.yml + profiles |
单元测试 | JUnit5 + Mockito |
集成测试 | MockMvc + TestContainers |
工具类型 | 建议 |
---|---|
IDE | IntelliJ IDEA Ultimate |
构建工具 | Maven |
版本控制 | Git + GitHub/GitLab |
数据库 | MySQL 8 + Navicat |
开发数据库 | dev_bookdb |
浏览器插件 | Postman / REST Client |
日志工具 | Logback + JSON Format |
使用 Spring Initializr 初始化:
com.example
bookmanager
用 Navicat 或 dbdiagram.io 画出 ER 图:
sql复制编辑CREATE TABLE users (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL UNIQUE,
password VARCHAR(100),
role ENUM('USER', 'ADMIN')
);
CREATE TABLE books (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
title VARCHAR(100),
author VARCHAR(100),
price DOUBLE
);
使用 JPA 实体类标注:
java复制编辑@Entity
public class Book {
@Id @GeneratedValue
private Long id;
private String title;
private String author;
private Double price;
}
javascript复制编辑Controller(Web控制层) ← 接收请求、返回视图或 JSON
↓
Service(业务逻辑层) ← 处理事务逻辑
↓
Repository(数据访问层) ← 连接数据库
↓
Entity(数据模型层) ← POJO、映射表
bash复制编辑com.example.bookmanager
├── controller # 控制器
├── service # 业务逻辑
├── repository # 数据库访问
├── entity # 数据模型
├── config # 配置类
├── security # 登录权限
└── BookManagerApplication.java
Book
、User
配合数据库映射使用 @Entity
、@Column
、@Id
注解
java复制编辑public interface BookRepository extends JpaRepository {
List findByTitleContaining(String keyword);
}
java复制编辑@Service
public class BookService {
@Autowired
private BookRepository repo;
public List listAll() {
return repo.findAll();
}
public void save(Book book) {
repo.save(book);
}
}
java复制编辑@Controller
public class BookController {
@Autowired
private BookService service;
@GetMapping("/")
public String viewHomePage(Model model) {
model.addAttribute("listBooks", service.listAll());
return "index";
}
@PostMapping("/save")
public String saveBook(@ModelAttribute("book") Book book) {
service.save(book);
return "redirect:/";
}
}
在 resources/templates/
下:
xml复制编辑
org.springframework.boot
spring-boot-starter-security
yaml复制编辑spring:
security:
user:
name: admin
password: admin123
或自定义认证逻辑 + 数据库存储用户密码 + BCrypt 加密。
java复制编辑@SpringBootTest
class BookServiceTest {
@Autowired BookService service;
@Test
void testListAll() {
List books = service.listAll();
assertFalse(books.isEmpty());
}
}
java复制编辑@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public String handleException(Exception e, Model model) {
model.addAttribute("error", e.getMessage());
return "error";
}
}
bash
复制编辑
mvn clean package
生成 bookmanager-0.0.1-SNAPSHOT.jar
bash
复制编辑
java -jar target/bookmanager-0.0.1-SNAPSHOT.jar
dockerfile复制编辑FROM openjdk:17
ADD target/bookmanager.jar app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
bash复制编辑docker build -t bookmanager .
docker run -p 8080:8080 bookmanager
阶段 | 时间占比 | 说明 |
---|---|---|
需求分析与设计 | 10% | 通常 1~2 天 |
环境准备与初始化 | 5% | 项目生成 + 配置 + DB连接 |
模块开发 | 40% | 重点开发,持续集成 |
页面开发 + 安全 | 15% | UI 渲染、登录校验 |
测试与优化 | 20% | 单元、接口、异常、性能 |
构建 + 部署上线 | 10% | Docker 或 Jar 发布 |
Spring Boot 已内置分页支持,无需额外依赖
java复制编辑public Page listAll(int pageNum, String keyword) {
Pageable pageable = PageRequest.of(pageNum - 1, 10); // 第0页起
if (keyword != null && !keyword.isEmpty()) {
return repo.findByTitleContaining(keyword, pageable);
}
return repo.findAll(pageable);
}
java
复制编辑
Page findByTitleContaining(String keyword, Pageable pageable);
java复制编辑@GetMapping("/")
public String viewHomePage(
@RequestParam(defaultValue = "1") int page,
@RequestParam(required = false) String keyword,
Model model) {
Page bookPage = service.listAll(page, keyword);
model.addAttribute("bookPage", bookPage);
model.addAttribute("currentPage", page);
model.addAttribute("keyword", keyword);
return "index";
}
html复制编辑
Spring Boot 自动启用 Multipart
yaml复制编辑spring:
servlet:
multipart:
enabled: true
max-file-size: 2MB
max-request-size: 5MB
upload:
dir: uploads/
java复制编辑@PostMapping("/upload")
public String uploadFile(@RequestParam("file") MultipartFile file) throws IOException {
String uploadDir = "uploads/";
String filename = UUID.randomUUID() + "_" + file.getOriginalFilename();
Path path = Paths.get(uploadDir + filename);
Files.createDirectories(path.getParent());
Files.write(path, file.getBytes());
return "redirect:/?uploadSuccess=true";
}
html复制编辑
xml复制编辑
io.jsonwebtoken
jjwt-api
0.11.5
java复制编辑public class JwtUtil {
private static final String SECRET_KEY = "secret123";
public static String generateToken(String username) {
return Jwts.builder()
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + 86400000)) // 1天
.signWith(SignatureAlgorithm.HS512, SECRET_KEY)
.compact();
}
public static String extractUsername(String token) {
return Jwts.parser().setSigningKey(SECRET_KEY)
.parseClaimsJws(token).getBody().getSubject();
}
}
java复制编辑@RestController
public class AuthController {
@PostMapping("/api/login")
public ResponseEntity> login(@RequestBody LoginRequest login) {
if (login.getUsername().equals("admin") && login.getPassword().equals("1234")) {
String token = JwtUtil.generateToken(login.getUsername());
return ResponseEntity.ok(Map.of("token", token));
}
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid credentials");
}
}
自定义 OncePerRequestFilter
解码并设置 SecurityContextHolder
,可在需要认证的接口中通过 @AuthenticationPrincipal
拿用户。
xml复制编辑
com.github.vladimir-bukhtoyarov
bucket4j-core
8.4.0
java复制编辑@Component
public class RateLimitInterceptor extends HandlerInterceptorAdapter {
private final Map ipBucketMap = new ConcurrentHashMap<>();
private Bucket resolveBucket(String ip) {
return ipBucketMap.computeIfAbsent(ip, key ->
Bucket4j.builder()
.addLimit(Bandwidth.classic(20, Refill.greedy(20, Duration.ofMinutes(1))))
.build()
);
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String ip = request.getRemoteAddr();
Bucket bucket = resolveBucket(ip);
if (bucket.tryConsume(1)) {
return true;
}
response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
response.getWriter().write("Rate limit exceeded");
return false;
}
}
然后注册到 WebMvcConfigurer 的 addInterceptors()
。
plaintext复制编辑[Spring Boot + Spring Security + JPA]
↓ ↓ ↓
JWT认证 分页搜索 文件上传
↓ ↓ ↓
REST风格接口 HTML页面 本地磁盘
↓
接口限流(Bucket4j)
plaintext复制编辑[需求 → 技术选型 → 架构 → 实体建模 → DB设计]
↓
[项目初始化 → 多层结构 → Controller/Service/Repo]
↓
[页面开发 + Thymeleaf + Bootstrap]
↓
[登录权限 + 测试 + 异常处理]
↓
[Maven打包 + Docker部署 + 上线发布]