# 快速入门

这里将介绍如何下载并启动前后端工程,主要介绍后端Spring Boot工程引入Fly框架后对于不同模块的入门使用。

# 环境准备

在开始之前,我们需要先做如下准备,以满足工程基本的运行条件:

  1. JDK (opens new window) 建议使用 8 或 11;
  2. Maven (opens new window) 3.0 版本及以上;
  3. 集成开发环境(IDE),常见的开发工具有 IntelliJ IDEA、Visual Studio Code 和 Eclipse。

编写示例时,所用的环境为 JDK 1.8.0_231,Maven3.6.3 以及 IntelliJ IDEA,仅作参考。

# 下载工程

Openfuse提供了在线构建工具下载中心 (opens new window),在里面可以快速构建并预览下载多种模板工程,若你已有Spring Boot工程只需要引入Fly,可以跳过本小节。

【模板工程】

Fly Framework模板只包含了后端API工程,Fly Framwork + UI模板则同时包括基于Fly-vue (opens new window)的前端WEB工程,可以点击下方的预览按钮查看两者的区别。

Spring Boot版本】

Fly推荐使用及兼容的版本列表。

【后端工程配置】

Maven工程的基础配置。

【依赖】

用于调整后端工程所包含的依赖,当选择了Fly部分模块如Fly ORMFly RESTFly TEST,会在生成的工程中默认添加基本使用示例


这里选择Fly Framwork模板工程,配置调整完成后,点击下载压缩包到本地,解压即能得到我们所需的初始后端工程api,使用准备好的IDE打开api,工程结构如图:

因选择了h2依赖,在src/main/resources/application.yml中,可以看到有包含h2数据源的默认配置,在后续的开发API工程示例中,涉及数据访问,因此必须配置Spring数据源:

使用IDE启动SpringBootApplication,或在工程主目录下执行命令(注意检查pom.xml中的<java.version>):

mvn spring-boot:run

# 开发 API 工程

创建初始工程后,接下来将介绍Fly如何定义实体、根据实体定义进行数据表查询,以及通过实现interface快速生成该表的CRUD API等。

fly-orm模块基于spring-jdbcJdbcTemplate进行拓展,里面提供了基于对象关系映射设计的数据访问功能,Maven依赖如下:

<dependency>
    <groupId>cn.openfuse</groupId>
    <artifactId>fly-orm</artifactId>
    <version>${fly.version}</version>
</dependency>

fly-orm模块间接引入了fly-data模块,扫描及解析实体类。

# 创建实体

在工程src/main/java,与DemoApplication.java同级目录下创建一个类,用于定义实体:

.../entity/NewsEntity.java

import fly.core.data.annotation.Column;
import fly.core.data.annotation.CreatedAt;
import fly.core.data.annotation.Entity;
import fly.core.data.annotation.Filterable;
import fly.core.data.annotation.UUID;
import fly.core.data.annotation.UpdatedAt;
import java.util.Date;

@Entity
public class NewsEntity {

    @UUID
    protected String id;

    @Column
    @Filterable
    protected String title;

    @CreatedAt
    protected Date createdAt;

    @UpdatedAt
    protected Date updatedAt;

    @Column
    protected Boolean isRemoved;

    // get/set

}

在类上添加@Entity注解,表示该类为实体类;类属性添加@UUID@Column等注解,表示为实体的字段。更多实体定义的使用,可以查看实体定义文档。

示例中定义了一个名为News的实体,默认对应的数据库表名为news,每个为字段的属性对应一个表的列,列名默认为驼峰式属性名转下划线格式。由于fly-orm模块自动开启ddl数据库管理),添加实体类后在工程下次启动时,会在配置的数据源中检查并创建表和列。

# 数据访问

现已有News实体,添加单元测试试用通过实体进行数据访问,除了需要fly-orm模块外,还需引入fly-test模块:

<dependency>
    <groupId>cn.openfuse</groupId>
    <artifactId>fly-test</artifactId>
    <version>${fly.version}</version>
    <scope>test</scope>
</dependency>

src/test/java下创建AbstractNewsEntityTests.java

import com.example.demo.entity.NewsEntity;
import fly.orm.dao.Dao;
import fly.test.assertj.Assertions2;
import org.junit.jupiter.api.BeforeEach;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
public class AbstractNewsEntityTests extends Assertions2 {

    @Autowired
    protected Dao dao;

    /**
     * Delete all data before each test.
     */
    @BeforeEach
    public void beforeEach() {
        dao.careful().deleteAll(NewsEntity.class);
    }

}

