告别繁琐的飞书表格API调用,让飞书表格操作像操作Java对象一样简单!

目录

  1. 引言:开发者的飞书表格操作痛点
  2. [功能特性概览:为什么选择 feishu-table-helper](#功能特性概览:为什么选择 feishu-table-helper "#%E5%8A%9F%E8%83%BD%E7%89%B9%E6%80%A7%E6%A6%82%E8%A7%88%E4%B8%BA%E4%BB%80%E4%B9%88%E9%80%89%E6%8B%A9-feishu-table-helper")
  3. 快速上手:5分钟完成安装和配置
  4. @TableProperty注解详解:灵活配置字段映射
  5. 总结:GitHub地址

本文将通过具体的代码示例和实际应用场景,帮助Java开发者快速掌握 feishu-table-helper 的使用方法,大幅提升飞书表格操作的开发效率。

引言:开发者的飞书表格操作痛点

作为Java开发者,你是否曾经为了在飞书表格中读写数据而头疼不已?是否厌倦了反复查阅飞书API文档,编写大量的HTTP请求代码,处理复杂的JSON数据结构?

传统飞书表格操作的痛点

在日常开发中,我们经常需要与飞书表格进行数据交互,但直接使用飞书开放平台API往往面临以下挑战:

🔸 API调用复杂

  • 需要手动构建HTTP请求
  • 处理复杂的认证和授权流程
  • 管理访问令牌的刷新和过期

🔸 数据映射繁琐

  • 手动解析JSON响应数据
  • 在Java对象和表格数据间反复转换
  • 处理不同字段类型的格式化问题

🔸 代码维护困难

  • 大量重复的样板代码
  • 字段变更时需要修改多处代码
  • 错误处理逻辑分散且复杂

🔸 开发效率低下

  • 简单的表格操作需要编写大量代码
  • 调试和测试过程繁琐
  • 团队成员学习成本高

feishu-table-helper:让表格操作回归简单

想象一下,如果你可以像操作普通Java对象一样操作飞书表格,会是什么样的体验?

java 复制代码
// 传统方式:需要几十行代码的HTTP请求和JSON解析
// 现在只需要:
@TableProperty(name = "姓名")
private String name;

// 创建表格
String sheetId = FsHelper.create(sheetName, spreadsheetToken, Employee.class);

// 写入数据
FsHelper.write(employees);

// 读取数据
List<Employee> employees = FsHelper.read(Employee.class);

feishu-table-helper 正是为了解决这些痛点而生的Java库。它通过注解驱动的方式,让飞书表格操作变得像操作数据库一样简单直观。

核心价值:开发效率提升10倍

使用 feishu-table-helper,你将获得:

  • 🚀 极简API设计:一行代码完成复杂的表格操作
  • 📝 注解驱动配置:通过注解定义字段映射,告别手动转换
  • 🔄 自动数据同步:支持批量读写,自动处理数据格式转换
  • ⚡ 开箱即用:最小化配置,快速集成到现有项目

本文将带你了解什么?

在接下来的内容中,你将学到:

  1. 功能特性对比 - 了解相比直接使用API的优势
  2. 快速集成指南 - 5分钟完成项目配置
  3. 注解详细用法 - 掌握 @TableProperty 的所有配置选项
  4. 实际应用场景 - 探索项目管理、数据报表等业务场景
  5. 高级功能技巧 - 性能优化和最佳实践

无论你是刚接触飞书开发的新手,还是希望优化现有代码的资深开发者,这篇文章都将为你提供实用的解决方案。

功能特性概览:为什么选择 feishu-table-helper

核心功能特性

feishu-table-helper 提供了一套完整的飞书表格操作解决方案,让Java开发者能够以最简洁的方式实现复杂的表格操作功能。

🎯 注解驱动的字段映射

通过 @TableProperty 注解,轻松定义Java对象与飞书表格字段的映射关系:

java 复制代码
public class Employee {
    @TableProperty(name = "员工姓名", type = FieldType.TEXT)
    private String name;
    
    @TableProperty(name = "部门", type = FieldType.SINGLE_SELECT, 
                   options = {"技术部", "产品部", "运营部"})
    private String department;
    
    @TableProperty(name = "入职日期", type = FieldType.DATE)
    private LocalDate joinDate;
}

🚀 自动表格创建和管理

一行代码即可根据Java类定义自动创建飞书表格,包括字段类型、选项配置等:

java 复制代码
// 自动创建表格,包含所有字段定义
String sheetId = FsHelper.create(sheetName, spreadsheetToken, Employee.class);

📊 批量数据读写操作

支持高效的批量数据操作,自动处理数据格式转换和类型映射:

java 复制代码
// 批量写入数据
List<Employee> employees = Arrays.asList(/* 员工数据 */);
FsHelper.write(employees);

// 批量读取数据
List<Employee> allEmployees = FsHelper.read(Employee.class);

🔄 智能数据类型转换

自动处理Java对象与飞书表格数据类型之间的转换,支持:

  • 基础数据类型(String、Integer、Double、Boolean等)
  • 日期时间类型(LocalDate、LocalDateTime等)
  • 枚举类型和自定义选项
  • 复杂对象的序列化和反序列化

⚙️ 灵活的配置选项

功能对比:传统方案 vs feishu-table-helper

功能特性 直接使用飞书API feishu-table-helper 优势说明
表格创建 手动构建复杂的JSON请求体,处理字段类型和选项配置 FsHelper.create(sheetName, spreadsheetToken, Employee.class) 代码量减少90%,自动处理字段配置
数据写入 构建HTTP请求,手动转换数据格式,处理批量操作 FsHelper.write(employees) 一行代码完成批量写入,自动类型转换
数据读取 解析JSON响应,手动映射到Java对象,处理分页 FsHelper.read(Employee.class) 直接返回强类型对象列表,自动分页处理
字段映射 硬编码字段名称,手动处理类型转换 注解驱动,编译时检查 类型安全,重构友好,维护成本低
错误处理 分散的异常处理逻辑,需要理解各种API错误码 统一的异常体系,友好的错误信息 调试效率提升,错误定位准确
代码维护 大量样板代码,字段变更影响多处 集中的注解配置,变更影响最小 维护成本降低80%
学习成本 需要深入了解飞书API文档和数据结构 专注业务逻辑,API细节透明化 团队上手时间从天缩短到小时
开发效率 简单操作需要几十行代码 复杂操作只需几行代码 开发效率提升10倍以上

技术要求和依赖信息

系统要求

  • Java版本:JDK 8 或更高版本
  • Spring框架:Spring Boot 2.0+ (可选,用于自动配置)
  • 网络环境:能够访问飞书开放平台API

核心依赖

feishu-table-helper 基于以下稳定可靠的开源库构建:

xml 复制代码
<!-- 核心依赖(自动包含) -->
<dependencies>
    <!-- HTTP客户端 -->
    <dependency>
        <groupId>com.squareup.okhttp3</groupId>
        <artifactId>okhttp</artifactId>
        <version>4.12.0</version>
    </dependency>
    
    <!-- JSON处理 -->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.15.2</version>
    </dependency>
    
    <!-- 日志框架 -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.36</version>
    </dependency>
</dependencies>

兼容性说明

  • Spring Boot 2.x/3.x:完全兼容,提供自动配置
  • Maven/Gradle:支持主流构建工具
  • 多线程环境:线程安全设计,支持并发操作

相比传统方案的核心优势

1. 开发效率大幅提升

传统方案需要编写的代码:

java 复制代码
// 创建表格 - 需要50+行代码
String createTableUrl = "https://open.feishu.cn/open-apis/bitable/v1/apps/" + appId + "/tables";
Map<String, Object> requestBody = new HashMap<>();
// ... 复杂的JSON构建逻辑
HttpPost request = new HttpPost(createTableUrl);
// ... HTTP请求处理逻辑

使用 feishu-table-helper

java 复制代码
// 创建表格 - 只需1行代码
String sheetId = FsHelper.create(sheetName, spreadsheetToken, Employee.class);

2. 类型安全和编译时检查

  • 编译时验证:注解配置错误在编译期就能发现
  • 强类型支持:避免运行时的类型转换错误
  • IDE智能提示:完整的代码补全和重构支持

3. 业务逻辑专注度

开发者可以将更多精力投入到业务逻辑实现上,而不是底层API调用的细节处理。

4. 团队协作效率

  • 统一的开发模式:团队成员使用相同的API风格
  • 降低学习成本:新成员快速上手,无需深入学习飞书API
  • 代码可读性:注解驱动的配置更加直观易懂

通过以上功能特性的对比,我们可以看出 feishu-table-helper 在开发效率、代码质量、维护成本等方面都具有显著优势。接下来,让我们通过具体的安装配置步骤,开始实际体验这个强大的工具。

快速上手:5分钟完成安装和配置

第一步:添加项目依赖

Maven 项目配置

在你的 pom.xml 文件中添加以下依赖:

xml 复制代码
<dependency>
    <groupId>cn.isliu</groupId>
    <artifactId>feishu-table-helper</artifactId>
    <version>0.0.2</version>
</dependency>

Gradle 项目配置

在你的 build.gradle 文件中添加以下依赖:

gradle 复制代码
dependencies {
    implementation 'cn.isliu:feishu-table-helper:0.0.2'
}

第二步:获取飞书应用凭证

要使用 feishu-table-helper,你需要先在飞书开放平台创建应用并获取相关凭证。

2.1 创建飞书应用

  1. 访问 飞书开放平台 并登录
  2. 点击"创建应用" → 选择"自建应用"
  3. 填写应用基本信息(应用名称、描述等)
  4. 创建完成后,记录下 App IDApp Secret

2.2 配置应用权限

在应用管理页面,需要为应用添加以下权限:

必需权限:

  • 表格应用权限
  • 表格应用只读权限
  • 表格应用读写权限

权限配置步骤:

  1. 进入应用详情页 → "权限管理"
  2. 搜索并添加上述权限
  3. 点击"发布版本"使权限生效

2.3 获取访问凭证

飞书应用支持两种认证方式,根据你的使用场景选择:

方式一:应用级别访问(推荐用于服务端应用)

  • 使用 App ID 和 App Secret 获取 tenant_access_token
  • 适用于后端服务访问企业内部数据

方式二:用户级别访问

  • 需要用户授权,获取 user_access_token
  • 适用于需要用户身份验证的场景

第三步:初始化配置

3.1 基础配置方式

在代码中使用:

java 复制代码
@Service
public class EmployeeService {
      
    public void createEmployeeTable() {
        // 直接使用,无需手动初始化
        String spreadsheetToken = "你的表格 SHEET TOKEN";
        String sheetId = FsHelper.create("员工Sheet", spreadsheetToken, Employee.class);
        System.out.println("表格创建成功,ID: " + sheetId);
    }
}

3.3 完整的初始化示例

以下是一个完整的初始化和基本使用示例:

java 复制代码
import io.github.larktablehelper.api.TableHelper;
import io.github.larktablehelper.config.FsConfig;
import io.github.larktablehelper.exception.FeishuTableException;

public class QuickStartExample {
    
    public static void main(String[] args) {
        try {
            // 1. 初始化配置
            FsClient.getInstance().initializeClient("your_app_id", "your_app_secret_here");
            
            // 2. 现在可以开始使用TableHelper进行表格操作
            System.out.println("🎉 feishu-table-helper 初始化完成,可以开始使用了!");
            
            // 3. 创建表格Sheet
            String sheetId = FsHelper.create("员工Sheet", spreadsheetToken, Employee.class);
            
            // 4. 读取表格数据
             List<OceanEngineExcel> data = FsHelper.read(sheetId, spreadsheetToken, OceanEngineExcel.class);
             data.forEach(System.out::println);
            
            // 5. 写入表格数据
            Object write = FsHelper.write(sheetId, spreadsheetToken, data);
            
        } catch (FeishuTableException e) {
            System.err.println("初始化失败: " + e.getMessage());
            e.printStackTrace();
        }
    }
}

第一步:定义员工实体类

首先,我们创建一个 Employee 实体类,使用 @TableProperty 注解定义字段与飞书表格的映射关系:

java 复制代码
package com.company.model;

import io.github.larktablehelper.annotation.TableProperty;
import io.github.larktablehelper.enums.FieldType;
import java.time.LocalDate;
import java.math.BigDecimal;

/**
 * 员工信息实体类
 * 使用 @TableProperty 注解定义与飞书表格字段的映射关系
 */
public class Employee {
    
    /**
     * 员工姓名 - 文本类型字段
     * name: 在飞书表格中显示的字段名称
     * type: 字段类型,TEXT表示单行文本
     * required: 是否为必填字段
     */
    @TableProperty(name = "员工姓名", type = FieldType.TEXT)
    private String name;
    
    /**
     * 员工工号 - 文本类型,用作唯一标识
     * 设置为必填字段,确保每个员工都有唯一工号
     */
    @TableProperty(name = "员工工号", type = FieldType.TEXT)
    private String employeeId;
    
       /**
     * 基础文本字段 - 单行文本
     * 最常用的字段类型,用于存储简短的文本信息
     */
    @TableProperty(name = "员工姓名", type = FieldType.TEXT)
    private String name;
    
    /**
     * 多行文本字段 - 支持换行的长文本
     * 适用于逗号分割的数组类型数据
     */
    @TableProperty(name = "出行工具", type = FieldType.MULTI_TEXT)
    private List<String> cars;
    
    /**
     * URL字段 - 自动识别和验证链接
     */
    @TableProperty(name = "个人主页", type = FieldType.TEXT_URL)
    private String personalWebsite;
    
    /**
     * 性别
     * FEMALE("FEMALE", "女"),
     * MALE("MALE", "男"),
     * UNLIMITED("UNLIMITED", "不限");
     * 配合枚举类或者是自定义类 返回数据(创建表格时自动设置飞书下拉,读取数据时自动转为MALE)
     */
    @TableProperty(name = "性别", type = FieldType.SINGLE_SELECT, enumClass= GenderEnum.class)
    private String gende;
    
    /**
     * 电话字段
     * 跟单选相同
     */
    @TableProperty(name = "电话号码", type = FieldType.MULTI_SELECT, optionsClass = PhoneOptions.class)
    private List<String> phoneNumber;

    
    /**
     * 头像 - 图片(注:TEXT_FILE格式可读取任何文件数据,回写数据时,飞书只支持图片回写)
     *  预置 FileUrlProcess处理器,会下载图片至本地临时目录
     */
    @TableProperty(name = "头像", type = FieldType.TEXT_FILE, fieldFormatClass = FileUrlProcess.class))
    private String headImage;
    
}

