分享
  1. 首页
  2. 文章

CTF-PWN实战技能特训班

asdgs · · 12 次点击 · · 开始浏览

获课地址:666it.top/15776/ SpringBoot实战项目教程 - 无惧面试 项目概述 这是一个完整的SpringBoot电商后台管理系统实战项目,涵盖了面试中常见的技术点和业务场景。 技术栈 后端: SpringBoot 2.7+, Spring Security, JWT, MyBatis-Plus, Redis 数据库: MySQL 8.0, Redis 7.0 工具: Maven, Swagger, Logback, Docker 项目结构 text springboot-interview-project ├── src/main/java/com/interview │ ├── config/ # 配置类 │ ├── controller/ # 控制器层 │ ├── service/ # 服务层 │ ├── mapper/ # 数据访问层 │ ├── entity/ # 实体类 │ ├── dto/ # 数据传输对象 │ ├── vo/ # 视图对象 │ ├── security/ # 安全配置 │ └── utils/ # 工具类 ├── resources/ │ ├── application.yml # 配置文件 │ ├── mapper/ # XML映射文件 │ └── sql/ # 数据库脚本 └── test/ # 测试代码 快速开始 1. 数据库准备 sql CREATE DATABASE interview_db CHARACTER SET utf8mb4; USE interview_db; -- 用户表 CREATE TABLE `user` ( `id` bigint NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL UNIQUE, `password` varchar(100) NOT NULL, `email` varchar(100), `phone` varchar(20), `status` tinyint DEFAULT 1 COMMENT '1-正常, 0-禁用', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 商品表 CREATE TABLE `product` ( `id` bigint NOT NULL AUTO_INCREMENT, `name` varchar(200) NOT NULL, `price` decimal(10,2) NOT NULL, `stock` int NOT NULL DEFAULT 0, `category_id` bigint, `description` text, `status` tinyint DEFAULT 1 COMMENT '1-上架, 0-下架', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 2. 核心代码实现 2.1 SpringBoot主启动类 java @SpringBootApplication @MapperScan("com.interview.mapper") @EnableCaching @EnableScheduling public class InterviewApplication { public static void main(String[] args) { SpringApplication.run(InterviewApplication.class, args); } } 2.2 用户认证模块(JWT + Spring Security) JWT工具类 java @Component public class JwtTokenUtil { private final String secret = "interview-secret-key"; private final Long expiration = 86400000L; // 24小时 public String generateToken(UserDetails userDetails) { Map<String, Object> claims = new HashMap<>(); claims.put("sub", userDetails.getUsername()); claims.put("created", new Date()); return Jwts.builder() .setClaims(claims) .setExpiration(new Date(System.currentTimeMillis() + expiration)) .signWith(SignatureAlgorithm.HS512, secret) .compact(); } public String getUsernameFromToken(String token) { return getClaimsFromToken(token).getSubject(); } public Boolean validateToken(String token, UserDetails userDetails) { final String username = getUsernameFromToken(token); return (username.equals(userDetails.getUsername()) && !isTokenExpired(token)); } private Claims getClaimsFromToken(String token) { return Jwts.parser() .setSigningKey(secret) .parseClaimsJws(token) .getBody(); } } Security配置 java @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; @Autowired private JwtRequestFilter jwtRequestFilter; @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeRequests() .antMatchers("/api/auth/**", "/swagger-ui/**", "/v3/api-docs/**").permitAll() .anyRequest().authenticated() .and() .exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint) .and() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class); } } 2.3 MyBatis-Plus配置与使用 配置类 java @Configuration @MapperScan("com.interview.mapper") public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); // 分页插件 interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); // 乐观锁插件 interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); return interceptor; } } BaseEntity(包含审计字段) java @Data public abstract class BaseEntity { @TableId(type = IdType.AUTO) private Long id; @TableField(fill = FieldFill.INSERT) private LocalDateTime createTime; @TableField(fill = FieldFill.INSERT_UPDATE) private LocalDateTime updateTime; @Version private Integer version; } 2.4 Redis缓存实现 Redis配置 java @Configuration public class RedisConfig { @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(factory); // 使用Jackson序列化 Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class); ObjectMapper mapper = new ObjectMapper(); mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); mapper.activateDefaultTyping(mapper.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL); serializer.setObjectMapper(mapper); template.setKeySerializer(new StringRedisSerializer()); template.setValueSerializer(serializer); template.setHashKeySerializer(new StringRedisSerializer()); template.setHashValueSerializer(serializer); template.afterPropertiesSet(); return template; } } 商品服务层(缓存示例) java @Service @Slf4j public class ProductService { @Autowired private ProductMapper productMapper; @Autowired private RedisTemplate<String, Object> redisTemplate; private static final String PRODUCT_CACHE_KEY = "product:"; private static final String PRODUCT_LIST_CACHE_KEY = "product:list"; @Cacheable(value = "product", key = "#id") public ProductVO getProductById(Long id) { log.info("查询数据库获取商品: {}", id); Product product = productMapper.selectById(id); return convertToVO(product); } @CacheEvict(value = "product", key = "#id") public void updateProduct(ProductDTO productDTO) { Product product = convertToEntity(productDTO); productMapper.updateById(product); // 清除列表缓存 redisTemplate.delete(PRODUCT_LIST_CACHE_KEY); } @Cacheable(value = "product", key = "'list:' + #page + ':' + #size") public PageResult<ProductVO> getProductList(int page, int size, String keyword) { Page<Product> pageInfo = new Page<>(page, size); LambdaQueryWrapper<Product> queryWrapper = new LambdaQueryWrapper<>(); if (StringUtils.hasText(keyword)) { queryWrapper.like(Product::getName, keyword); } queryWrapper.orderByDesc(Product::getCreateTime); Page<Product> result = productMapper.selectPage(pageInfo, queryWrapper); return new PageResult<>( result.getRecords().stream().map(this::convertToVO).collect(Collectors.toList()), result.getTotal(), result.getCurrent(), result.getSize() ); } } 2.5 全局异常处理 java @RestControllerAdvice @Slf4j public class GlobalExceptionHandler { @ExceptionHandler(BusinessException.class) public ResponseEntity<ApiResponse<?>> handleBusinessException(BusinessException e) { log.error("业务异常: {}", e.getMessage(), e); return ResponseEntity.status(HttpStatus.BAD_REQUEST) .body(ApiResponse.error(e.getCode(), e.getMessage())); } @ExceptionHandler(AccessDeniedException.class) public ResponseEntity<ApiResponse<?>> handleAccessDeniedException(AccessDeniedException e) { return ResponseEntity.status(HttpStatus.FORBIDDEN) .body(ApiResponse.error(403, "权限不足")); } @ExceptionHandler(Exception.class) public ResponseEntity<ApiResponse<?>> handleException(Exception e) { log.error("系统异常: {}", e.getMessage(), e); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body(ApiResponse.error(500, "系统异常,请稍后重试")); } } 2.6 日志配置(Logback) xml <!-- src/main/resources/logback-spring.xml --> <configuration> <property name="LOG_PATH" value="./logs"/> <property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"/> <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>${LOG_PATTERN}</pattern> </encoder> </appender> <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>${LOG_PATH}/application.log</file> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>${LOG_PATH}/application.%d{yyyy-MM-dd}.log</fileNamePattern> <maxHistory>30</maxHistory> </rollingPolicy> <encoder> <pattern>${LOG_PATTERN}</pattern> </encoder> </appender> <root level="INFO"> <appender-ref ref="CONSOLE"/> <appender-ref ref="FILE"/> </root> <!-- SQL日志 --> <logger name="com.interview.mapper" level="DEBUG" additivity="false"> <appender-ref ref="CONSOLE"/> </logger> </configuration> 3. 面试重点功能实现 3.1 分布式ID生成(雪花算法) java @Component public class SnowflakeIdGenerator { private final long twepoch = 1625068800000L; // 起始时间戳 private final long workerIdBits = 5L; private final long datacenterIdBits = 5L; private final long sequenceBits = 12L; private final long maxWorkerId = ~(-1L << workerIdBits); private final long maxDatacenterId = ~(-1L << datacenterIdBits); private final long sequenceMask = ~(-1L << sequenceBits); private final long workerIdShift = sequenceBits; private final long datacenterIdShift = sequenceBits + workerIdBits; private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits; private long workerId; private long datacenterId; private long sequence = 0L; private long lastTimestamp = -1L; public SnowflakeIdGenerator(@Value("${snowflake.worker-id:1}") long workerId, @Value("${snowflake.datacenter-id:1}") long datacenterId) { if (workerId > maxWorkerId || workerId < 0) { throw new IllegalArgumentException("workerId不合法"); } if (datacenterId > maxDatacenterId || datacenterId < 0) { throw new IllegalArgumentException("datacenterId不合法"); } this.workerId = workerId; this.datacenterId = datacenterId; } public synchronized long nextId() { long timestamp = timeGen(); if (timestamp < lastTimestamp) { throw new RuntimeException("时钟回拨异常"); } if (timestamp == lastTimestamp) { sequence = (sequence + 1) & sequenceMask; if (sequence == 0) { timestamp = tilNextMillis(lastTimestamp); } } else { sequence = 0L; } lastTimestamp = timestamp; return ((timestamp - twepoch) << timestampLeftShift) | (datacenterId << datacenterIdShift) | (workerId << workerIdShift) | sequence; } private long tilNextMillis(long lastTimestamp) { long timestamp = timeGen(); while (timestamp <= lastTimestamp) { timestamp = timeGen(); } return timestamp; } private long timeGen() { return System.currentTimeMillis(); } } 3.2 异步处理与线程池 java @Configuration @EnableAsync public class AsyncConfig { @Bean("taskExecutor") public ThreadPoolTaskExecutor taskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); // 核心线程数 executor.setCorePoolSize(10); // 最大线程数 executor.setMaxPoolSize(50); // 队列容量 executor.setQueueCapacity(200); // 线程存活时间 executor.setKeepAliveSeconds(60); // 线程名前缀 executor.setThreadNamePrefix("async-task-"); // 拒绝策略 executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); executor.initialize(); return executor; } } @Service @Slf4j public class OrderService { @Async("taskExecutor") @Transactional(propagation = Propagation.REQUIRES_NEW) public CompletableFuture<Void> processOrderAsync(Long orderId) { log.info("开始异步处理订单: {}", orderId); // 模拟业务处理 try { Thread.sleep(3000); // 发送邮件通知 sendOrderEmail(orderId); // 更新库存 updateStock(orderId); // 记录日志 saveOrderLog(orderId); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } log.info("订单处理完成: {}", orderId); return CompletableFuture.completedFuture(null); } } 3.3 接口限流(Redis + Lua) java @Component public class RateLimiter { @Autowired private StringRedisTemplate redisTemplate; private static final String RATE_LIMIT_SCRIPT = "local key = KEYS[1]\n" + "local limit = tonumber(ARGV[1])\n" + "local window = tonumber(ARGV[2])\n" + "local current = redis.call('get', key)\n" + "if current == false then\n" + " redis.call('setex', key, window, 1)\n" + " return 1\n" + "elseif tonumber(current) < limit then\n" + " redis.call('incr', key)\n" + " return 1\n" + "else\n" + " return 0\n" + "end"; public boolean tryAcquire(String key, int limit, int window) { DefaultRedisScript<Long> script = new DefaultRedisScript<>(); script.setScriptText(RATE_LIMIT_SCRIPT); script.setResultType(Long.class); Long result = redisTemplate.execute(script, Collections.singletonList(key), String.valueOf(limit), String.valueOf(window)); return result != null && result == 1; } } @Aspect @Component @Slf4j public class RateLimitAspect { @Autowired private RateLimiter rateLimiter; @Around("@annotation(rateLimit)") public Object around(ProceedingJoinPoint joinPoint, RateLimit rateLimit) throws Throwable { String key = rateLimit.key(); int limit = rateLimit.limit(); int window = rateLimit.window(); if (!rateLimiter.tryAcquire(key, limit, window)) { throw new BusinessException("请求过于频繁,请稍后重试"); } return joinPoint.proceed(); } } 4. 测试用例 java @SpringBootTest @AutoConfigureMockMvc class ProductControllerTest { @Autowired private MockMvc mockMvc; @Autowired private ObjectMapper objectMapper; @Test @WithMockUser(username = "admin", roles = {"ADMIN"}) void testCreateProduct() throws Exception { ProductDTO productDTO = new ProductDTO(); productDTO.setName("测试商品"); productDTO.setPrice(new BigDecimal("99.99")); productDTO.setStock(100); mockMvc.perform(post("/api/products") .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(productDTO))) .andExpect(status().isOk()) .andExpect(jsonPath("$.code").value(200)) .andExpect(jsonPath("$.data.name").value("测试商品")); } @Test void testRateLimit() throws Exception { for (int i = 0; i < 11; i++) { mockMvc.perform(get("/api/public/test")) .andDo(MockMvcResultHandlers.print()); } // 第11次应该被限流 mockMvc.perform(get("/api/public/test")) .andExpect(status().isTooManyRequests()); } } 5. 部署配置 Docker部署文件 dockerfile FROM openjdk:11-jre-slim WORKDIR /app COPY target/springboot-interview-1.0.0.jar app.jar EXPOSE 8080 ENTRYPOINT ["java", "-jar", "app.jar"] Docker Compose配置 yaml version: '3.8' services: mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: root123 MYSQL_DATABASE: interview_db ports: - "3306:3306" volumes: - mysql_data:/var/lib/mysql redis: image: redis:7.0-alpine ports: - "6379:6379" volumes: - redis_data:/data app: build: . depends_on: - mysql - redis environment: SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/interview_db SPRING_REDIS_HOST: redis ports: - "8080:8080" volumes: mysql_data: redis_data: 6. 面试准备要点 高频面试问题 SpringBoot自动配置原理 @SpringBootApplication组合注解 spring.factories机制 条件注解@Conditional Spring事务管理 声明式事务@Transactional 传播行为(Propagation) 隔离级别(Isolation) 数据库优化 索引设计与优化 分库分表策略 读写分离实现 缓存穿透/击穿/雪崩解决方案 布隆过滤器 缓存空对象 互斥锁 热点数据永不过期 分布式锁实现 Redis分布式锁(Redisson) Zookeeper分布式锁 数据库乐观锁 项目亮点提炼 代码规范:统一异常处理、日志记录、API响应格式 性能优化:Redis缓存、数据库连接池、异步处理 安全性:JWT认证、接口限流、SQL防注入 可维护性:模块化设计、清晰的包结构、完整的文档 可扩展性:微服务化预留接口、配置外部化 7. 项目运行 克隆项目并导入IDE 修改application.yml中的数据库配置 运行SQL脚本初始化数据库 启动Redis服务 运行InterviewApplication 访问 http://localhost:8080/swagger-ui.html 这个项目涵盖了SpringBoot开发中的核心技术和面试常见问题,通过实际编码掌握这些内容,可以帮助你在面试中更有信心。建议在学习过程中多思考、多实践,理解每个技术点的原理和适用场景。

有疑问加站长微信联系(非本文作者))

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

关注微信
12 次点击
暂无回复
添加一条新回复 (您需要 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传

用户登录

没有账号?注册
(追記) (追記ここまで)

今日阅读排行

    加载中
(追記) (追記ここまで)

一周阅读排行

    加载中

关注我

  • 扫码关注领全套学习资料 关注微信公众号
  • 加入 QQ 群:
    • 192706294(已满)
    • 731990104(已满)
    • 798786647(已满)
    • 729884609(已满)
    • 977810755(已满)
    • 815126783(已满)
    • 812540095(已满)
    • 1006366459(已满)
    • 692541889

  • 关注微信公众号
  • 加入微信群:liuxiaoyan-s,备注入群
  • 也欢迎加入知识星球 Go粉丝们(免费)

给该专栏投稿 写篇新文章

每篇文章有总共有 5 次投稿机会

收入到我管理的专栏 新建专栏