NewsEntityTests.java

import com.example.demo.entity.NewsEntity;
import org.junit.jupiter.api.Test;

import java.util.List;

/**
 * Tests for {@link com.example.demo.entity.NewsEntity}.
 */
public class NewsEntityTests extends AbstractNewsEntityTests {

    @Test
    public void testCrudByDao() {
        NewsEntity news = new NewsEntity("News information");
        dao.insert(news);
        assertNotEmpty(news.getId());

        news.setTitle("A good news from Peter");
        dao.update(news);

        NewsEntity found = dao.mustFind(NewsEntity.class, news.getId());
        assertEquals(news.getTitle(), found.getTitle());

        List<NewsEntity> list = dao.createQuery(NewsEntity.class).filter("title", found.getTitle()).list();
        assertSize(list, 1);

        dao.mustDelete(NewsEntity.class, news.getId());
    }
    
    @Test
    public void testSQLByDao() {
        List<NewsEntity> list = dao.createSQLQuery("SELECT #id, #createdAt FROM [News]", NewsEntity.class).list();
        assertEmpty(list);

        int affected = dao.executeUpdate("DELETE FROM [News]");
        assertEquals(affected, 0);
    }

}

实体的数据访问,主要通过fly.orm.dao.Dao接口实现,可以注入使用。

testCrudByDao()中举例了基于实体的增删改查基础操作。在执行方法时指定实体类,会根据实体配置,如实体表名映射、字段列名映射生成SQL。

testSQLByDao()中使用了SQL模板,通过一定的语法,自动进行实体名、字段名转换。需要注意的是,SQL为更新、插入或者删除,都属于数据库的更新,都使用executeUpdate方法。

参考日志(logging.level.fly: DEBUG):

// dao.insert(news);
INSERT INTO news (id, title, created_at, updated_at) VALUES (?, ?, ?, ?)
Args: ['7fc9c4de-c20a-4ff7-b06b-23c587d2c5bb','News information','Thu Apr 07 13:32:55 CST 2022','Thu Apr 07 13:32:55 CST 2022']
1 rows affected

// dao.update(news);
UPDATE news SET title=?, updated_at=? WHERE id=?
Args: ['A good news from Peter','Thu Apr 07 13:32:55 CST 2022','7fc9c4de-c20a-4ff7-b06b-23c587d2c5bb']
1 rows affected

// dao.mustFind(NewsEntity.class, news.getId());
SELECT id, title, created_at, updated_at FROM news WHERE id=?
Args: ['7fc9c4de-c20a-4ff7-b06b-23c587d2c5bb']
record found

// dao.createQuery(NewsEntity.class).filter("title", found.getTitle()).list();
SELECT t.id, t.title, t.created_at, t.updated_at FROM news t WHERE t.title = ?
Args: ['A good news from Peter']
1 rows fetched

// dao.mustDelete(NewsEntity.class, news.getId());
DELETE FROM news WHERE id=?
Args: ['7fc9c4de-c20a-4ff7-b06b-23c587d2c5bb']
1 rows affected

// dao.createSQLQuery("SELECT #id, #createdAt FROM [News]", NewsEntity.class).list();
SELECT id, created_at FROM news
Args: []
0 rows fetched

// dao.executeUpdate("DELETE FROM [News]");
DELETE FROM news
Args: []

从日志中的rows affected可看出执行操作返回或影响的数据量。

更多使用见数据访问文档。

# CRUD APIs

fly-rest是为REST架构设计的模块,结合实体模型配置,能够快速开发符合RESTful风格的API,通过Controller实现接口快速生成对应实体的CRUD接口。

pom.xml中引入依赖:

<dependency>
    <groupId>cn.openfuse</groupId>
    <artifactId>fly-rest</artifactId>
    <version>${fly.version}</version>
</dependency>

在工程src/main/javaApplication同级目录下创建一个Controller

.../controller/NewsController.java

