增加属于自己的cursorrules

This commit is contained in:
dengqichen 2024-12-02 14:57:39 +08:00
parent 2dcb8bd115
commit 5282716028
9 changed files with 432 additions and 28 deletions

View File

@ -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 开发规范
### 包结构说明

View File

@ -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": "操作成功"
}
```
## 技术栈
### 后端技术栈

View File

@ -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<Tenant, TenantDTO, Long, TenantQuery> {
@Resource
private ITenantService tenantService;
@Operation(summary = "获取状态")
@GetMapping("/{id}/enabled")
public Response<Boolean> getStatus(
@Parameter(description = "租户ID", required = true) @PathVariable Long id
) {
return Response.success(tenantService.getStatus(id));
}
@Operation(summary = "更新状态")
@PutMapping("/{id}/enabled")
public Response<Void> 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<TenantDTO> data) {
// TODO: 实现导出功能
}
// @GetMapping("/code/{code}")
// public Response<TenantResponse> findByCode(@PathVariable String code) {
// Tenant tenant = service.findByCode(code);
// return Response.success(converter.toResponse(tenant));
// }
}

View File

@ -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/**",

View File

@ -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<Tenant, TenantDTO, Long> {
// 添加租户特有的业务方法
/**
* 更新租户启用状态
*
* @param id 租户ID
* @param enabled 是否启用
*/
void updateStatus(Long id, boolean enabled);
/**
* 获取租户启用状态
*
* @param id 租户ID
* @return 是否启用
*/
boolean getStatus(Long id);
}

View File

@ -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<Tenant, TenantDTO, Long> implements ITenantService {
// public TenantServiceImpl(ITenantRepository repository, TenantConverter converter) {
// super(repository, converter);
// }
@Resource
private ITenantRepository tenantRepository;
// @Override
// public List<TenantDTO> findAll(BaseQuery query) {
// return null;
// }
@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();
}
}

View File

@ -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:

View File

@ -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));
}
}

View File

@ -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);
}
}