@TableProperty注解详解:灵活配置字段映射

在前面的员工管理系统示例中,我们看到了 @TableProperty 注解的基本使用。现在让我们深入了解这个核心注解的所有参数和高级用法,掌握如何灵活配置字段映射以满足各种业务需求。

注解参数详细说明

@TableProperty 注解提供了丰富的配置选项,让你能够精确控制Java字段与飞书表格字段之间的映射关系。

完整参数列表

参数名 类型 默认值 必填 说明
name String "" 飞书表格中的字段名称
type FieldType TEXT 字段类型(文本、单选、多选等)
enumClass Class<?> Object.class 枚举类型映射
fieldFormatClass Class<?> Object.class 自定义格式化处理类
optionsClass Class<?> Object.class 选项值处理类
order int 0 字段在表格中的显示顺序

不同字段类型的配置示例

1. 文本类型字段

java 复制代码
public class TextFieldExamples {
    
    /**
     * 基础文本字段 - 单行文本
     * 最常用的字段类型,用于存储简短的文本信息
     */
    @TableProperty(name = "员工姓名", type = FieldType.TEXT)
    private String name;
    
    /**
     * 多行文本字段 - 支持换行的长文本
     * 适用于逗号分割的数组类型数据
     */
    @TableProperty(name = "出行工具", type = FieldType.MULTI_TEXT)
    private List<String> cars;
    
