本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:本项目基于SpringBoot框架,整合MyBatis、Druid连接池、Redis缓存、WebSocket实时通信、Swagger2接口文档等主流技术,构建了一个功能完善的微服务架构。内容涵盖多数据源配置、Redis消息发布订阅、WebSocket双向通信、接口文档生成、数据库分页、自定义注解与AOP日志记录等核心功能,适用于企业级应用开发,帮助开发者全面掌握SpringBoot微服务项目的开发与集成技巧。
springboot+mybatis+druid 多数据源+redis+websocket+swagger2

1. SpringBoot快速开发与自动配置原理

1.1 SpringBoot简介与快速开发优势

SpringBoot 是基于 Spring 框架的扩展,旨在简化 Spring 应用的初始搭建与开发。它通过 自动配置(Auto-Configuration) 起步依赖(Starter Dependency) 机制,显著降低了配置复杂度,提升了开发效率。SpringBoot 的核心理念是“约定优于配置”,开发者只需引入相关 Starter 包,即可自动完成大部分 Bean 的注册与初始化。

例如,只需在 pom.xml 中引入如下依赖,即可快速集成 Web 功能:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

该依赖不仅包含了 Spring MVC 和 Tomcat 嵌入式服务器,还会根据类路径自动配置 DispatcherServlet、ViewResolver 等关键组件,从而实现“开箱即用”。

2. MyBatis持久层框架集成与数据库操作

在现代Java开发中,MyBatis作为一款轻量级的ORM框架,凭借其灵活性与高性能,成为SpringBoot项目中处理数据库操作的首选工具。它不仅支持灵活的SQL编写,还能很好地与SpringBoot整合,实现高效的数据库访问与事务管理。本章将围绕MyBatis在SpringBoot项目中的集成方式、CRUD操作实践以及事务与缓存机制进行深入讲解,并结合代码示例和流程图进行详细说明。

2.1 MyBatis与SpringBoot的整合方式

将MyBatis整合进SpringBoot项目中是实现数据库访问的第一步。通过引入合适的依赖、配置文件设置以及使用注解绑定Mapper接口与XML文件,可以快速完成集成。

2.1.1 依赖引入与配置文件设置

要使用MyBatis,首先需要在 pom.xml 中添加相应的依赖:

<!-- MyBatis Spring Boot Starter -->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>3.0.3</version>
</dependency>

<!-- MySQL 驱动 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.33</version>
</dependency>

添加完依赖后,还需要在 application.yml 中配置数据库连接信息和MyBatis相关设置:

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=UTC
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver

mybatis:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.example.model

参数说明:

  • spring.datasource.* :配置数据库连接信息。
  • mybatis.mapper-locations :指定XML映射文件的位置。
  • mybatis.type-aliases-package :配置实体类的别名包路径,简化映射文件中实体类引用。

2.1.2 Mapper接口与XML映射文件绑定

MyBatis通过Mapper接口与XML文件实现SQL与Java对象的映射。接口中定义方法,XML中定义SQL语句与结果映射。

Mapper接口示例:

package com.example.mapper;

import com.example.model.User;
import java.util.List;

public interface UserMapper {
    User selectById(Long id);
    List<User> selectAll();
    void insert(User user);
    void update(User user);
    void deleteById(Long id);
}

