feat(admin): implement tenant/user/role account APIs

This commit is contained in:
2026-07-07 11:23:29 +08:00
parent 56f8ab9e8e
commit 6a94f53ff0
9 changed files with 253 additions and 0 deletions

View File

@@ -36,6 +36,11 @@
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>3.0.4</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
@@ -58,6 +63,12 @@
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-webmvc-test</artifactId>
<version>4.1.0</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>

View File

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

View File

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

View File

@@ -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<String> roleCodes;
}

View File

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

View File

@@ -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<PlatformRole>().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<PlatformUser>()
.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;
}
}

View File

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

View File

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

View File

@@ -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 {
}