    /**
     * URL字段 - 自动识别和验证链接
     */
    @TableProperty(name = "个人主页", type = FieldType.TEXT_URL)
    private String personalWebsite;
    
    /**
     * 性别
     * FEMALE("FEMALE", "女"),
     * MALE("MALE", "男"),
     * UNLIMITED("UNLIMITED", "不限");
     * 配合枚举类或者是自定义类 返回数据(创建表格时自动设置飞书下拉,读取数据时自动转为MALE)
     */
    @TableProperty(name = "性别", type = FieldType.SINGLE_SELECT, enumClass= GenderEnum.class)
    private String gende;
    
    /**
     * 电话字段
     * 跟单选相同
     */
    @TableProperty(name = "电话号码", type = FieldType.MULTI_SELECT, optionsClass = PhoneOptions.class)
    private List<String> phoneNumber;

    
    /**
     * 头像 - 图片(注:TEXT_FILE格式可读取任何文件数据,回写数据时,飞书只支持图片回写)
     *  预置 FileUrlProcess处理器,会下载图片至本地临时目录
     */
    @TableProperty(name = "头像", type = FieldType.TEXT_FILE, fieldFormatClass = FileUrlProcess.class))
    private String headImage;
}

枚举类的使用方法和示例

使用枚举类可以让代码更加类型安全,同时提供更好的IDE支持和重构能力。