XML映射文件示例(UserMapper.xml):

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.UserMapper">

    <select id="selectById" resultType="com.example.model.User">
        SELECT * FROM users WHERE id = #{id}
    </select>

    <select id="selectAll" resultType="com.example.model.User">
        SELECT * FROM users
    </select>

    <insert id="insert">
        INSERT INTO users(name, email) VALUES(#{name}, #{email})
    </insert>

    <update id="update">
        UPDATE users SET name = #{name}, email = #{email} WHERE id = #{id}
    </update>

    <delete id="deleteById">
        DELETE FROM users WHERE id = #{id}
    </delete>

</mapper>

逻辑分析:

  • namespace :指向Mapper接口的全限定类名。
  • 每个SQL语句的 id 对应接口中的方法名。
  • #{} 表示参数占位符,防止SQL注入。
  • resultType 指定返回结果的Java类。

2.1.3 使用@Mapper与@MapperScan注解

为了简化配置,SpringBoot支持使用 @Mapper 注解直接将接口注册为Bean,也可以通过 @MapperScan 批量扫描Mapper接口。

方式一:单个接口注册

@Mapper
public interface UserMapper {
    ...
}

方式二:批量扫描接口

在SpringBoot启动类上添加:

@MapperScan("com.example.mapper")
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

流程图展示整合过程:

graph TD
    A[添加MyBatis依赖] --> B[配置application.yml]
    B --> C[创建Mapper接口]
    C --> D[编写XML映射文件]
    D --> E{选择注册方式}
    E --> F[使用@Mapper]
    E --> G[使用@MapperScan]

2.2 数据库CRUD操作实践

CRUD(Create, Read, Update, Delete)是数据库操作的核心内容。MyBatis通过灵活的SQL编写和动态SQL支持,能够很好地处理各种数据库操作场景。

2.2.1 单表增删改查实现

继续以 UserMapper 为例,展示基本的CRUD操作实现:

@RestController
@RequestMapping("/users")
public class UserController {

    @Autowired
    private UserMapper userMapper;

    @GetMapping("/{id}")
    public User getUserById(@PathVariable Long id) {
        return userMapper.selectById(id);
    }

    @GetMapping
    public List<User> getAllUsers() {
        return userMapper.selectAll();
    }

    @PostMapping
    public void createUser(@RequestBody User user) {
        userMapper.insert(user);
    }

    @PutMapping
    public void updateUser(@RequestBody User user) {
        userMapper.update(user);
    }

    @DeleteMapping("/{id}")
    public void deleteUser(@PathVariable Long id) {
        userMapper.deleteById(id);
    }
}

逻辑分析:

  • 控制器通过 @Autowired 注入Mapper。
  • 使用HTTP方法对应增删改查操作。
  • @PathVariable 用于获取路径参数, @RequestBody 用于接收JSON请求体。

2.2.2 动态SQL的编写技巧

动态SQL是MyBatis的亮点之一,允许根据条件生成不同的SQL语句。常见标签包括 <if> <choose> <when> <set> <foreach> 等。

示例:动态查询用户

<select id="searchUsers" resultType="com.example.model.User">
    SELECT * FROM users
    <where>
        <if test="name != null">
            AND name LIKE CONCAT('%', #{name}, '%')
        </if>
        <if test="email != null">
            AND email LIKE CONCAT('%', #{email}, '%')
        </if>
    </where>
</select>

逻辑分析:

  • <where> 标签会自动处理 AND OR 开头的问题。
  • <if> 根据参数是否为空决定是否拼接条件。
  • CONCAT('%', #{name}, '%') 用于模糊查询。

2.2.3 结果集映射与关联查询处理

当数据库查询结果与Java对象不一致时,可以通过 <resultMap> 进行自定义映射。

示例:一对一关联查询

<resultMap id="userResultMap" type="com.example.model.User">
    <id property="id" column="id"/>
    <result property="name" column="name"/>
    <result property="email" column="email"/>
    <association property="role" javaType="com.example.model.Role">
        <id property="roleId" column="role_id"/>
        <result property="roleName" column="role_name"/>
    </association>
</resultMap>

<select id="selectUserWithRole" resultMap="userResultMap">
    SELECT u.id, u.name, u.email, r.id AS role_id, r.name AS role_name
    FROM users u
    LEFT JOIN roles r ON u.role_id = r.id
    WHERE u.id = #{id}
</select>

逻辑分析:

  • <resultMap> 定义映射规则。
  • <association> 用于一对一关联对象。
  • 查询结果中的字段名与Java属性不一致时,可通过 column 指定。

表格:MyBatis常用SQL标签对比

标签名 用途说明
<if> 条件判断,满足条件则拼接SQL
<choose> 多条件分支判断,满足一个即停止
<when> <choose> 的子标签,表示条件分支
<set> 自动处理更新语句中的逗号问题
<foreach> 遍历集合,常用于IN查询或批量操作
<resultMap> 自定义结果映射

2.3 MyBatis事务管理与缓存机制

事务和缓存是数据库操作中提升性能和保证数据一致性的关键机制。MyBatis提供了对事务和缓存的良好支持。

2.3.1 本地事务与分布式事务简介

MyBatis自身并不管理事务,而是交由Spring管理。Spring通过 @Transactional 注解实现事务控制。

@Service
public class UserService {

    @Autowired
    private UserMapper userMapper;

    @Transactional
    public void transferMoney(Long fromId, Long toId, BigDecimal amount) {
        User fromUser = userMapper.selectById(fromId);
        User toUser = userMapper.selectById(toId);

        fromUser.setBalance(fromUser.getBalance().subtract(amount));
        toUser.setBalance(toUser.getBalance().add(amount));

        userMapper.update(fromUser);
        userMapper.update(toUser);
    }
}

逻辑分析:

  • 方法上添加 @Transactional 即可开启事务。
  • 所有操作在同一个事务中执行,任一失败则回滚。

本地事务 vs 分布式事务对比表:

特性 本地事务 分布式事务
涉及数据源 单个数据库 多个数据库或服务
ACID支持 完全支持 需要使用如Seata、XA协议等中间件
实现复杂度 简单 复杂
性能 较低
典型场景 同库转账、订单状态更新 跨库支付、跨服务订单处理

2.3.2 一级缓存与二级缓存配置

MyBatis提供了一级缓存(SqlSession级别)和二级缓存(Mapper级别)来提升查询性能。

一级缓存默认开启,无需配置。

二级缓存配置步骤:

  1. 在Mapper接口上添加注解:
@CacheNamespace
public interface UserMapper {
    ...
}
  1. application.yml 中启用二级缓存:
mybatis:
  configuration:
    cache-enabled: true
  1. XML文件中使用缓存:
<mapper namespace="com.example.mapper.UserMapper">
    <cache/>
    ...
</mapper>

逻辑分析:

  • 一级缓存作用域为单个SqlSession,每次查询结果会被缓存。
  • 二级缓存作用域为整个Mapper接口,跨SqlSession共享缓存数据。

2.3.3 缓存穿透与失效策略优化

缓存穿透是指查询一个不存在的数据,导致每次都穿透到数据库。可以采用如下策略优化:

  • 空值缓存策略 :缓存空对象,设置短TTL。
  • 布隆过滤器 :快速判断是否存在该键,避免无效查询。

示例:缓存空值处理

public User getUserById(Long id) {
    User user = userMapper.selectById(id);
    if (user == null) {
        // 缓存空对象,设置5分钟过期
        redisTemplate.opsForValue().set("user:" + id, "", 5, TimeUnit.MINUTES);
    }
    return user;
}

缓存失效策略对比:

策略 说明
TTL(存活时间) 设置缓存的最大存活时间
TTI(空闲时间) 设置缓存的最长空闲时间
主动清除 通过代码主动删除缓存
基于事件监听 缓存更新时触发事件通知其他节点同步

流程图展示缓存穿透优化过程:

graph TD
    A[请求查询数据] --> B{缓存是否存在?}
    B -->|是| C[返回缓存数据]
    B -->|否| D[查询数据库]
    D --> E{数据是否存在?}
    E -->|是| F[写入缓存并返回]
    E -->|否| G[缓存空值并设置TTL]

本章详细讲解了MyBatis与SpringBoot的整合方式、CRUD操作实践以及事务与缓存机制的使用。通过合理的配置与编码技巧,可以显著提升数据库访问效率与系统稳定性。下一章将继续深入探讨数据库连接池的配置与性能监控。

3. Druid连接池配置与数据库性能监控

3.1 Druid连接池的引入与基础配置

3.1.1 依赖配置与连接参数设置

Druid 是阿里巴巴开源的一款高性能数据库连接池,广泛用于 Java Web 应用中。它不仅提供了连接池的基本功能,还内置了强大的监控能力,适合对数据库性能有较高要求的项目。

要在 SpringBoot 项目中引入 Druid,首先需要在 pom.xml 文件中添加依赖:

<!-- Druid Starter -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.2.15</version>
</dependency>

<!-- MySQL 驱动 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.30</version>
</dependency>

接下来,在 application.yml application.properties 中配置 Druid 连接池参数:

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=UTC
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      initial-size: 5
      min-idle: 5
      max-active: 20
      max-wait: 60000
      time-between-eviction-runs-millis: 60000
      min-evictable-idle-time-millis: 300000
      validation-query: SELECT 1 FROM DUAL
      test-while-idle: true
      test-on-borrow: false
      test-on-return: false
      filters: stat,wall
参数说明:
参数名 说明
initial-size 初始连接池大小
min-idle 最小空闲连接数
max-active 最大连接数
max-wait 获取连接的最大等待时间(毫秒)
time-between-eviction-runs-millis 空闲连接回收线程运行间隔时间
min-evictable-idle-time-millis 连接在池中保持空闲而不被回收的最长时间
validation-query 用于检测连接是否有效的 SQL 语句
test-while-idle 是否在连接空闲时检测有效性
filters 配置监控过滤器,如 stat (监控)、 wall (防御 SQL 注入)

3.1.2 初始化与连接复用机制

Druid 的连接池在应用启动时会根据配置自动初始化连接,并根据使用情况动态调整连接数。其连接复用机制主要包括以下几个方面:

  1. 连接池预热 :通过 initial-size 配置项,系统在启动时就创建一定数量的连接。
  2. 空闲连接回收 :通过 min-evictable-idle-time-millis time-between-eviction-runs-millis 控制空闲连接的回收。
  3. 连接有效性检测 :每次从连接池中获取连接时,会根据 validation-query 检查连接是否可用。
示例代码:自定义 Druid 配置类

虽然使用 Starter 可以简化配置,但在某些复杂场景下,我们可能需要手动配置 DruidDataSource

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;

@Configuration
public class DruidConfig {

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.druid")
    public DataSource druidDataSource() {
        return new DruidDataSource();
    }
}

代码分析:

  • @ConfigurationProperties(prefix = "spring.datasource.druid") :将配置文件中以该前缀开头的属性绑定到 DruidDataSource 实例上。
  • @Bean :将 DruidDataSource 注册为 Spring 容器中的 Bean。
  • return new DruidDataSource() :创建并返回一个 Druid 数据源实例。

3.2 Druid内置监控平台搭建

3.2.1 配置StatViewServlet与WebStatFilter

Druid 提供了内置的监控页面,可以实时查看数据库连接状态、SQL 执行情况、慢查询等信息。要启用监控功能,需要配置 StatViewServlet WebStatFilter

步骤一:添加监控依赖(如已使用 Starter 可跳过)
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.15</version>
</dependency>
步骤二:配置监控页面访问权限
spring:
  datasource:
    druid:
      stat-view-servlet:
        enabled: true
        url-pattern: /druid/*
        login-username: admin
        login-password: admin
        reset-enable: false
      web-stat-filter:
        enabled: true
        url-pattern: /*
        exclusions: "*.js,*.gif,*.jpg,*.css,/druid/*"
步骤三:创建配置类启用监控
import com.alibaba.druid.spring.boot3.autoconfigure.DruidDataSourceAutoConfigure;
import com.alibaba.druid.spring.boot3.autoconfigure.DruidDataSourceConfiguration;
import org.springframework.context.annotation.Configuration;

@Configuration
public class DruidMonitorConfig {
    // 无需额外代码,Starter 自动配置
}

访问方式:

启动项目后,访问: http://localhost:8080/druid ,输入配置的用户名和密码即可进入 Druid 监控面板。

3.2.2 SQL监控与慢查询日志分析

Druid 支持对 SQL 执行进行监控,包括执行次数、耗时、慢查询日志等。

配置 SQL 监控:
spring:
  datasource:
    druid:
      filters: stat,wall,slf4j
      filter:
        stat:
          slow-sql-millis: 1000
          log-slow-sql: true
  • slow-sql-millis :设置慢查询阈值(单位:毫秒)
  • log-slow-sql :是否启用慢查询日志记录
日志输出示例:
[INFO] slow sql : 1200ms
SELECT * FROM user WHERE id = 1

3.3 性能调优与安全策略

3.3.1 连接泄漏检测与防止

连接泄漏是指连接被获取后未被释放,导致连接池资源耗尽。Druid 提供了连接泄漏检测机制。

配置连接泄漏检测:
spring:
  datasource:
    druid:
      remove-abandoned: true
      remove-abandoned-timeout: 300
      log-abandoned: true
  • remove-abandoned : 是否启用连接泄漏检测
  • remove-abandoned-timeout : 设置连接未被释放的超时时间(秒)
  • log-abandoned : 是否记录泄漏的连接堆栈信息
示例日志:
[INFO] Abandoned connection opened
com.alibaba.druid.pool.DruidPooledConnection@12345678

3.3.2 SQL防火墙配置与访问控制

Druid 支持 SQL 防火墙(wall),防止 SQL 注入攻击。

启用 SQL 防火墙:
spring:
  datasource:
    druid:
      filters: wall
自定义 SQL 白名单:
spring:
  datasource:
    druid:
      filter:
        wall:
          config:
            drop-table-allow: false
            select-where-always-true-check: true
  • drop-table-allow : 是否允许执行 DROP TABLE 语句
  • select-where-always-true-check : 检查 WHERE 1=1 类似语句,防止 SQL 注入

3.3.3 结合SpringBoot Actuator实现健康检查

SpringBoot Actuator 提供了 /actuator/health 端点,可用于检查数据库连接状态。

配置健康检查:
management:
  health:
    db:
      enabled: true
  endpoints:
    web:
      exposure:
        include: "*"
访问健康检查接口:
GET http://localhost:8080/actuator/health

返回示例:

{
  "status": "UP",
  "components": {
    "db": {
      "status": "UP",
      "details": {
        "database": "MySQL",
        "validationQuery": "SELECT 1"
      }
    }
  }
}

总结与延伸

本章从 Druid 连接池的引入、基础配置,到监控平台搭建、性能调优、安全策略等多个维度进行了详细讲解,并通过代码示例展示了如何在 SpringBoot 项目中集成和配置 Druid。后续章节中,我们将结合 Redis 缓存机制,进一步提升系统的响应速度和数据库压力控制能力。

4. Redis缓存与消息发布/订阅实现

Redis 作为当前最主流的 NoSQL 缓存数据库之一,凭借其高性能、丰富的数据结构支持、持久化能力以及分布式特性,广泛应用于各类高并发、低延迟的系统架构中。本章将从 Redis 的基础引入与连接配置入手,逐步深入讲解其在缓存设计、性能优化方面的最佳实践,并结合 Redis 的发布/订阅机制,实现基于 Redis 的异步消息通信与事件驱动模型,帮助读者掌握如何在实际项目中高效利用 Redis 提升系统性能与响应能力。

4.1 Redis的引入与基本操作

4.1.1 Redis配置与连接池设置

在 SpringBoot 项目中集成 Redis,通常使用 spring-boot-starter-data-redis 作为核心依赖,并结合连接池(如 Lettuce 或 Jedis)来管理 Redis 连接资源。以下是一个典型的 Redis 配置示例。

Maven 依赖配置:
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>io.lettuce.core</groupId>
    <artifactId>lettuce-core</artifactId>
    <version>6.2.0</version>
</dependency>
application.yml 配置:
spring:
  redis:
    host: 127.0.0.1
    port: 6379
    lettuce:
      pool:
        max-active: 8
        max-idle: 4
        min-idle: 1
        max-wait: 2000ms

参数说明:
- host :Redis 服务器地址
- port :Redis 服务端口
- lettuce.pool :连接池配置,控制最大连接数、最小空闲连接等,用于优化资源使用

逻辑分析:
  • 通过 Spring Boot 自动配置,Spring 会自动创建 RedisConnectionFactory ,并基于配置连接到 Redis 服务器。
  • 使用 Lettuce 作为默认客户端,其基于 Netty 实现,具有高性能和非阻塞特性。
  • 连接池的设置有助于避免频繁创建和销毁连接,提升 Redis 操作效率。

4.1.2 String、Hash、List 等常用数据类型操作

Spring Data Redis 提供了 RedisTemplate StringRedisTemplate 来操作 Redis 中的数据。以下展示如何使用 RedisTemplate 进行基本操作。

示例代码:
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

@Service
public class RedisService {

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    // 存储 String 类型
    public void set(String key, Object value, long timeout, TimeUnit unit) {
        redisTemplate.opsForValue().set(key, value, timeout, unit);
    }

    // 获取 String 类型
    public Object get(String key) {
        return redisTemplate.opsForValue().get(key);
    }

    // 存储 Hash 类型
    public void setHash(String key, String hashKey, Object value) {
        redisTemplate.opsForHash().put(key, hashKey, value);
    }

    // 获取 Hash 类型中的某个字段
    public Object getHash(String key, String hashKey) {
        return redisTemplate.opsForHash().get(key, hashKey);
    }

    // 存储 List 类型
    public void leftPushList(String key, Object value) {
        redisTemplate.opsForList().leftPush(key, value);
    }

    // 获取 List 类型的指定索引元素
    public Object getIndexFromList(String key, long index) {
        return redisTemplate.opsForList().index(key, index);
    }
}
代码逻辑分析:
  • RedisTemplate 支持多种数据结构操作,包括 Value (字符串)、 Hash (哈希)、 List (列表)、 Set ZSet 等。
  • set() 方法用于设置一个带过期时间的键值对,常用于缓存数据。
  • setHash() 方法用于将对象以 Hash 形式存储,适合存储结构化数据。
  • leftPushList() 方法用于将元素插入列表头部,适合构建队列结构。

4.2 缓存设计与性能优化

4.2.1 热点数据缓存策略

热点数据是指被频繁访问的数据,将其缓存可以显著降低数据库压力,提高系统响应速度。常见的热点缓存策略如下:

缓存策略 描述 适用场景
缓存预热 系统启动时主动加载热点数据 电商秒杀、首页推荐
缓存穿透 针对不存在数据的频繁请求,使用空值缓存或布隆过滤器 查询不存在的用户ID
缓存击穿 针对热点数据同时过期的问题,使用互斥锁或永不过期策略 热门商品、活动信息
缓存雪崩 大量缓存同时过期,使用随机过期时间或集群缓存 所有缓存统一过期场景

设计建议:
- 对于热点数据,设置较长的过期时间或使用永不过期策略。
- 对于缓存穿透,建议引入布隆过滤器进行前置判断。
- 对于缓存雪崩,可设置缓存时间随机偏移。

4.2.2 缓存过期机制与淘汰策略

Redis 提供了多种过期机制和淘汰策略来管理内存资源:

过期机制 说明
TTL 设置键的过期时间(单位为秒)
EXPIRE 为已存在的键设置生存时间
PERSIST 移除键的过期时间
淘汰策略 说明
noeviction 不淘汰,写入失败
allkeys-lru 所有键中淘汰最近最少使用
volatile-lru 仅淘汰设置了过期时间的键
volatile-ttl 优先淘汰更早过期的键
volatile-random 随机淘汰设置了过期时间的键
allkeys-random 随机淘汰所有键

配置示例:

spring:
  redis:
    timeout: 600s
    lettuce:
      pool:
        max-idle: 5
        min-idle: 1
        max-active: 8

4.2.3 缓存雪崩、击穿与穿透的解决方案

缓存雪崩(大量缓存同时失效)

解决方案:

  • 设置随机过期时间偏移:
int expireTime = baseExpire + new Random().nextInt(300);
redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);
  • 使用分布式缓存集群(如 Redis Cluster)避免单点失效
缓存击穿(热点数据失效)

解决方案:

  • 互斥锁加锁机制(适用于并发写)
public Object getWithLock(String key) {
    String lockKey = "lock:" + key;
    try {
        Boolean isLocked = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);
        if (Boolean.TRUE.equals(isLocked)) {
            // 从数据库加载数据
            Object data = loadDataFromDB();
            redisTemplate.opsForValue().set(key, data, 300, TimeUnit.SECONDS);
            return data;
        } else {
            // 等待并重试
            Thread.sleep(50);
            return getWithLock(key);
        }
    } catch (Exception e) {
        // 异常处理
        return null;
    } finally {
        redisTemplate.delete(lockKey);
    }
}
  • 永不过期策略(适合高并发场景)
缓存穿透(查询不存在数据)

解决方案:

  • 布隆过滤器(BloomFilter)前置拦截非法请求
graph TD
    A[客户端请求] --> B{布隆过滤器}
    B -->|存在| C[Redis查询]
    C --> D{是否命中?}
    D -->|是| E[返回数据]
    D -->|否| F[数据库查询]
    F --> G[写入Redis]
    G --> H[返回结果]
    B -->|不存在| I[直接返回空]

4.3 Redis消息队列与事件驱动

4.3.1 发布/订阅模式实现异步通信

Redis 的发布/订阅机制允许客户端订阅一个或多个频道,并接收其他客户端发送到这些频道的消息。该机制适用于事件通知、日志广播等场景。

示例代码:发布消息
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Service
public class RedisMessagePublisher {

    @Resource
    private RedisTemplate<String, String> redisTemplate;

    public void publish(String channel, String message) {
        redisTemplate.convertAndSend(channel, message);
    }
}
示例代码:订阅消息
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.connection.MessageListener;
import org.springframework.stereotype.Component;

@Component
public class RedisMessageSubscriber implements MessageListener {

    @Override
    public void onMessage(Message message, byte[] pattern) {
        String channel = new String(message.getChannel());
        String body = new String(message.getBody());
        System.out.println("Received message from channel [" + channel + "]: " + body);
    }
}
配置监听器:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.listener.ChannelTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;

@Configuration
public class RedisPubSubConfig {

    @Bean
    public RedisMessageListenerContainer redisContainer(MessageListenerAdapter listenerAdapter) {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(redisTemplate.getConnectionFactory());
        container.addMessageListener(listenerAdapter, new ChannelTopic("my_channel"));
        return container;
    }

    @Bean
    public MessageListenerAdapter listenerAdapter(RedisMessageSubscriber subscriber) {
        return new MessageListenerAdapter(subscriber, "onMessage");
    }
}
逻辑分析:
  • RedisMessagePublisher 使用 convertAndSend 方法向指定频道发布消息。
  • RedisMessageSubscriber 实现 MessageListener 接口,用于监听频道消息。
  • RedisMessageListenerContainer 负责注册监听器并绑定频道。
  • 整个流程实现了 Redis 的发布/订阅机制,可用于系统间异步通信。

4.3.2 基于Redis的事件监听器设计

在实际项目中,可以将 Redis 的发布/订阅机制与 Spring 的事件监听机制结合,实现跨服务、跨模块的事件广播。

示例:Spring Event + Redis Pub/Sub
import org.springframework.context.ApplicationEvent;
import org.springframework.stereotype.Component;

@Component
public class OrderCreatedEvent extends ApplicationEvent {
    public OrderCreatedEvent(Object source) {
        super(source);
    }
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

@Component
public class OrderEventListener implements ApplicationListener<OrderCreatedEvent> {

    @Autowired
    private RedisMessagePublisher redisMessagePublisher;

    @Override
    public void onApplicationEvent(OrderCreatedEvent event) {
        String message = "Order Created: " + event.getSource();
        redisMessagePublisher.publish("order_events", message);
    }
}

流程说明:
- 当订单创建时,Spring 触发 OrderCreatedEvent
- OrderEventListener 监听到事件后,通过 Redis 发布到 order_events 频道。
- 其他服务通过订阅该频道接收事件,实现事件驱动架构。

4.3.3 消息持久化与可靠性传输

虽然 Redis 的发布/订阅机制是内存级别的,不具备持久化能力,但可通过以下方式增强其可靠性:

  • 结合 Redis Stream 数据结构实现持久化消息队列:
// 写入 Stream
redisTemplate.opsForStream().add("mystream", Map.of("order_id", "1001", "status", "created"));

// 读取 Stream
redisTemplate.opsForStream().read("mystream", StreamOffset.from("0-0"));
  • 使用 ACK 机制与消费者组实现消息确认与重试机制
graph LR
    A[生产者] --> B[Redis Stream]
    B --> C[消费者组]
    C --> D[消费者1]
    C --> E[消费者2]
    D --> F[确认处理]
    E --> F

特点:
- Redis Stream 支持消息持久化,可持久存储至磁盘。
- 支持消费者组和 ACK 确认机制,提升消息可靠性。
- 适合构建高可用的消息队列系统。

以上为《第四章:Redis缓存与消息发布/订阅实现》的完整内容,涵盖 Redis 的基础配置、缓存设计优化、发布/订阅机制与事件驱动设计,并结合代码、流程图与表格进行详细说明,满足从入门到进阶的深度学习需求。

5. WebSocket实时通信与单发群发消息处理

WebSocket 是一种全双工通信协议,能够实现在客户端与服务器之间建立持久连接,适用于实时数据传输的场景,如在线聊天、实时通知、股票行情推送等。本章将深入探讨如何在 SpringBoot 中集成 WebSocket,并实现单点消息发送、广播机制、消息格式编解码、会话管理以及性能优化等关键技术点。

5.1 WebSocket基础配置与连接建立

WebSocket 的核心优势在于其建立的连接是双向通信的,这与传统的 HTTP 请求-响应模式有本质区别。在 SpringBoot 中,Spring 提供了对 WebSocket 的良好支持,通过 @ServerEndpoint 注解和 javax.websocket 接口可以快速实现 WebSocket 服务端逻辑。

5.1.1 SpringBoot中WebSocket的集成方式

SpringBoot 集成 WebSocket 的方式主要有两种:

  • 使用 @ServerEndpoint 注解配合 ServerEndpointExporter Bean;
  • 使用 WebSocketHandler 接口结合 WebSocketConfigurer 配置类。
示例代码:配置 WebSocket 支持
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    @Autowired
    private ChatWebSocketHandler chatWebSocketHandler;

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(chatWebSocketHandler, "/chat")
                .setAllowedOrigins("*");
    }
}

逻辑分析:

  • @EnableWebSocket 启用 WebSocket 支持;
  • registerWebSocketHandlers 方法注册了一个 WebSocket 处理器 chatWebSocketHandler ,监听路径为 /chat
  • setAllowedOrigins("*") 允许跨域请求,生产环境建议设置具体域名。

5.1.2 客户端连接与握手拦截器配置

在 WebSocket 握手阶段,可以加入拦截器用于认证用户身份或记录连接日志。

示例代码:握手拦截器实现
@Component
public class ChatHandshakeInterceptor implements HandshakeInterceptor {

    @Override
    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
        if (request instanceof ServletServerHttpRequest) {
            ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
            HttpSession session = servletRequest.getServletRequest().getSession();
            String userId = servletRequest.getServletRequest().getParameter("userId");
            if (userId != null && !userId.isEmpty()) {
                attributes.put("userId", userId);
                return true;
            } else {
                response.setStatusCode(HttpStatus.FORBIDDEN);
                return false;
            }
        }
        return false;
    }

    @Override
    public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {
        // 握手完成后的操作,如记录日志
    }
}

逻辑分析:

  • beforeHandshake 方法在握手前执行,用于验证用户身份;
  • 通过 userId 参数判断是否允许连接,若未传参则返回 403;
  • 使用 attributes 存储用户信息,供后续处理使用。

5.2 实时消息收发机制实现

WebSocket 的核心功能是消息的实时收发。Spring 提供了 WebSocketSession 对象用于管理会话连接,并支持发送文本、二进制等类型的消息。

5.2.1 单点消息发送与广播机制

示例代码:消息处理器类
@Component
public class ChatWebSocketHandler extends TextWebSocketHandler {

    private static final Map<String, WebSocketSession> sessions = new ConcurrentHashMap<>();

    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        String userId = (String) session.getAttributes().get("userId");
        sessions.put(userId, session);
        session.sendMessage(new TextMessage("欢迎用户:" + userId));
    }

    @Override
    public void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        String payload = message.getPayload();
        JSONObject json = new JSONObject(payload);
        String to = json.getString("to");
        String content = json.getString("content");

        if ("all".equals(to)) {
            // 广播消息
            sessions.forEach((id, s) -> {
                try {
                    if (s.isOpen()) {
                        s.sendMessage(new TextMessage("[" + session.getAttributes().get("userId") + "]: " + content));
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            });
        } else {
            // 单点发送
            WebSocketSession targetSession = sessions.get(to);
            if (targetSession != null && targetSession.isOpen()) {
                targetSession.sendMessage(new TextMessage("[" + session.getAttributes().get("userId") + "]: " + content));
            }
        }
    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        String userId = (String) session.getAttributes().get("userId");
        sessions.remove(userId);
    }
}

逻辑分析:

  • afterConnectionEstablished 方法在连接建立时调用,将用户加入会话池;
  • handleTextMessage 方法接收客户端消息,解析 JSON 数据;
  • to 字段为 “all”,则向所有连接发送广播;
  • 否则向指定用户发送单点消息;
  • 连接关闭时从会话池中移除用户。
示例表格:WebSocketSession关键方法说明
方法名 作用
sendMessage 发送消息
isOpen 判断连接是否开启
getAttributes 获取握手阶段存储的属性
close 主动关闭连接

5.2.2 消息格式设计与编解码处理

消息格式设计是 WebSocket 实现的关键环节,推荐使用 JSON 格式进行数据交互。

示例:消息格式定义(JSON)
{
  "to": "user123",
  "content": "你好,WebSocket!"
}
示例代码:JSON编解码工具类
public class JsonUtil {
    private static final ObjectMapper mapper = new ObjectMapper();

    public static String toJson(Object obj) {
        try {
            return mapper.writeValueAsString(obj);
        } catch (JsonProcessingException e) {
            throw new RuntimeException("JSON序列化失败");
        }
    }

    public static <T> T fromJson(String json, Class<T> clazz) {
        try {
            return mapper.readValue(json, clazz);
        } catch (JsonProcessingException e) {
            throw new RuntimeException("JSON反序列化失败");
        }
    }
}

逻辑分析:

  • toJson 方法将对象转换为 JSON 字符串;
  • fromJson 方法将 JSON 字符串解析为对象;
  • 使用 ObjectMapper 提供高性能的 JSON 操作。

5.2.3 用户会话管理与连接池优化

会话管理策略
  • 使用 ConcurrentHashMap 管理用户会话,线程安全;
  • 为每个用户分配唯一标识(如 userId );
  • 在连接关闭时移除无效会话,避免内存泄漏。
示例代码:会话池管理类
@Component
public class SessionManager {

    private final Map<String, WebSocketSession> sessionMap = new ConcurrentHashMap<>();

    public void addSession(String userId, WebSocketSession session) {
        sessionMap.put(userId, session);
    }

    public void removeSession(String userId) {
        sessionMap.remove(userId);
    }

    public WebSocketSession getSession(String userId) {
        return sessionMap.get(userId);
    }

    public Collection<WebSocketSession> getAllSessions() {
        return sessionMap.values();
    }
}

逻辑分析:

  • addSession 添加用户会话;
  • removeSession 移除断开连接的用户;
  • getSession 获取指定用户会话;
  • getAllSessions 获取所有连接用户。

5.3 实战案例:在线聊天与通知推送

在实际业务中,WebSocket 常用于构建在线聊天系统、实时通知系统等。下面以一个在线聊天应用为例,展示如何将前面的技术整合使用。

5.3.1 用户登录状态绑定与消息路由

示例代码:用户登录后建立 WebSocket 连接
@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private SessionManager sessionManager;

    @GetMapping("/login")
    public String login(@RequestParam String userId, HttpSession httpSession) {
        // 模拟登录逻辑
        httpSession.setAttribute("userId", userId);
        return "登录成功,请连接 WebSocket";
    }
}

逻辑分析:

  • 登录接口将用户 ID 存入 Session;
  • 前端在登录后携带 userId 建立 WebSocket 连接。

5.3.2 多端同步与消息重发机制

WebSocket 支持多个设备同时登录一个用户,此时需考虑消息的多端同步与离线重发机制。

示例:多设备连接时的消息广播
public void broadcastToUser(String userId, String message) {
    List<WebSocketSession> sessions = sessionManager.getSessions(userId);
    for (WebSocketSession session : sessions) {
        if (session.isOpen()) {
            session.sendMessage(new TextMessage(message));
        }
    }
}

逻辑分析:

  • 一个用户可能有多个连接(如 PC、手机);
  • 遍历所有连接发送消息;
  • 若连接关闭,则跳过。

5.3.3 安全控制与权限隔离

为了防止非法用户发送消息,可以在 WebSocket 握手阶段进行权限校验。

示例代码:权限校验拦截器
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
    String token = request.getURI().getQuery().split("=")[1];
    if (!isValidToken(token)) {
        response.setStatusCode(HttpStatus.UNAUTHORIZED);
        return false;
    }
    return true;
}

private boolean isValidToken(String token) {
    // 调用鉴权服务验证token
    return true;
}

逻辑分析:

  • 从 URL 参数中提取 token;
  • 调用鉴权服务验证 token 合法性;
  • 验证失败返回 401 状态码。

小结

本章系统讲解了 WebSocket 在 SpringBoot 中的集成与使用方法,包括:

  • WebSocket 的基础配置与连接建立;
  • 消息的单发与广播机制;
  • 消息格式设计与编解码;
  • 用户会话管理与连接池优化;
  • 实战案例中的登录绑定、多端同步、安全控制等高级功能。

通过本章内容,开发者可以快速构建一个高性能、实时通信的 Web 应用,适用于在线聊天、消息推送、多人协作等场景。后续章节将继续探讨如何将 WebSocket 与安全框架(如 Spring Security)整合,进一步提升系统安全性与可扩展性。

6. Swagger2接口文档生成与API测试

6.1 Swagger2的集成与基础配置

6.1.1 依赖引入与Docket配置

在SpringBoot项目中集成Swagger2,首先需要引入相关的依赖。打开 pom.xml 文件,添加如下依赖项:

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.9.2</version>
</dependency>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>2.9.2</version>
</dependency>

引入依赖后,需要创建一个配置类来启用Swagger2。以下是Swagger2的典型配置示例:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2
public class SwaggerConfig {
    @Bean
    public Docket createRestApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo()) // 设置API文档基本信息
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.example.controller")) // 指定扫描的包路径
                .paths(PathSelectors.any())
                .build();
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("SpringBoot接口文档")
                .description("基于Swagger2构建的API文档")
                .version("1.0")
                .build();
    }
}

代码说明:

  • @EnableSwagger2 :启用Swagger2支持。
  • Docket :用于定义Swagger生成文档的具体规则。
  • RequestHandlerSelectors.basePackage("com.example.controller") :指定Swagger扫描的包路径,建议指向控制器所在的包。
  • apiInfo() 方法:设置文档的基本信息,如标题、描述、版本等。

6.1.2 接口描述注解使用规范

为了使生成的API文档更清晰,开发者需要使用Swagger的注解对API进行描述。以下是常用的注解:

注解名称 用途说明
@Api 描述Controller类,用于说明该类的作用
@ApiOperation 描述方法,用于说明方法的功能
@ApiParam 描述方法参数
@ApiModel 描述实体类
@ApiModelProperty 描述实体类字段

示例代码:

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/users")
@Api(tags = "用户管理接口")
public class UserController {

    @GetMapping("/{id}")
    @ApiOperation("根据ID获取用户信息")
    public User getUserById(@PathVariable @ApiParam("用户ID") Long id) {
        // 实际业务逻辑
        return new User(id, "张三");
    }

    @PostMapping
    @ApiOperation("新增用户")
    public User createUser(@RequestBody @ApiParam("用户信息") User user) {
        // 保存用户逻辑
        return user;
    }
}

6.2 API文档生成与测试实践

6.2.1 控制器接口文档自动生成

启动项目后,访问 http://localhost:8080/swagger-ui.html 即可看到自动生成的API文档界面。界面中将展示所有被 @Api 注解标记的控制器类及其接口方法。

每个接口方法的详细信息包括:

  • 接口描述( @ApiOperation
  • 参数说明( @ApiParam
  • 返回值格式(通过方法返回类型自动推断)
  • 请求方式(GET、POST等)

6.2.2 参数说明与返回值示例展示

@ApiParam 中可以设置参数是否为必填、示例值、描述等信息。例如:

@ApiParam(value = "用户ID", required = true, example = "123")

此外,可以通过 @ApiModel @ApiModelProperty 对返回值实体类进行描述:

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;

@ApiModel("用户信息")
public class User {
    @ApiModelProperty("用户唯一标识")
    private Long id;

    @ApiModelProperty("用户姓名")
    private String name;

    // 构造方法、getter、setter略
}

这样,在生成的文档中,返回值结构将更清晰,便于前端开发人员理解。

6.2.3 在线接口测试与调试

Swagger UI不仅提供文档展示功能,还支持在线测试接口。点击接口下的“Try it out”按钮,输入参数后点击“Execute”,即可直接调用后端接口并查看返回结果。

优势:

  • 无需使用Postman或其他测试工具,即可完成接口调试。
  • 支持多种请求方式(GET、POST、PUT、DELETE等)。
  • 可查看请求头、响应体、HTTP状态码等信息。

6.3 高级特性与安全控制

6.3.1 接口分组管理与版本控制

当项目中存在多个模块或版本的API时,可以使用Swagger的分组功能进行管理。例如:

@Bean
public Docket userApi() {
    return new Docket(DocumentationType.SWAGGER_2)
            .groupName("用户管理")
            .apiInfo(apiInfo())
            .select()
            .apis(RequestHandlerSelectors.basePackage("com.example.controller.user"))
            .build();
}

@Bean
public Docket orderApi() {
    return new Docket(DocumentationType.SWAGGER_2)
            .groupName("订单管理")
            .apiInfo(apiInfo())
            .select()
            .apis(RequestHandlerSelectors.basePackage("com.example.controller.order"))
            .build();
}

通过 groupName() 方法设置分组名称,可在Swagger UI中切换不同分组查看对应的API。

6.3.2 接口权限控制与文档访问限制

在生产环境中,通常不希望将接口文档暴露给所有人。可以通过配置Spring Security来限制访问Swagger资源。

SecurityConfig 类中添加如下配置:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
        .antMatchers("/swagger-ui.html", "/webjars/**", "/v2/api-docs", "/swagger-resources/**")
        .hasRole("ADMIN") // 仅管理员角色可访问Swagger文档
        .anyRequest().authenticated()
        .and()
        .formLogin().and()
        .httpBasic();
}

6.3.3 整合Spring Security实现安全访问

结合Spring Security,可以实现更细粒度的访问控制。例如,基于用户名密码、OAuth2、JWT等方式进行身份验证,确保只有授权用户可以访问接口文档。

以下是一个基于HTTP Basic认证的简单示例:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
            .antMatchers("/swagger-ui.html", "/webjars/**", "/v2/api-docs", "/swagger-resources/**").authenticated()
            .anyRequest().permitAll()
            .and()
            .httpBasic(); // 启用HTTP Basic认证
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
            .withUser("admin")
            .password("{noop}123456") // 不加密密码
            .roles("ADMIN");
    }
}

通过上述配置,访问Swagger文档时将弹出登录框,需输入用户名 admin 和密码 123456 才能查看文档内容。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:本项目基于SpringBoot框架,整合MyBatis、Druid连接池、Redis缓存、WebSocket实时通信、Swagger2接口文档等主流技术,构建了一个功能完善的微服务架构。内容涵盖多数据源配置、Redis消息发布订阅、WebSocket双向通信、接口文档生成、数据库分页、自定义注解与AOP日志记录等核心功能,适用于企业级应用开发,帮助开发者全面掌握SpringBoot微服务项目的开发与集成技巧。


本文还有配套的精品资源,点击获取
menu-r.4af5f7ec.gif

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