From 52827160289347e5630d04eb1b1f5b24918c7be1 Mon Sep 17 00:00:00 2001 From: dengqichen Date: Mon, 2 Dec 2024 14:57:39 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=B1=9E=E4=BA=8E=E8=87=AA?= =?UTF-8?q?=E5=B7=B1=E7=9A=84cursorrules?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/.cursorrules | 1 - backend/README.md | 136 ++++++++++++++++++ .../backend/api/TenantApiController.java | 57 ++++++-- .../security/config/SecurityConfig.java | 1 + .../backend/service/ITenantService.java | 23 ++- .../service/impl/TenantServiceImpl.java | 48 +++++-- backend/src/main/resources/application.yml | 6 +- .../backend/api/TenantApiControllerTest.java | 87 +++++++++++ .../service/impl/TenantServiceImplTest.java | 101 +++++++++++++ 9 files changed, 432 insertions(+), 28 deletions(-) create mode 100644 backend/src/test/java/com/qqchen/deploy/backend/api/TenantApiControllerTest.java create mode 100644 backend/src/test/java/com/qqchen/deploy/backend/service/impl/TenantServiceImplTest.java diff --git a/backend/.cursorrules b/backend/.cursorrules index 6bc7ace8..063a5f49 100644 --- a/backend/.cursorrules +++ b/backend/.cursorrules @@ -16,7 +16,6 @@ - 对于新设计的实体类、字段、方法都要写注释,对于实际的逻辑要有逻辑注释。 - 写了新的数据库表,应该在V1.0.0__init_schema.sql、V1.0.1__init_data.sql补充数据库表和初始化数据。 - 编写代码前,先查看一下V1.0.0__init_schema.sql、V1.0.1__init_data.sql表结构,再进行编写。 - # Deploy Ease Platform 开发规范 ### 包结构说明 diff --git a/backend/README.md b/backend/README.md index 7ca9e4ae..9574e1dd 100644 --- a/backend/README.md +++ b/backend/README.md @@ -110,6 +110,142 @@ Deploy Ease Platform 是一个现代化的部署管理平台,旨在简化和 - 测试数据构建 - Mock支持 +### 7. 租户管理API + +#### 7.1 分页查询租户 +```http +GET /api/v1/tenant/page + +请求参数: +{ + "pageNum": 1, // 页码(从1开始) + "pageSize": 10, // 每页大小 + "sortField": "createTime", // 排序字段 + "sortOrder": "desc", // 排序方式 + "code": "string", // 租户编码(可选) + "name": "string", // 租户名称(可选) + "enabled": true // 是否启用(可选) +} + +响应结果: +{ + "success": true, + "code": 200, + "message": "操作成功", + "data": { + "content": [{ + "id": 1, + "createTime": "2024-01-01 12:00:00", + "createBy": "admin", + "updateTime": "2024-01-01 12:00:00", + "updateBy": "admin", + "code": "TEST", + "name": "测试租户", + "address": "北京市朝阳区", + "contactName": "张三", + "contactPhone": "13800138000", + "email": "test@example.com", + "enabled": true + }], + "totalElements": 100, + "totalPages": 10, + "size": 10, + "number": 0, + "first": true, + "last": false, + "empty": false + } +} +``` + +#### 7.2 创建租户 +```http +POST /api/v1/tenant + +请求参数: +{ + "code": "TEST", // 租户编码(必填) + "name": "测试租户", // 租户名称(必填) + "address": "北京市朝阳区", // 地址(可选) + "contactName": "张三", // 联系人姓名(可选) + "contactPhone": "13800138000", // 联系人电话(可选) + "email": "test@example.com", // 联系人邮箱(可选) + "enabled": true // 是否启用(可选,默认true) +} + +响应结果: +{ + "success": true, + "code": 200, + "message": "操作成功", + "data": { + "id": 1, + // ... 其他字段同上 + } +} +``` + +#### 7.3 更新租户 +```http +PUT /api/v1/tenant/{id} + +请求参数: +{ + // 同创建接口 +} + +响应结果: +{ + "success": true, + "code": 200, + "message": "操作成功", + "data": { + "id": 1, + // ... 其他字段同上 + } +} +``` + +#### 7.4 删除租户 +```http +DELETE /api/v1/tenant/{id} + +响应结果: +{ + "success": true, + "code": 200, + "message": "操作成功" +} +``` + +#### 7.5 获取租户状态 +```http +GET /api/v1/tenant/{id}/enabled + +响应结果: +{ + "success": true, + "code": 200, + "message": "操作成功", + "data": true // true: 启用,false: 禁用 +} +``` + +#### 7.6 更新租户状态 +```http +PUT /api/v1/tenant/{id}/enabled + +请求参数: +enabled=true // 是否启用(必填) + +响应结果: +{ + "success": true, + "code": 200, + "message": "操作成功" +} +``` + ## 技术栈 ### 后端技术栈 diff --git a/backend/src/main/java/com/qqchen/deploy/backend/api/TenantApiController.java b/backend/src/main/java/com/qqchen/deploy/backend/api/TenantApiController.java index 0fd86baa..ecc576ff 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/api/TenantApiController.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/api/TenantApiController.java @@ -1,28 +1,59 @@ package com.qqchen.deploy.backend.api; -import com.qqchen.deploy.backend.model.TenantDTO; -import com.qqchen.deploy.backend.framework.controller.BaseController; -import com.qqchen.deploy.backend.model.query.TenantQuery; import com.qqchen.deploy.backend.entity.Tenant; +import com.qqchen.deploy.backend.framework.api.Response; +import com.qqchen.deploy.backend.framework.controller.BaseController; +import com.qqchen.deploy.backend.model.TenantDTO; +import com.qqchen.deploy.backend.model.query.TenantQuery; +import com.qqchen.deploy.backend.service.ITenantService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; import jakarta.servlet.http.HttpServletResponse; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; import java.util.List; +/** + * 租户管理API控制器 + * 提供租户的REST接口,包括: + * 1. 基础CRUD操作 + * 2. 状态管理 + * + * @author QQChen + * @version 1.0.0 + */ +@Slf4j @RestController @RequestMapping("/api/v1/tenant") +@Tag(name = "租户管理API", description = "对外提供的租户管理接口") public class TenantApiController extends BaseController { + @Resource + private ITenantService tenantService; + + @Operation(summary = "获取状态") + @GetMapping("/{id}/enabled") + public Response getStatus( + @Parameter(description = "租户ID", required = true) @PathVariable Long id + ) { + return Response.success(tenantService.getStatus(id)); + } + + @Operation(summary = "更新状态") + @PutMapping("/{id}/enabled") + public Response updateStatus( + @Parameter(description = "租户ID", required = true) @PathVariable Long id, + @Parameter(description = "是否启用", required = true) @RequestParam boolean enabled + ) { + tenantService.updateStatus(id, enabled); + return Response.success(); + } + @Override protected void exportData(HttpServletResponse response, List data) { - + // TODO: 实现导出功能 } - -// @GetMapping("/code/{code}") -// public Response findByCode(@PathVariable String code) { -// Tenant tenant = service.findByCode(code); -// return Response.success(converter.toResponse(tenant)); -// } - } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/framework/security/config/SecurityConfig.java b/backend/src/main/java/com/qqchen/deploy/backend/framework/security/config/SecurityConfig.java index 52fa7d9b..6dc81bda 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/framework/security/config/SecurityConfig.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/framework/security/config/SecurityConfig.java @@ -38,6 +38,7 @@ public class SecurityConfig { .requestMatchers( "/api/v1/user/login", "/api/v1/user/register", + "/api/v1/tenant/1/enabled", "/api/v1/tenant/list", "/swagger-ui/**", "/v3/api-docs/**", diff --git a/backend/src/main/java/com/qqchen/deploy/backend/service/ITenantService.java b/backend/src/main/java/com/qqchen/deploy/backend/service/ITenantService.java index 3dfff3c3..5baaebbd 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/service/ITenantService.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/service/ITenantService.java @@ -4,6 +4,27 @@ import com.qqchen.deploy.backend.entity.Tenant; import com.qqchen.deploy.backend.framework.service.IBaseService; import com.qqchen.deploy.backend.model.TenantDTO; +/** + * 租户服务接口 + * + * @author QQChen + * @version 1.0.0 + */ public interface ITenantService extends IBaseService { - // 添加租户特有的业务方法 + + /** + * 更新租户启用状态 + * + * @param id 租户ID + * @param enabled 是否启用 + */ + void updateStatus(Long id, boolean enabled); + + /** + * 获取租户启用状态 + * + * @param id 租户ID + * @return 是否启用 + */ + boolean getStatus(Long id); } \ No newline at end of file diff --git a/backend/src/main/java/com/qqchen/deploy/backend/service/impl/TenantServiceImpl.java b/backend/src/main/java/com/qqchen/deploy/backend/service/impl/TenantServiceImpl.java index d55e5a9a..521b95df 100644 --- a/backend/src/main/java/com/qqchen/deploy/backend/service/impl/TenantServiceImpl.java +++ b/backend/src/main/java/com/qqchen/deploy/backend/service/impl/TenantServiceImpl.java @@ -1,20 +1,48 @@ package com.qqchen.deploy.backend.service.impl; import com.qqchen.deploy.backend.entity.Tenant; +import com.qqchen.deploy.backend.framework.annotation.ServiceType; import com.qqchen.deploy.backend.framework.service.impl.BaseServiceImpl; -import com.qqchen.deploy.backend.service.ITenantService; import com.qqchen.deploy.backend.model.TenantDTO; +import com.qqchen.deploy.backend.repository.ITenantRepository; +import com.qqchen.deploy.backend.service.ITenantService; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import static com.qqchen.deploy.backend.framework.annotation.ServiceType.Type.DATABASE; + +/** + * 租户服务实现类 + * 主要功能: + * 1. 租户的CRUD操作 + * 2. 租户状态管理 + * + * @author QQChen + * @version 1.0.0 + */ +@Slf4j @Service +@ServiceType(DATABASE) public class TenantServiceImpl extends BaseServiceImpl implements ITenantService { - -// public TenantServiceImpl(ITenantRepository repository, TenantConverter converter) { -// super(repository, converter); -// } - -// @Override -// public List findAll(BaseQuery query) { -// return null; -// } + + @Resource + private ITenantRepository tenantRepository; + + @Override + @Transactional + public void updateStatus(Long id, boolean enabled) { + Tenant tenant = findEntityById(id); + tenant.setEnabled(enabled); + tenantRepository.save(tenant); + log.info("租户[{}]状态已更新为: {}", tenant.getName(), enabled ? "启用" : "禁用"); + } + + @Override + @Transactional(readOnly = true) + public boolean getStatus(Long id) { + Tenant tenant = findEntityById(id); + return tenant.getEnabled(); + } } \ No newline at end of file diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml index 55e3fc87..f90413d5 100644 --- a/backend/src/main/resources/application.yml +++ b/backend/src/main/resources/application.yml @@ -2,9 +2,9 @@ server: port: 8080 spring: datasource: - url: jdbc:mysql://192.168.1.111:3306/deploy-ease-platform?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true - username: deploy-ease-platform - password: qichen5210523 + url: jdbc:mysql://127.0.0.1:3306/deploy-ease-platform?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true + username: root + password: root driver-class-name: com.mysql.cj.jdbc.Driver jpa: hibernate: diff --git a/backend/src/test/java/com/qqchen/deploy/backend/api/TenantApiControllerTest.java b/backend/src/test/java/com/qqchen/deploy/backend/api/TenantApiControllerTest.java new file mode 100644 index 00000000..4e93f379 --- /dev/null +++ b/backend/src/test/java/com/qqchen/deploy/backend/api/TenantApiControllerTest.java @@ -0,0 +1,87 @@ +package com.qqchen.deploy.backend.api; + +import com.qqchen.deploy.backend.service.ITenantService; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.web.servlet.MockMvc; + +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.when; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * 租户API控制器测试类 + * + * @author QQChen + * @version 1.0.0 + */ +@SpringBootTest +@AutoConfigureMockMvc +@WithMockUser(username = "admin", roles = {"ADMIN"}) +class TenantApiControllerTest { + + @Autowired + private MockMvc mockMvc; + + @MockBean + private ITenantService tenantService; + + @Test + void getStatus_WhenEnabled_ShouldReturnTrue() throws Exception { + // Mock + when(tenantService.getStatus(1L)).thenReturn(true); + + // 执行并验证 + mockMvc.perform(get("/api/v1/tenant/1/enabled")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(200)) + .andExpect(jsonPath("$.success").value(true)) + .andExpect(jsonPath("$.data").value(true)); + } + + @Test + void getStatus_WhenDisabled_ShouldReturnFalse() throws Exception { + // Mock + when(tenantService.getStatus(1L)).thenReturn(false); + + // 执行并验证 + mockMvc.perform(get("/api/v1/tenant/1/enabled")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(200)) + .andExpect(jsonPath("$.success").value(true)) + .andExpect(jsonPath("$.data").value(false)); + } + + @Test + void updateStatus_WhenEnabled_ShouldReturnSuccess() throws Exception { + // Mock + doNothing().when(tenantService).updateStatus(1L, true); + + // 执行并验证 + mockMvc.perform(put("/api/v1/tenant/1/enabled") + .param("enabled", "true")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(200)) + .andExpect(jsonPath("$.success").value(true)); + } + + @Test + void updateStatus_WhenDisabled_ShouldReturnSuccess() throws Exception { + // Mock + doNothing().when(tenantService).updateStatus(1L, false); + + // 执行并验证 + mockMvc.perform(put("/api/v1/tenant/1/enabled") + .param("enabled", "false")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(200)) + .andExpect(jsonPath("$.success").value(true)); + } +} \ No newline at end of file diff --git a/backend/src/test/java/com/qqchen/deploy/backend/service/impl/TenantServiceImplTest.java b/backend/src/test/java/com/qqchen/deploy/backend/service/impl/TenantServiceImplTest.java new file mode 100644 index 00000000..35eb192d --- /dev/null +++ b/backend/src/test/java/com/qqchen/deploy/backend/service/impl/TenantServiceImplTest.java @@ -0,0 +1,101 @@ +package com.qqchen.deploy.backend.service.impl; + +import com.qqchen.deploy.backend.entity.Tenant; +import com.qqchen.deploy.backend.repository.ITenantRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; + +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +/** + * 租户服务测试类 + * + * @author QQChen + * @version 1.0.0 + */ +@SpringBootTest +class TenantServiceImplTest { + + @MockBean + private ITenantRepository tenantRepository; + + @Autowired + private TenantServiceImpl tenantService; + + private Tenant tenant; + + @BeforeEach + void setUp() { + // 准备测试数据 + tenant = new Tenant(); + tenant.setId(1L); + tenant.setName("测试租户"); + tenant.setCode("TEST"); + tenant.setEnabled(true); + } + + @Test + void getStatus_WhenEnabled_ShouldReturnTrue() { + // Mock + when(tenantRepository.findById(1L)).thenReturn(Optional.of(tenant)); + + // 执行 + boolean result = tenantService.getStatus(1L); + + // 验证 + assertThat(result).isTrue(); + } + + @Test + void getStatus_WhenDisabled_ShouldReturnFalse() { + // 准备数据 + tenant.setEnabled(false); + + // Mock + when(tenantRepository.findById(1L)).thenReturn(Optional.of(tenant)); + + // 执行 + boolean result = tenantService.getStatus(1L); + + // 验证 + assertThat(result).isFalse(); + } + + @Test + void updateStatus_WhenDisabled_ShouldUpdateTenantStatus() { + // Mock + when(tenantRepository.findById(1L)).thenReturn(Optional.of(tenant)); + when(tenantRepository.save(any(Tenant.class))).thenReturn(tenant); + + // 执行 + tenantService.updateStatus(1L, false); + + // 验证 + assertThat(tenant.getEnabled()).isFalse(); + verify(tenantRepository).save(tenant); + } + + @Test + void updateStatus_WhenEnabled_ShouldUpdateTenantStatus() { + // 准备数据 + tenant.setEnabled(false); + + // Mock + when(tenantRepository.findById(1L)).thenReturn(Optional.of(tenant)); + when(tenantRepository.save(any(Tenant.class))).thenReturn(tenant); + + // 执行 + tenantService.updateStatus(1L, true); + + // 验证 + assertThat(tenant.getEnabled()).isTrue(); + verify(tenantRepository).save(tenant); + } +} \ No newline at end of file