定义枚举类

java 复制代码
public enum GenderEnum implements BaseEnum {
    
    FEMALE("FEMALE", "女"),
    MALE("MALE", "男"),
    UNLIMITED("UNLIMITED", "不限");
    
    private final String code;
    private final String desc;
    
    GenderEnum(String code, String desc) {
        this.code = code;
        this.desc = desc;
    }
    
    public static GenderEnum getByCode(String code) {
        for (GenderEnum value : values()) {
            if (value.getCode().equals(code)) {
                return value;
            }
        }
        return null;
    }
} 

在实体类中使用枚举

java 复制代码
public class Employee {
    
      /**
     * 性别
     * 配合枚举类或者是自定义类 返回数据(创建表格时自动设置飞书下拉,读取数据时自动转为MALE)
     */
    @TableProperty(name = "性别", type = FieldType.SINGLE_SELECT, enumClass= GenderEnum.class)
    private String gende;
    
}

格式化处理类的高级用法

当内置的格式化选项无法满足需求时,可以通过自定义格式化处理类来实现复杂的数据转换逻辑。

自定义格式化处理器接口

java 复制代码
/**
 * 字段格式化处理器接口
 * 用于自定义字段值的格式化和解析逻辑
 */
public interface FieldValueProcess<T> {

    T process(Object value);

