Merge pull request #3 from qianmoQ/restful-api

Restful api
This commit is contained in:
qianmoQ 2022-09-18 13:25:40 +08:00 committed by GitHub
commit b3fdfed790
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 699 additions and 2 deletions

View File

@ -3,7 +3,7 @@
commit_msg=$(cat "$1")
email=$(git config user.email)
msg_re="^(feature|fix|docs|style|refactor|perf|test|workflow|build|ci|env|release)(\(.+\))?: .{1,100}"
msg_re="^(feature|fix|docs|style|refactor|perf|test|workflow|build|ci|env|release|api|web)(\(.+\))?: .{1,100}"
if [[ ! $commit_msg =~ $msg_re ]]; then
printf "Invalid commit message submission format. Please use the correct format\

View File

@ -2,4 +2,4 @@
printf "Code verification before submission"
./mvnw clean checkstyle:checkstyle findbugs:findbugs cobertura:cobertura -X
./mvnw clean checkstyle:checkstyle findbugs:findbugs -X

16
pom.xml
View File

@ -18,6 +18,7 @@
<springboot.version>2.7.3</springboot.version>
<lombok.version>1.18.24</lombok.version>
<junit.version>4.13.2</junit.version>
<commons-lang3.version>3.12.0</commons-lang3.version>
<plugin.maven.checkstyle.version>3.0.0</plugin.maven.checkstyle.version>
<plugin.maven.findbugs.version>3.0.5</plugin.maven.findbugs.version>
<plugin.maven.compiler.version>3.3</plugin.maven.compiler.version>
@ -46,6 +47,21 @@
<artifactId>spring-boot-starter-web</artifactId>
<version>${springboot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<version>${springboot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>${springboot.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons-lang3.version}</version>
</dependency>
</dependencies>
</dependencyManagement>

View File

@ -13,6 +13,9 @@
<name>DataCap for server</name>
<properties>
<mysql.version>8.0.28</mysql.version>
<h2.version>2.1.214</h2.version>
<findbugs.version>3.0.1</findbugs.version>
<assembly-plugin.version>3.1.1</assembly-plugin.version>
</properties>
@ -21,6 +24,40 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<version>${springboot.version}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>${h2.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>findbugs</artifactId>
<version>${findbugs.version}</version>
</dependency>
</dependencies>
<build>

View File

@ -1 +1,5 @@
server.port=9096
# Set env for datasource
spring.datasource.url=jdbc:mysql://localhost:3306/datacap?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true&useSSL=false
spring.datasource.username=root
spring.datasource.password=12345678

View File

@ -0,0 +1,23 @@
package io.edurt.datacap.server.common;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class JSON
{
public static final ObjectMapper objectmapper = new ObjectMapper();
private JSON() {}
public static String toJSON(Object object)
{
String json;
try {
json = objectmapper.writeValueAsString(object);
}
catch (JsonProcessingException e) {
json = null;
}
return json;
}
}

View File

@ -0,0 +1,8 @@
package io.edurt.datacap.server.common;
public enum ProtocolEnum
{
HTTP,
HTTPS,
SSH
}

View File

@ -0,0 +1,46 @@
package io.edurt.datacap.server.common;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class Response<T>
{
private Boolean status;
private Integer code;
private String message;
private T data;
public static Response success(Object data)
{
Response response = new Response();
response.code = State.SUCCESS.getCode();
response.message = State.SUCCESS.getValue();
response.data = data;
response.status = true;
return response;
}
public static Response failure(String message)
{
Response response = new Response();
response.code = State.FAILURE.getCode();
response.message = message;
response.status = false;
return response;
}
public static Response failure(ServiceState state)
{
Response response = new Response();
response.code = state.getCode();
response.message = state.getValue();
response.status = false;
return response;
}
}

View File

@ -0,0 +1,25 @@
package io.edurt.datacap.server.common;
public enum ServiceState
{
SOURCE_NOT_FOUND(1001, "Source does not exist");
private Integer code;
private String value;
ServiceState(Integer code, String value)
{
this.code = code;
this.value = value;
}
public Integer getCode()
{
return code;
}
public String getValue()
{
return value;
}
}

View File

@ -0,0 +1,25 @@
package io.edurt.datacap.server.common;
public enum State
{
SUCCESS(200, "Query successful"),
FAILURE(400, "Query failure");
private Integer code;
private String value;
State(Integer code, String value)
{
this.code = code;
this.value = value;
}
public Integer getCode()
{
return code;
}
public String getValue()
{
return value;
}
}

View File

@ -0,0 +1,53 @@
package io.edurt.datacap.server.configure;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
@Configuration
@EnableJpaRepositories(basePackages = "io.edurt.datacap.server.repository",
repositoryImplementationPostfix = "Impl",
entityManagerFactoryRef = "entityManagerFactory",
transactionManagerRef = "transactionManager")
@EnableTransactionManagement
public class DataJpaConfigure
{
@Bean
public JpaVendorAdapter jpaVendorAdapter()
{
HibernateJpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter();
jpaVendorAdapter.setDatabase(Database.MYSQL);
jpaVendorAdapter.setShowSql(true);
jpaVendorAdapter.setDatabasePlatform("org.hibernate.dialect.MySQL5Dialect");
jpaVendorAdapter.setGenerateDdl(false);
return jpaVendorAdapter;
}
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource, JpaVendorAdapter jpaVendorAdapter)
{
LocalContainerEntityManagerFactoryBean managerFactoryBean = new LocalContainerEntityManagerFactoryBean();
managerFactoryBean.setDataSource(dataSource);
managerFactoryBean.setJpaVendorAdapter(jpaVendorAdapter);
managerFactoryBean.setPackagesToScan("io.edurt.datacap.server.entity");
return managerFactoryBean;
}
@Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory managerFactory)
{
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(managerFactory);
return transactionManager;
}
}

View File

@ -0,0 +1,55 @@
package io.edurt.datacap.server.controller;
import io.edurt.datacap.server.common.Response;
import io.edurt.datacap.server.entity.PageEntity;
import io.edurt.datacap.server.entity.SourceEntity;
import io.edurt.datacap.server.service.SourceService;
import io.edurt.datacap.server.validation.ValidationGroup;
import org.springframework.http.MediaType;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController()
@RequestMapping(value = "/api/v1/source")
public class SourceController
{
private final SourceService sourceService;
public SourceController(SourceService sourceService)
{
this.sourceService = sourceService;
}
@PostMapping(produces = {MediaType.APPLICATION_JSON_VALUE})
public Response<SourceEntity> save(@RequestBody @Validated(ValidationGroup.Crud.Create.class) SourceEntity configure)
{
return this.sourceService.saveOrUpdate(configure);
}
@PutMapping(produces = {MediaType.APPLICATION_JSON_VALUE})
public Response<SourceEntity> update(@RequestBody @Validated(ValidationGroup.Crud.Update.class) SourceEntity configure)
{
return this.sourceService.saveOrUpdate(configure);
}
@GetMapping
public Response<PageEntity<SourceEntity>> getAll(@RequestParam(value = "start", defaultValue = "1") int start,
@RequestParam(value = "size", defaultValue = "10") int end)
{
return this.sourceService.getAll(start, end);
}
@DeleteMapping(value = "{id}")
public Response<Long> delete(@PathVariable(value = "id") Long id)
{
return this.sourceService.delete(id);
}
}

View File

@ -0,0 +1,33 @@
package io.edurt.datacap.server.entity;
import lombok.Data;
import lombok.ToString;
import org.springframework.data.domain.Page;
import java.util.List;
@Data
@ToString
public class PageEntity<T>
{
private int page;
private int size;
private long total;
private long totalPage;
private List<T> content;
private PageEntity()
{
}
public static PageEntity build(Page page)
{
PageEntity pageEntity = new PageEntity<>();
pageEntity.setPage(page.getNumber());
pageEntity.setSize(page.getSize());
pageEntity.setTotal(page.getTotalElements());
pageEntity.setTotalPage(page.getTotalPages());
pageEntity.setContent(page.getContent());
return pageEntity;
}
}

View File

@ -0,0 +1,72 @@
package io.edurt.datacap.server.entity;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import io.edurt.datacap.server.common.ProtocolEnum;
import io.edurt.datacap.server.validation.ValidationGroup;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.sql.Timestamp;
@Data
@ToString
@NoArgsConstructor
@Entity
@Table(name = "source")
@org.hibernate.annotations.Table(appliesTo = "source", comment = "The storage is used to query the data connection source")
@SuppressFBWarnings(value = {"EI_EXPOSE_REP"},
justification = "I prefer to suppress these FindBugs warnings")
public class SourceEntity
{
@Id()
@GeneratedValue(strategy = GenerationType.IDENTITY)
@NotNull(groups = {ValidationGroup.Crud.Update.class}, message = "The passed source id cannot be empty")
private Long id;
@Column(name = "name", unique = true, nullable = false)
@NotEmpty(message = "The passed name cannot by empty")
private String name;
@Column(name = "description")
private String description;
@Column(name = "protocol", unique = true, nullable = false, columnDefinition = "varchar default 'HTTP'")
@Enumerated(EnumType.STRING)
@NotNull(message = "The passed protocol cannot by empty")
private ProtocolEnum protocol;
@Column(name = "host", unique = true, nullable = false)
@NotEmpty(message = "The passed host cannot by empty")
private String host;
@Column(name = "port", unique = true, nullable = false)
@NotNull(message = "The passed port cannot by empty")
private Integer port;
@Column(name = "username")
private String username;
@Column(name = "password")
private String password;
@Column(name = "_catalog")
private String catalog;
@Column(name = "_database")
private String database;
@Column(name = "create_time", columnDefinition = "datetime default CURRENT_TIMESTAMP()")
private Timestamp createTime;
}

View File

@ -0,0 +1,9 @@
package io.edurt.datacap.server.repository;
import io.edurt.datacap.server.entity.SourceEntity;
import org.springframework.data.repository.PagingAndSortingRepository;
public interface SourceRepository
extends PagingAndSortingRepository<SourceEntity, Long>
{
}

View File

@ -0,0 +1,14 @@
package io.edurt.datacap.server.service;
import io.edurt.datacap.server.common.Response;
import io.edurt.datacap.server.entity.PageEntity;
import io.edurt.datacap.server.entity.SourceEntity;
public interface SourceService
{
Response<SourceEntity> saveOrUpdate(SourceEntity configure);
Response<PageEntity<SourceEntity>> getAll(int offset, int limit);
Response<Long> delete(Long id);
}

View File

@ -0,0 +1,42 @@
package io.edurt.datacap.server.service.impl;
import io.edurt.datacap.server.common.Response;
import io.edurt.datacap.server.entity.PageEntity;
import io.edurt.datacap.server.entity.SourceEntity;
import io.edurt.datacap.server.repository.SourceRepository;
import io.edurt.datacap.server.service.SourceService;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
@Service
public class SourceServiceImpl
implements SourceService
{
private final SourceRepository sourceRepository;
public SourceServiceImpl(SourceRepository sourceRepository)
{
this.sourceRepository = sourceRepository;
}
@Override
public Response<SourceEntity> saveOrUpdate(SourceEntity configure)
{
return Response.success(this.sourceRepository.save(configure));
}
@Override
public Response<PageEntity<SourceEntity>> getAll(int offset, int limit)
{
Pageable pageable = PageRequest.of(offset, limit);
return Response.success(PageEntity.build(this.sourceRepository.findAll(pageable)));
}
@Override
public Response<Long> delete(Long id)
{
this.sourceRepository.deleteById(id);
return Response.success(id);
}
}

View File

@ -0,0 +1,27 @@
package io.edurt.datacap.server.validation;
import javax.validation.groups.Default;
public interface ValidationGroup
extends Default
{
interface Crud
extends ValidationGroup
{
interface Create
extends Crud
{}
interface Update
extends Crud
{}
interface Query
extends Crud
{}
interface Delete
extends Crud
{}
}
}

View File

@ -0,0 +1,22 @@
package io.edurt.datacap.server;
import io.edurt.datacap.server.common.ProtocolEnum;
import io.edurt.datacap.server.entity.SourceEntity;
public class BaseParamTest
{
private BaseParamTest()
{
}
public static SourceEntity builderSource()
{
SourceEntity source = new SourceEntity();
source.setName("Test");
source.setDescription("This is test source");
source.setHost("localhost");
source.setPort(3306);
source.setProtocol(ProtocolEnum.HTTP);
return source;
}
}

View File

@ -0,0 +1,111 @@
package io.edurt.datacap.server.controller;
import io.edurt.datacap.server.BaseParamTest;
import io.edurt.datacap.server.common.JSON;
import io.edurt.datacap.server.entity.SourceEntity;
import lombok.extern.slf4j.Slf4j;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.context.jdbc.SqlGroup;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
@RunWith(value = SpringRunner.class)
@SpringBootTest
@WebAppConfiguration
@Slf4j
public class SourceControllerTest
{
@Autowired
private SourceController sourceController;
private MockMvc mockMvc;
@Before
public void setup()
{
mockMvc = MockMvcBuilders.standaloneSetup(sourceController).build();
}
@Test
@Sql(value = "classpath:schema/source.sql")
public void save()
throws Exception
{
MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.post("/api/v1/source")
.contentType(MediaType.APPLICATION_JSON)
.content(JSON.objectmapper.writeValueAsString(BaseParamTest.builderSource())))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$.code").value(200))
.andDo(MockMvcResultHandlers.print())
.andReturn();
log.info(mvcResult.getResponse().getContentAsString());
}
@Test
@SqlGroup(value = {
@Sql(value = "classpath:schema/source.sql"),
@Sql(value = "classpath:data/source.sql")
})
public void update()
throws Exception
{
SourceEntity source = BaseParamTest.builderSource();
source.setName("TestSource_1");
source.setId(1L);
MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.put("/api/v1/source")
.contentType(MediaType.APPLICATION_JSON)
.content(JSON.objectmapper.writeValueAsString(source)))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$.code").value(200))
.andDo(MockMvcResultHandlers.print())
.andReturn();
log.info(mvcResult.getResponse().getContentAsString());
}
@Test
@SqlGroup(value = {
@Sql(value = "classpath:schema/source.sql"),
@Sql(value = "classpath:data/source.sql")
})
public void getAll()
throws Exception
{
MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.get("/api/v1/source")
.param("start", "1")
.param("end", "10"))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$.code").value(200))
.andDo(MockMvcResultHandlers.print())
.andReturn();
log.info(mvcResult.getResponse().getContentAsString());
}
@Test
@SqlGroup(value = {
@Sql(value = "classpath:schema/source.sql"),
@Sql(value = "classpath:data/source.sql")
})
public void delete()
throws Exception
{
Long id = Long.valueOf(1);
MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.delete("/api/v1/source/" + id))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$.code").value(200))
.andDo(MockMvcResultHandlers.print())
.andReturn();
log.info(mvcResult.getResponse().getContentAsString());
}
}