import com.example.demo.entity.NewsEntity;
import fly.core.data.model.Partial;
import fly.data.common.query.Filters;
import fly.data.common.query.Query;
import fly.rest.data.annotation.Crud;
import fly.rest.data.crud.CrudContext;
import fly.rest.data.crud.operation.*;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * Included APIs:
 * - GET /news - {@link FlexibleCrudQuery#query(CrudContext, Query)}
 * - GET /news/exists - {@link CrudExists#exists(CrudContext, Filters)}
 * - POST /news - {@link FlexibleCrudCreate#create(CrudContext, Partial)}
 * - GET /news/{id} - {@link FlexibleCrudFind#createFindQuery(CrudContext)}
 * - PATCH /news/{id} - {@link CrudUpdate#update(CrudContext, Partial)}
 * - DELETE /news/{id} - {@link CrudDelete#delete(CrudContext)}
 * - GET /example/{name} - {@link #example(String)}
 */
@RestController
@RequestMapping("/news")
@Crud(entityClass = NewsEntity.class)
public class NewsController implements FlexibleCrudQuery, CrudExists, FlexibleCrudCreate, FlexibleCrudFind, CrudUpdate,
        CrudDelete {

    public static final String HELLO_WORLD = "Hello World! ";

    /**
     * Example custom method.
     */
    @GetMapping("/example/{name}")
    public String example(@PathVariable("name") String name) {
        return HELLO_WORLD + name;
    }

}

快速生成对应实体的CRUD API,主要分为两步:

  • Controller类上添加@Crud注解指定实体类;

  • 实现示例中框架提供的CRUD接口。

如示例类中实现的FlexibleCrudQuery接口,会在Controller类的@RequestMapping("/news")路径上再生成GET接口,即最终生成的接口为GET /news

从示例类注解中总览Controller包含的API,重启工程,可以使用Postman等工具测试接口是否可用,或编写单元测试调试。

为了可视单元测试请求日志,在application.yml中增加以下配置:

logging:
  level:
    "org.springframework.web.client": DEBUG # 输出请求类型、完整路径及参数
    org.apache.coyote.http11.Http11InputBuffer: DEBUG # 包括请求头等
    "org.springframework.web.servlet.DispatcherServlet": DEBUG # 包括请求耗时等

src/test/java下创建NewsControllerTests.java

import com.example.demo.controller.NewsController;
import com.example.demo.entity.NewsEntity;
import fly.lang.New;
import fly.test.rest.client.TRestResource;
import fly.test.rest.client.TRestTemplate;
import fly.test.rest.client.model.TQuery;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;

import javax.annotation.PostConstruct;
import java.util.List;

/**
 * Tests for {@link com.example.demo.controller.NewsController}.
 */
public class NewsControllerTests extends AbstractNewsEntityTests {

    @Autowired
    protected TRestTemplate rest;

    private static TRestResource news;

    @PostConstruct
    protected void postInit() {
        news = rest.resource("news");
    }

    @Test
    public void testExample() {
        String name = "Fly";
        TRestTemplate template = news.rest("/example/{name}");
        String result = template.getFor200("", name);
        assertEquals(NewsController.HELLO_WORLD + name, result);
    }

    @Test
    public void testCrudByRest() {
        NewsEntity created = news.createFor200(New.hashMap("title", "News Information"), NewsEntity.class);
        assertNotEmpty(created.getId());

        news.patchUpdate(created.getId(), New.hashMap("title", "A good news from Peter"));

        NewsEntity found = news.find(NewsEntity.class, created.getId());
        assertEquals("A good news from Peter", found.getTitle());

        List<NewsEntity> list = news.queryList(NewsEntity.class, new TQuery().filter("title", "A good news from Peter"));
        assertSize(list, 1);

        news.delete(created.getId());
    }

}

模块提供的测试请求类简化了很多如拼接请求路径、返回数据格式转换等操作流程。运行testCrudByRest()单元测试方法,能在IDE控制台中看到实际发送的请求信息:

...RestTemplate              : HTTP POST http://localhost:8080/news
...RestTemplate              : Accept=[application/json, application/*+json]
...RestTemplate              : Writing [{title=News Information}] with org.springframework.http.converter.json.MappingJackson2HttpMessageConverter

...

# 查看 API 文档

现已有News实体相关API,引入springdoc依赖,可以输出API文档:

<dependency>
    <groupId>cn.openfuse</groupId>
    <artifactId>fly-starter-rest-springdoc</artifactId>
    <version>${fly.version}</version>
</dependency>

访问http://localhost:8080/v3/api-docs (opens new window)查看OpenAPI Json(图片仅作参考):

引入swaggerui依赖,可视化API资源:

<dependency>
    <groupId>cn.openfuse</groupId>
    <artifactId>fly-starter-rest-swaggerui</artifactId>
    <version>${fly.version}</version>
</dependency>

访问http://localhost:8080/swagger-ui.html (opens new window)(图片仅作参考):

更多文档使用配置,可见API 文档

顶部