    /**
     * 反向处理,将枚举值转换为原始值
     */
     Object reverseProcess(Object value);
}

/**
 * 处理文件类型数据
 */
public class FileUrlProcess implements FieldValueProcess<String> {

    private static final Logger log = Logger.getLogger(FileUrlProcess.class.getName());

    @Override
    public String process(Object value) {
        if (value instanceof String) {
            return value.toString();
        }

        List<String> fileUrls = new ArrayList<>();
        if (value instanceof JsonArray) {
            JsonArray arr = (JsonArray) value;
            for (int i = 0; i < arr.size(); i++) {
                JsonElement jsonElement = arr.get(i);
                if (jsonElement.isJsonObject()) {
                    JsonObject jsonObject = jsonElement.getAsJsonObject();
                    String url = getUrlByTextFile(jsonObject);
                    fileUrls.add(url);
                }
            }
        } else if (value instanceof JsonObject) {
            JsonObject jsb = (JsonObject) value;
            String url = getUrlByTextFile(jsb);
            fileUrls.add(url);
        }
        return String.join(",", fileUrls);
    }

    @Override
    public Object reverseProcess(Object value) {
        if (value == null) {
            return null;
        } else {
            if (value instanceof String) {
                String path = value.toString();
                try {
                    FileData fileData = new FileData();
                    fileData.setFileUrl( path);
                    fileData.setFileType(FileUtil.isImageFile(path) ? "image" : "file");
                    fileData.setFileName(FileUtil.getFileName(path));
                    fileData.setImageData(FileUtil.getImageData(path));
                    return fileData;
                } catch (Exception e) {
                    FsLogger.error(ErrorCode.BUSINESS_LOGIC_ERROR,"【飞书表格】 文件上传-文件URL处理异常!" + e.getMessage(),  path, e);
                    return value;
                }

            } else {
                return value;
            }
        }
    }

    private synchronized String getUrlByTextFile(JsonObject jsb) {
        String url = "";
        String cellType = jsb.get("type").getAsString();

        switch (cellType) {
            case "url":
                String link = jsb.get("link").getAsString();
                if (link == null) {
                    url = jsb.get("text").getAsString();
                } else {
                    url = link;
                }
                break;
            case "embed-image":
                url = getImageOssUrl(jsb);
                break;
            case "attachment":
                url = getAttachmentOssUrl(jsb);
                break;
        }
        return url;
    }

    public static String getImageOssUrl(JsonObject jsb) {
        String url = "";
        String fileToken = jsb.get("fileToken").getAsString();

        String fileUuid = UUID.randomUUID().toString();
        String filePath = FileUtil.getRootPath() + File.separator + fileUuid + ".png";

        boolean isSuccess = true;
        try {
            FsApiUtil.downloadMaterial(fileToken, filePath , FsClient.getInstance().getClient(), null);
            url = filePath;
        } catch (Exception e) {
            FsLogger.warn("【飞书表格】 根据文件FileToken下载失败!fileToken: {}, e: {}", fileToken, e.getMessage());
            isSuccess = false;
        }

        if (!isSuccess) {
            String tmpUrl = FsApiUtil.downloadTmpMaterialUrl(fileToken, FsClient.getInstance().getClient());
            // 根据临时下载地址下载
            FileUtil.downloadFile(tmpUrl, filePath);
        }

        FsLogger.info("【飞书表格】 文件上传-飞书图片上传成功!fileToken: {}, filePath: {}", fileToken, filePath);
        return url;
    }