View File

@ -0,0 +1,30 @@
package io.edurt.datacap.server.repository;
import io.edurt.datacap.server.BaseParamTest;
import io.edurt.datacap.server.common.ProtocolEnum;
import io.edurt.datacap.server.entity.SourceEntity;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.context.junit4.SpringRunner;
import static org.assertj.core.api.Assertions.assertThat;
@RunWith(value = SpringRunner.class)
@DataJpaTest
public class SourceRepositoryTest
{
@Autowired
private SourceRepository sourceRepository;
@Test
@Sql(value = "classpath:schema/source.sql")
public void save()
{
SourceEntity source = BaseParamTest.builderSource();
this.sourceRepository.save(source);
assertThat(source.getId() > 0);
}
}

View File

@ -0,0 +1,28 @@
package io.edurt.datacap.server.service;
import io.edurt.datacap.server.BaseParamTest;
import io.edurt.datacap.server.common.ProtocolEnum;
import io.edurt.datacap.server.entity.SourceEntity;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.test.context.junit4.SpringRunner;
import static org.assertj.core.api.Assertions.assertThat;
@RunWith(value = SpringRunner.class)
@SpringBootTest
public class SourceServiceTest
{
@Autowired
private SourceService sourceService;
@Test
@Sql(value = "classpath:schema/source.sql")
public void saveOrUpdate()
{
assertThat(this.sourceService.saveOrUpdate(BaseParamTest.builderSource()).getStatus());
}
}

View File

@ -0,0 +1,2 @@
insert into source(_catalog, _database, description, host, name, password, port, protocol, username)
values ('default', 'datacap', 'This is description', 'localhost', 'TestSource', null, 3306, 'HTTP', 'default');

View File

@ -0,0 +1,15 @@
create table if not exists source
(
id bigint not null auto_increment,
_catalog varchar(255),
create_time datetime default CURRENT_TIMESTAMP(),
_database varchar(255),
description varchar(255),
host varchar(255) not null,
name varchar(255) not null,
password varchar(255),
port bigint not null,
protocol varchar(255),
username varchar(255),
primary key (id)
);