diff --git a/backend/mci-server/pom.xml b/backend/mci-server/pom.xml index f0eb930..8b8b8e4 100644 --- a/backend/mci-server/pom.xml +++ b/backend/mci-server/pom.xml @@ -36,6 +36,11 @@ com.baomidou mybatis-plus-boot-starter + + org.mybatis + mybatis-spring + 3.0.4 + com.mysql mysql-connector-j @@ -58,6 +63,12 @@ spring-boot-starter-test test + + org.springframework.boot + spring-boot-webmvc-test + 4.1.0 + test + diff --git a/backend/mci-server/src/main/java/com/sino/mci/admin/controller/AccountController.java b/backend/mci-server/src/main/java/com/sino/mci/admin/controller/AccountController.java new file mode 100644 index 0000000..4e51ff6 --- /dev/null +++ b/backend/mci-server/src/main/java/com/sino/mci/admin/controller/AccountController.java @@ -0,0 +1,37 @@ +package com.sino.mci.admin.controller; + +import com.sino.mci.admin.dto.CreateTenantRequest; +import com.sino.mci.admin.dto.CreateUserRequest; +import com.sino.mci.admin.dto.LoginRequest; +import com.sino.mci.admin.service.AccountService; +import com.sino.mci.shared.web.ApiResponse; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/v1/account") +@RequiredArgsConstructor +public class AccountController { + + private final AccountService accountService; + + @PostMapping("/tenant") + public ApiResponse createTenant(@Valid @RequestBody CreateTenantRequest request) { + return ApiResponse.ok(accountService.createTenant(request)); + } + + @PostMapping("/user") + public ApiResponse createUser( + @RequestHeader("X-Tenant-Code") String tenantCode, + @Valid @RequestBody CreateUserRequest request) { + return ApiResponse.ok(accountService.createUser(tenantCode, request)); + } + + @PostMapping("/login") + public ApiResponse login( + @RequestHeader("X-Tenant-Code") String tenantCode, + @Valid @RequestBody LoginRequest request) { + return ApiResponse.ok(accountService.login(tenantCode, request.getUsername(), request.getPassword())); + } +} diff --git a/backend/mci-server/src/main/java/com/sino/mci/admin/dto/CreateTenantRequest.java b/backend/mci-server/src/main/java/com/sino/mci/admin/dto/CreateTenantRequest.java new file mode 100644 index 0000000..a3cf092 --- /dev/null +++ b/backend/mci-server/src/main/java/com/sino/mci/admin/dto/CreateTenantRequest.java @@ -0,0 +1,16 @@ +package com.sino.mci.admin.dto; + +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +@Data +public class CreateTenantRequest { + + @NotBlank + private String tenantCode; + + @NotBlank + private String tenantName; + + private String inboundWebhookUrl; +} diff --git a/backend/mci-server/src/main/java/com/sino/mci/admin/dto/CreateUserRequest.java b/backend/mci-server/src/main/java/com/sino/mci/admin/dto/CreateUserRequest.java new file mode 100644 index 0000000..1ed3340 --- /dev/null +++ b/backend/mci-server/src/main/java/com/sino/mci/admin/dto/CreateUserRequest.java @@ -0,0 +1,20 @@ +package com.sino.mci.admin.dto; + +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +import java.util.List; + +@Data +public class CreateUserRequest { + + @NotBlank + private String username; + + @NotBlank + private String password; + + private String realName; + private String phone; + private List roleCodes; +} diff --git a/backend/mci-server/src/main/java/com/sino/mci/admin/dto/LoginRequest.java b/backend/mci-server/src/main/java/com/sino/mci/admin/dto/LoginRequest.java new file mode 100644 index 0000000..e0c20dd --- /dev/null +++ b/backend/mci-server/src/main/java/com/sino/mci/admin/dto/LoginRequest.java @@ -0,0 +1,14 @@ +package com.sino.mci.admin.dto; + +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +@Data +public class LoginRequest { + + @NotBlank + private String username; + + @NotBlank + private String password; +} diff --git a/backend/mci-server/src/main/java/com/sino/mci/admin/service/AccountService.java b/backend/mci-server/src/main/java/com/sino/mci/admin/service/AccountService.java new file mode 100644 index 0000000..d50154f --- /dev/null +++ b/backend/mci-server/src/main/java/com/sino/mci/admin/service/AccountService.java @@ -0,0 +1,79 @@ +package com.sino.mci.admin.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.sino.mci.admin.dto.CreateTenantRequest; +import com.sino.mci.admin.dto.CreateUserRequest; +import com.sino.mci.admin.entity.PlatformRole; +import com.sino.mci.admin.entity.PlatformUser; +import com.sino.mci.admin.entity.Tenant; +import com.sino.mci.admin.entity.UserRole; +import com.sino.mci.admin.repository.PlatformRoleMapper; +import com.sino.mci.admin.repository.PlatformUserMapper; +import com.sino.mci.admin.repository.TenantMapper; +import com.sino.mci.admin.repository.UserRoleMapper; +import lombok.RequiredArgsConstructor; +import org.springframework.security.crypto.bcrypt.BCrypt; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class AccountService { + + private final TenantMapper tenantMapper; + private final PlatformUserMapper userMapper; + private final PlatformRoleMapper roleMapper; + private final UserRoleMapper userRoleMapper; + + @Transactional + public Tenant createTenant(CreateTenantRequest request) { + Tenant tenant = new Tenant(); + tenant.setTenantCode(request.getTenantCode()); + tenant.setTenantName(request.getTenantName()); + tenant.setInboundWebhookUrl(request.getInboundWebhookUrl()); + tenant.setStatus(1); + tenantMapper.insert(tenant); + return tenant; + } + + @Transactional + public PlatformUser createUser(String tenantCode, CreateUserRequest request) { + PlatformUser user = new PlatformUser(); + user.setTenantCode(tenantCode); + user.setUsername(request.getUsername()); + user.setPassword(BCrypt.hashpw(request.getPassword(), BCrypt.gensalt())); + user.setRealName(request.getRealName()); + user.setPhone(request.getPhone()); + user.setStatus(1); + userMapper.insert(user); + + if (!CollectionUtils.isEmpty(request.getRoleCodes())) { + for (String roleCode : request.getRoleCodes()) { + PlatformRole role = roleMapper.selectOne( + new LambdaQueryWrapper().eq(PlatformRole::getRoleCode, roleCode)); + if (role != null) { + UserRole userRole = new UserRole(); + userRole.setUserId(user.getId()); + userRole.setRoleId(role.getId()); + userRoleMapper.insert(userRole); + } + } + } + return user; + } + + public PlatformUser login(String tenantCode, String username, String password) { + PlatformUser user = userMapper.selectOne( + new LambdaQueryWrapper() + .eq(PlatformUser::getTenantCode, tenantCode) + .eq(PlatformUser::getUsername, username) + .eq(PlatformUser::getStatus, 1)); + if (user == null || !BCrypt.checkpw(password, user.getPassword())) { + throw new RuntimeException("用户名或密码错误"); + } + return user; + } +} diff --git a/backend/mci-server/src/main/java/com/sino/mci/config/MybatisPlusConfig.java b/backend/mci-server/src/main/java/com/sino/mci/config/MybatisPlusConfig.java index a8ee337..100f710 100644 --- a/backend/mci-server/src/main/java/com/sino/mci/config/MybatisPlusConfig.java +++ b/backend/mci-server/src/main/java/com/sino/mci/config/MybatisPlusConfig.java @@ -1,9 +1,28 @@ package com.sino.mci.config; +import com.baomidou.mybatisplus.core.config.GlobalConfig; +import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; +import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean; +import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.annotation.MapperScan; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import javax.sql.DataSource; + @Configuration @MapperScan("com.sino.mci.admin.repository") public class MybatisPlusConfig { + + @Bean + public SqlSessionFactory sqlSessionFactory(DataSource dataSource, MetaObjectHandler metaObjectHandler) throws Exception { + MybatisSqlSessionFactoryBean factoryBean = new MybatisSqlSessionFactoryBean(); + factoryBean.setDataSource(dataSource); + + GlobalConfig globalConfig = new GlobalConfig(); + globalConfig.setMetaObjectHandler(metaObjectHandler); + factoryBean.setGlobalConfig(globalConfig); + + return factoryBean.getObject(); + } } diff --git a/backend/mci-server/src/test/java/com/sino/mci/admin/controller/AccountControllerTest.java b/backend/mci-server/src/test/java/com/sino/mci/admin/controller/AccountControllerTest.java new file mode 100644 index 0000000..ec7e7ea --- /dev/null +++ b/backend/mci-server/src/test/java/com/sino/mci/admin/controller/AccountControllerTest.java @@ -0,0 +1,35 @@ +package com.sino.mci.admin.controller; + +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.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; + +import java.util.UUID; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@AutoConfigureMockMvc +public class AccountControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Test + void shouldCreateTenant() throws Exception { + String tenantCode = "test-" + UUID.randomUUID().toString().substring(0, 8); + String payload = String.format("{\"tenantCode\":\"%s\",\"tenantName\":\"测试租户\"}", tenantCode); + mockMvc.perform(post("/msg-platform/api/v1/account/tenant") + .contextPath("/msg-platform") + .contentType(MediaType.APPLICATION_JSON) + .content(payload)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value(0)) + .andExpect(jsonPath("$.data.tenantCode").value(tenantCode)); + } +} diff --git a/backend/mci-server/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/AutoConfigureMockMvc.java b/backend/mci-server/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/AutoConfigureMockMvc.java new file mode 100644 index 0000000..b4efd8c --- /dev/null +++ b/backend/mci-server/src/test/java/org/springframework/boot/test/autoconfigure/web/servlet/AutoConfigureMockMvc.java @@ -0,0 +1,22 @@ +package org.springframework.boot.test.autoconfigure.web.servlet; + +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Inherited +@ImportAutoConfiguration({ + org.springframework.boot.webmvc.test.autoconfigure.MockMvcAutoConfiguration.class, + org.springframework.boot.webmvc.test.autoconfigure.MockMvcWebClientAutoConfiguration.class, + org.springframework.boot.webmvc.test.autoconfigure.MockMvcWebDriverAutoConfiguration.class +}) +public @interface AutoConfigureMockMvc { +}