    public String getAttachmentOssUrl(JsonObject jsb) {
        String url = "";
        String token = jsb.get("fileToken").getAsString();
        String fileName = jsb.get("text").getAsString();

        String fileUuid = UUID.randomUUID().toString();
        String path = FileUtil.getRootPath() + File.separator + fileUuid + fileName;

        boolean isSuccess = true;
        try {
            FsApiUtil.downloadMaterial(token, path , FsClient.getInstance().getClient(), null);
            url = path;
        } catch (Exception e) {
            FsLogger.warn("【飞书表格】 附件-根据文件FileToken下载失败!fileToken: {}, e: {}", token, e.getMessage());
            isSuccess = false;
        }

        if (!isSuccess) {
            String tmpUrl = FsApiUtil.downloadTmpMaterialUrl(token, FsClient.getInstance().getClient());
            FileUtil.downloadFile(tmpUrl, path);
        }

        FsLogger.info("【飞书表格】 文件上传-附件上传成功!fileToken: {}, filePath: {}", token, path);
        return url;
    }
}

在实体类中使用自定义格式化器

java 复制代码
public class EmployeeWithFormatter {
    
        /**
     * 头像 - 图片(注:TEXT_FILE格式可读取任何文件数据,回写数据时,飞书只支持图片回写)
     *  预置 FileUrlProcess处理器,会下载图片至本地临时目录
     */
    @TableProperty(name = "头像", type = FieldType.TEXT_FILE, fieldFormatClass = FileUrlProcess.class))
    private String headImage;
    
}

选项处理类的使用示例

选项处理类用于动态生成选择字段的选项列表,特别适用于需要从数据库或外部API获取选项的场景。

选项处理器接口

java 复制代码
/**
 * 选项处理器接口
 * 用于动态生成选择字段的选项列表(主要是获取动态数据)
 */
public interface OptionsValueProcess<T> {

    T process();
}

实现选项处理器

java 复制代码
/**
 * 标签选项处理器
 * 从数据库动态获取标签列表
 */
public class TagOptionsProcess implements OptionsValueProcess<List<String>> {
    @Override
    public List<String> process() {

        // TODO: 接口获取标签数据,当前模拟数据
        List<String> tags = new ArrayList<>();
        tags.add("标签1");
        tags.add("标签2");
        tags.add("标签3");

        return tags;
    }
}

在实体类中使用选项处理器

java 复制代码
public class Employee {
    
    /**
     * 使用动态部门选项
     * optionHandler参数指定选项处理类
     */
    @TableProperty(
        name = "标签", 
        type = FieldType.MULTI_SELECT,
        optionClass = TagOptionsProcess.class
    )
    private String tags;
    
}

END

🔗 GitHub 项目地址

主项目仓库: github.com/luckday-cn/...

📚 相关资源

🏷️ Maven 坐标

xml 复制代码
<dependency>
    <groupId>cn.isliu</groupId>
    <artifactId>feishu-table-helper</artifactId>
    <version>0.0.2</version>
</dependency>
相关推荐
rannn_1118 分钟前
【Javaweb学习|黑马笔记|Day1】初识,入门网页,HTML-CSS|常见的标签和样式|标题排版和样式、正文排版和样式
css·后端·学习·html·javaweb
柏油16 分钟前
Spring @Cacheable 解读
redis·后端·spring
柏油1 小时前
Spring @TransactionalEventListener 解读
spring boot·后端·spring
不太可爱的叶某人2 小时前
【学习笔记】Java并发编程的艺术——第6章 Java并发容器和框架
java·笔记·学习
shark_chili3 小时前
面试官再问synchronized底层原理,这样回答让他眼前一亮!
后端
灵魂猎手3 小时前
2. MyBatis 参数处理机制:从 execute 方法到参数流转全解析
java·后端·源码
易元4 小时前
模式组合应用-桥接模式(一)
后端·设计模式
柑木4 小时前
隐私计算-SecretFlow/SCQL-SCQL的两种部署模式
后端·安全·数据分析
灵魂猎手4 小时前
1. Mybatis Mapper动态代理创建&实现
java·后端·源码