Initial commit
This commit is contained in:
commit
4fe7ba5d0d
|
|
@ -0,0 +1,150 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<groupId>com.tacit</groupId>
|
||||||
|
<artifactId>tacit-parent</artifactId>
|
||||||
|
<version>1.0.0-SNAPSHOT</version>
|
||||||
|
<packaging>pom</packaging>
|
||||||
|
|
||||||
|
<name>Tacit Parent</name>
|
||||||
|
<description>Tacit App Microservice Architecture Parent Project</description>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<java.version>21</java.version>
|
||||||
|
<spring-boot.version>3.2.0</spring-boot.version>
|
||||||
|
<spring-cloud.version>2023.0.0</spring-cloud.version>
|
||||||
|
<spring-cloud-alibaba.version>2023.0.1.0</spring-cloud-alibaba.version>
|
||||||
|
<mybatis-plus.version>3.5.5</mybatis-plus.version>
|
||||||
|
<lombok.version>1.18.30</lombok.version>
|
||||||
|
<jjwt.version>0.11.5</jjwt.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencyManagement>
|
||||||
|
<dependencies>
|
||||||
|
<!-- Spring Boot -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-dependencies</artifactId>
|
||||||
|
<version>${spring-boot.version}</version>
|
||||||
|
<type>pom</type>
|
||||||
|
<scope>import</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Spring Cloud -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-dependencies</artifactId>
|
||||||
|
<version>${spring-cloud.version}</version>
|
||||||
|
<type>pom</type>
|
||||||
|
<scope>import</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Spring Cloud Alibaba -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
|
||||||
|
<version>${spring-cloud-alibaba.version}</version>
|
||||||
|
<type>pom</type>
|
||||||
|
<scope>import</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- MyBatis Plus -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.baomidou</groupId>
|
||||||
|
<artifactId>mybatis-plus-boot-starter</artifactId>
|
||||||
|
<version>${mybatis-plus.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Lombok -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<version>${lombok.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- JJWT -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.jsonwebtoken</groupId>
|
||||||
|
<artifactId>jjwt-api</artifactId>
|
||||||
|
<version>${jjwt.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.jsonwebtoken</groupId>
|
||||||
|
<artifactId>jjwt-impl</artifactId>
|
||||||
|
<version>${jjwt.version}</version>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.jsonwebtoken</groupId>
|
||||||
|
<artifactId>jjwt-jackson</artifactId>
|
||||||
|
<version>${jjwt.version}</version>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</dependencyManagement>
|
||||||
|
|
||||||
|
<modules>
|
||||||
|
<module>tacit-gateway</module>
|
||||||
|
<module>tacit-admin</module>
|
||||||
|
<module>tacit-app-api</module>
|
||||||
|
<module>tacit-common</module>
|
||||||
|
</modules>
|
||||||
|
|
||||||
|
<repositories>
|
||||||
|
<repository>
|
||||||
|
<id>central</id>
|
||||||
|
<name>Maven Central Repository</name>
|
||||||
|
<url>https://repo1.maven.org/maven2/</url>
|
||||||
|
<layout>default</layout>
|
||||||
|
<releases>
|
||||||
|
<enabled>true</enabled>
|
||||||
|
</releases>
|
||||||
|
<snapshots>
|
||||||
|
<enabled>false</enabled>
|
||||||
|
</snapshots>
|
||||||
|
</repository>
|
||||||
|
</repositories>
|
||||||
|
|
||||||
|
<pluginRepositories>
|
||||||
|
<pluginRepository>
|
||||||
|
<id>central</id>
|
||||||
|
<name>Maven Central Repository</name>
|
||||||
|
<url>https://repo1.maven.org/maven2/</url>
|
||||||
|
<layout>default</layout>
|
||||||
|
<releases>
|
||||||
|
<enabled>true</enabled>
|
||||||
|
</releases>
|
||||||
|
<snapshots>
|
||||||
|
<enabled>false</enabled>
|
||||||
|
</snapshots>
|
||||||
|
</pluginRepository>
|
||||||
|
</pluginRepositories>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
<version>${spring-boot.version}</version>
|
||||||
|
<configuration>
|
||||||
|
<skip>true</skip>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>3.11.0</version>
|
||||||
|
<configuration>
|
||||||
|
<source>${java.version}</source>
|
||||||
|
<target>${java.version}</target>
|
||||||
|
<encoding>UTF-8</encoding>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
|
||||||
|
</project>
|
||||||
|
|
@ -0,0 +1,101 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<parent>
|
||||||
|
<artifactId>tacit-parent</artifactId>
|
||||||
|
<groupId>com.tacit</groupId>
|
||||||
|
<version>1.0.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<artifactId>tacit-admin</artifactId>
|
||||||
|
<name>Tacit Admin</name>
|
||||||
|
<description>Admin Service for Tacit Microservices</description>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<!-- Spring Boot -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-security</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Spring Cloud -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-starter-openfeign</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Spring Cloud Alibaba -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- MyBatis Plus -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.baomidou</groupId>
|
||||||
|
<artifactId>mybatis-plus-boot-starter</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Database -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.oceanbase</groupId>
|
||||||
|
<artifactId>oceanbase-client</artifactId>
|
||||||
|
<version>2.4.0</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Swagger -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springdoc</groupId>
|
||||||
|
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||||||
|
<version>2.3.0</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Common Module -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.tacit</groupId>
|
||||||
|
<artifactId>tacit-common</artifactId>
|
||||||
|
<version>${project.parent.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Lombok -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Testing -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
<version>${spring-boot.version}</version>
|
||||||
|
<configuration>
|
||||||
|
<skip>false</skip>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
package com.tacit.admin;
|
||||||
|
|
||||||
|
import org.mybatis.spring.annotation.MapperScan;
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
|
||||||
|
import org.springframework.cloud.openfeign.EnableFeignClients;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
@EnableDiscoveryClient
|
||||||
|
@EnableFeignClients
|
||||||
|
@MapperScan("com.tacit.admin.mapper")
|
||||||
|
public class AdminApplication {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(AdminApplication.class, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,114 @@
|
||||||
|
package com.tacit.admin.config;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
|
import org.springframework.security.authentication.ProviderManager;
|
||||||
|
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
|
||||||
|
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
|
import org.springframework.security.core.userdetails.User;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
|
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||||
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||||
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
|
||||||
|
import jakarta.servlet.FilterChain;
|
||||||
|
import jakarta.servlet.ServletException;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSecurity
|
||||||
|
@EnableMethodSecurity(prePostEnabled = true)
|
||||||
|
public class SecurityConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||||
|
http
|
||||||
|
// 禁用CSRF保护
|
||||||
|
.csrf(csrf -> csrf.disable())
|
||||||
|
// 允许跨域请求
|
||||||
|
.cors(cors -> cors.disable())
|
||||||
|
// 设置会话管理为无状态
|
||||||
|
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||||
|
// 添加JWT认证过滤器
|
||||||
|
.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
|
||||||
|
// 设置请求授权规则
|
||||||
|
.authorizeHttpRequests(auth -> auth
|
||||||
|
// 允许访问的路径
|
||||||
|
.requestMatchers("/test/**", "/user/login", "/v3/api-docs/**", "/swagger-ui/**", "/swagger-ui.html").permitAll()
|
||||||
|
// 其他请求需要认证
|
||||||
|
.anyRequest().authenticated()
|
||||||
|
);
|
||||||
|
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public OncePerRequestFilter jwtAuthenticationFilter() {
|
||||||
|
return new OncePerRequestFilter() {
|
||||||
|
@Override
|
||||||
|
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
|
||||||
|
// 从请求头中获取用户信息(由网关添加)
|
||||||
|
String userId = request.getHeader("X-User-Id");
|
||||||
|
String username = request.getHeader("X-Username");
|
||||||
|
String role = request.getHeader("X-Role");
|
||||||
|
|
||||||
|
// 如果有用户信息,则创建Authentication对象并设置到SecurityContext
|
||||||
|
if (userId != null && username != null && role != null) {
|
||||||
|
// 创建用户对象
|
||||||
|
org.springframework.security.core.userdetails.User user = new User(
|
||||||
|
username,
|
||||||
|
"",
|
||||||
|
Collections.singletonList(() -> "ROLE_" + role.toUpperCase())
|
||||||
|
);
|
||||||
|
|
||||||
|
// 创建认证对象
|
||||||
|
org.springframework.security.authentication.UsernamePasswordAuthenticationToken authentication =
|
||||||
|
new org.springframework.security.authentication.UsernamePasswordAuthenticationToken(
|
||||||
|
user,
|
||||||
|
null,
|
||||||
|
user.getAuthorities()
|
||||||
|
);
|
||||||
|
|
||||||
|
// 设置认证信息到SecurityContext
|
||||||
|
org.springframework.security.core.context.SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 继续过滤链
|
||||||
|
filterChain.doFilter(request, response);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public AuthenticationManager authenticationManager(UserDetailsService userDetailsService, PasswordEncoder passwordEncoder) {
|
||||||
|
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
|
||||||
|
provider.setUserDetailsService(userDetailsService);
|
||||||
|
provider.setPasswordEncoder(passwordEncoder);
|
||||||
|
return new ProviderManager(Collections.singletonList(provider));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public UserDetailsService userDetailsService() {
|
||||||
|
// 这里可以根据实际情况实现UserDetailsService,从数据库中获取用户信息
|
||||||
|
return username -> {
|
||||||
|
// 由于我们使用网关进行认证,这里可以返回一个空实现
|
||||||
|
// 实际项目中应该根据用户名从数据库中获取用户信息
|
||||||
|
throw new UsernameNotFoundException("User not found: " + username);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public PasswordEncoder passwordEncoder() {
|
||||||
|
return new BCryptPasswordEncoder();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
package com.tacit.admin.config;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.models.OpenAPI;
|
||||||
|
import io.swagger.v3.oas.models.info.Info;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Swagger配置类
|
||||||
|
* 使用SpringDoc OpenAPI配置API文档
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
public class SwaggerConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public OpenAPI adminOpenAPI() {
|
||||||
|
return new OpenAPI()
|
||||||
|
.info(new Info()
|
||||||
|
.title("Tacit Admin API")
|
||||||
|
.description("管理台服务API文档")
|
||||||
|
.version("1.0.0"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
package com.tacit.admin.controller;
|
||||||
|
|
||||||
|
import com.tacit.common.entity.ResponseResult;
|
||||||
|
import com.tacit.common.feign.AppApiFeignClient;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/test")
|
||||||
|
@Tag(name = "测试接口", description = "用于测试的接口")
|
||||||
|
public class TestController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private AppApiFeignClient appApiFeignClient;
|
||||||
|
|
||||||
|
@Operation(summary = "测试接口", description = "这是一个测试接口")
|
||||||
|
@GetMapping("/hello")
|
||||||
|
public ResponseResult<String> hello() {
|
||||||
|
return ResponseResult.success("Hello from admin service!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "测试Feign调用", description = "测试通过Feign调用APP接口服务")
|
||||||
|
@GetMapping("/feign/{userId}")
|
||||||
|
public ResponseResult<Object> testFeign(@PathVariable Long userId) {
|
||||||
|
return appApiFeignClient.getUserInfo(userId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
package com.tacit.admin.controller;
|
||||||
|
|
||||||
|
import com.tacit.admin.entity.User;
|
||||||
|
import com.tacit.admin.entity.dto.LoginRequest;
|
||||||
|
import com.tacit.admin.entity.dto.LoginResponse;
|
||||||
|
import com.tacit.admin.service.UserService;
|
||||||
|
import com.tacit.common.entity.ResponseResult;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/user")
|
||||||
|
@Tag(name = "用户管理", description = "用户相关接口")
|
||||||
|
public class UserController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserService userService;
|
||||||
|
|
||||||
|
@Operation(summary = "用户登录", description = "用户登录获取JWT令牌")
|
||||||
|
@PostMapping("/login")
|
||||||
|
public ResponseResult<LoginResponse> login(@RequestBody LoginRequest loginRequest) {
|
||||||
|
LoginResponse loginResponse = userService.login(loginRequest);
|
||||||
|
return ResponseResult.success(loginResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "获取所有用户", description = "获取系统中所有用户列表")
|
||||||
|
@GetMapping("/list")
|
||||||
|
@PreAuthorize("hasRole('ADMIN')")
|
||||||
|
public ResponseResult<List<User>> getAllUsers() {
|
||||||
|
List<User> users = userService.getAllUsers();
|
||||||
|
return ResponseResult.success(users);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "根据ID获取用户", description = "根据用户ID获取用户详情")
|
||||||
|
@GetMapping("/info/{id}")
|
||||||
|
@PreAuthorize("hasRole('ADMIN')")
|
||||||
|
public ResponseResult<User> getUserById(@PathVariable Long id) {
|
||||||
|
User user = userService.getUserById(id);
|
||||||
|
return ResponseResult.success(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "创建用户", description = "创建新用户")
|
||||||
|
@PostMapping("/create")
|
||||||
|
@PreAuthorize("hasRole('ADMIN')")
|
||||||
|
public ResponseResult<Boolean> createUser(@RequestBody User user) {
|
||||||
|
boolean result = userService.createUser(user);
|
||||||
|
return ResponseResult.success(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "更新用户", description = "更新用户信息")
|
||||||
|
@PutMapping("/update")
|
||||||
|
@PreAuthorize("hasRole('ADMIN')")
|
||||||
|
public ResponseResult<Boolean> updateUser(@RequestBody User user) {
|
||||||
|
boolean result = userService.updateUser(user);
|
||||||
|
return ResponseResult.success(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "删除用户", description = "根据用户ID删除用户")
|
||||||
|
@DeleteMapping("/delete/{id}")
|
||||||
|
@PreAuthorize("hasRole('ADMIN')")
|
||||||
|
public ResponseResult<Boolean> deleteUser(@PathVariable Long id) {
|
||||||
|
boolean result = userService.deleteUser(id);
|
||||||
|
return ResponseResult.success(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
package com.tacit.admin.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@TableName("t_user")
|
||||||
|
public class User implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@TableId(type = IdType.AUTO)
|
||||||
|
private Long id;
|
||||||
|
private String username;
|
||||||
|
private String password;
|
||||||
|
private String nickname;
|
||||||
|
private String email;
|
||||||
|
private String phone;
|
||||||
|
private Integer status;
|
||||||
|
private String role;
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
private LocalDateTime updateTime;
|
||||||
|
private Integer delFlag;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
package com.tacit.admin.entity.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 登录请求DTO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class LoginRequest implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户名
|
||||||
|
*/
|
||||||
|
private String username;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 密码
|
||||||
|
*/
|
||||||
|
private String password;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
package com.tacit.admin.entity.dto;
|
||||||
|
|
||||||
|
import com.tacit.admin.entity.User;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 登录响应DTO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class LoginResponse implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JWT令牌
|
||||||
|
*/
|
||||||
|
private String token;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户信息
|
||||||
|
*/
|
||||||
|
private User user;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
package com.tacit.admin.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.tacit.admin.entity.User;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface UserMapper extends BaseMapper<User> {
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
package com.tacit.admin.service;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
|
import com.tacit.admin.entity.User;
|
||||||
|
import com.tacit.admin.entity.dto.LoginRequest;
|
||||||
|
import com.tacit.admin.entity.dto.LoginResponse;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface UserService extends IService<User> {
|
||||||
|
/**
|
||||||
|
* 根据用户名查询用户
|
||||||
|
* @param username 用户名
|
||||||
|
* @return 用户信息
|
||||||
|
*/
|
||||||
|
User getUserByUsername(String username);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据用户ID查询用户
|
||||||
|
* @param id 用户ID
|
||||||
|
* @return 用户信息
|
||||||
|
*/
|
||||||
|
User getUserById(Long id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有用户列表
|
||||||
|
* @return 用户列表
|
||||||
|
*/
|
||||||
|
List<User> getAllUsers();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建用户
|
||||||
|
* @param user 用户信息
|
||||||
|
* @return 创建结果
|
||||||
|
*/
|
||||||
|
boolean createUser(User user);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新用户信息
|
||||||
|
* @param user 用户信息
|
||||||
|
* @return 更新结果
|
||||||
|
*/
|
||||||
|
boolean updateUser(User user);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除用户
|
||||||
|
* @param id 用户ID
|
||||||
|
* @return 删除结果
|
||||||
|
*/
|
||||||
|
boolean deleteUser(Long id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户登录
|
||||||
|
* @param loginRequest 登录请求参数
|
||||||
|
* @return 登录响应结果
|
||||||
|
*/
|
||||||
|
LoginResponse login(LoginRequest loginRequest);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,98 @@
|
||||||
|
package com.tacit.admin.service.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
import com.tacit.admin.entity.User;
|
||||||
|
import com.tacit.admin.entity.dto.LoginRequest;
|
||||||
|
import com.tacit.admin.entity.dto.LoginResponse;
|
||||||
|
import com.tacit.admin.mapper.UserMapper;
|
||||||
|
import com.tacit.admin.service.UserService;
|
||||||
|
import com.tacit.common.utils.JwtUtils;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserMapper userMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private PasswordEncoder passwordEncoder;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public User getUserByUsername(String username) {
|
||||||
|
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
|
||||||
|
queryWrapper.eq("username", username)
|
||||||
|
.eq("del_flag", 0);
|
||||||
|
return userMapper.selectOne(queryWrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public User getUserById(Long id) {
|
||||||
|
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
|
||||||
|
queryWrapper.eq("id", id)
|
||||||
|
.eq("del_flag", 0);
|
||||||
|
return userMapper.selectOne(queryWrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<User> getAllUsers() {
|
||||||
|
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
|
||||||
|
queryWrapper.eq("del_flag", 0)
|
||||||
|
.orderByDesc("create_time");
|
||||||
|
return userMapper.selectList(queryWrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean createUser(User user) {
|
||||||
|
user.setDelFlag(0);
|
||||||
|
return save(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean updateUser(User user) {
|
||||||
|
return updateById(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean deleteUser(Long id) {
|
||||||
|
User user = new User();
|
||||||
|
user.setId(id);
|
||||||
|
user.setDelFlag(1);
|
||||||
|
return updateById(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LoginResponse login(LoginRequest loginRequest) {
|
||||||
|
// 根据用户名查询用户
|
||||||
|
User user = getUserByUsername(loginRequest.getUsername());
|
||||||
|
if (user == null) {
|
||||||
|
throw new RuntimeException("用户名或密码错误");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证密码
|
||||||
|
if (!passwordEncoder.matches(loginRequest.getPassword(), user.getPassword())) {
|
||||||
|
throw new RuntimeException("用户名或密码错误");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成JWT令牌
|
||||||
|
Map<String, Object> claims = new HashMap<>();
|
||||||
|
claims.put("userId", user.getId());
|
||||||
|
claims.put("username", user.getUsername());
|
||||||
|
claims.put("role", user.getRole());
|
||||||
|
String token = JwtUtils.generateToken(claims);
|
||||||
|
|
||||||
|
// 构建登录响应
|
||||||
|
LoginResponse loginResponse = new LoginResponse();
|
||||||
|
loginResponse.setToken(token);
|
||||||
|
loginResponse.setUser(user);
|
||||||
|
|
||||||
|
return loginResponse;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
server:
|
||||||
|
port: 8081
|
||||||
|
|
||||||
|
spring:
|
||||||
|
datasource:
|
||||||
|
driver-class-name: com.oceanbase.jdbc.Driver
|
||||||
|
url: jdbc:oceanbase://localhost:2881/tacit?useUnicode=true&characterEncoding=utf-8&useSSL=false
|
||||||
|
username: root
|
||||||
|
password: password
|
||||||
|
|
||||||
|
mybatis-plus:
|
||||||
|
configuration:
|
||||||
|
map-underscore-to-camel-case: true
|
||||||
|
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||||||
|
mapper-locations: classpath*:mapper/**/*.xml
|
||||||
|
type-aliases-package: com.tacit.admin.entity
|
||||||
|
|
||||||
|
# Swagger Configuration
|
||||||
|
springdoc:
|
||||||
|
api-docs:
|
||||||
|
enabled: true
|
||||||
|
path: /v3/api-docs
|
||||||
|
swagger-ui:
|
||||||
|
enabled: true
|
||||||
|
path: /swagger-ui.html
|
||||||
|
|
||||||
|
# Logging Configuration
|
||||||
|
logging:
|
||||||
|
level:
|
||||||
|
com.tacit.admin: debug
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
spring:
|
||||||
|
application:
|
||||||
|
name: tacit-admin
|
||||||
|
cloud:
|
||||||
|
nacos:
|
||||||
|
discovery:
|
||||||
|
server-addr: localhost:8848
|
||||||
|
namespace: public
|
||||||
|
config:
|
||||||
|
server-addr: localhost:8848
|
||||||
|
namespace: public
|
||||||
|
file-extension: yml
|
||||||
|
group: DEFAULT_GROUP
|
||||||
|
refresh-enabled: true
|
||||||
|
profiles:
|
||||||
|
active: dev
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
server:
|
||||||
|
port: 8081
|
||||||
|
|
||||||
|
spring:
|
||||||
|
datasource:
|
||||||
|
driver-class-name: com.oceanbase.jdbc.Driver
|
||||||
|
url: jdbc:oceanbase://localhost:2881/tacit?useUnicode=true&characterEncoding=utf-8&useSSL=false
|
||||||
|
username: root
|
||||||
|
password: password
|
||||||
|
|
||||||
|
mybatis-plus:
|
||||||
|
configuration:
|
||||||
|
map-underscore-to-camel-case: true
|
||||||
|
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||||||
|
mapper-locations: classpath*:mapper/**/*.xml
|
||||||
|
type-aliases-package: com.tacit.admin.entity
|
||||||
|
|
||||||
|
# Swagger Configuration
|
||||||
|
springdoc:
|
||||||
|
api-docs:
|
||||||
|
enabled: true
|
||||||
|
path: /v3/api-docs
|
||||||
|
swagger-ui:
|
||||||
|
enabled: true
|
||||||
|
path: /swagger-ui.html
|
||||||
|
|
||||||
|
# Logging Configuration
|
||||||
|
logging:
|
||||||
|
level:
|
||||||
|
com.tacit.admin: debug
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
spring:
|
||||||
|
application:
|
||||||
|
name: tacit-admin
|
||||||
|
cloud:
|
||||||
|
nacos:
|
||||||
|
discovery:
|
||||||
|
server-addr: localhost:8848
|
||||||
|
namespace: public
|
||||||
|
config:
|
||||||
|
server-addr: localhost:8848
|
||||||
|
namespace: public
|
||||||
|
file-extension: yml
|
||||||
|
group: DEFAULT_GROUP
|
||||||
|
refresh-enabled: true
|
||||||
|
profiles:
|
||||||
|
active: dev
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,101 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<parent>
|
||||||
|
<artifactId>tacit-parent</artifactId>
|
||||||
|
<groupId>com.tacit</groupId>
|
||||||
|
<version>1.0.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<artifactId>tacit-app-api</artifactId>
|
||||||
|
<name>Tacit App API</name>
|
||||||
|
<description>App API Service for Tacit Microservices</description>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<!-- Spring Boot -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-security</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Spring Cloud -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-starter-openfeign</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Spring Cloud Alibaba -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- MyBatis Plus -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.baomidou</groupId>
|
||||||
|
<artifactId>mybatis-plus-boot-starter</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Database -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.oceanbase</groupId>
|
||||||
|
<artifactId>oceanbase-client</artifactId>
|
||||||
|
<version>2.4.0</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Swagger -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springdoc</groupId>
|
||||||
|
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||||||
|
<version>2.3.0</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Common Module -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.tacit</groupId>
|
||||||
|
<artifactId>tacit-common</artifactId>
|
||||||
|
<version>${project.parent.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Lombok -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Testing -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
<version>${spring-boot.version}</version>
|
||||||
|
<configuration>
|
||||||
|
<skip>false</skip>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
package com.tacit.app;
|
||||||
|
|
||||||
|
import org.mybatis.spring.annotation.MapperScan;
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
|
||||||
|
import org.springframework.cloud.openfeign.EnableFeignClients;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
@EnableDiscoveryClient
|
||||||
|
@EnableFeignClients
|
||||||
|
@MapperScan("com.tacit.app.mapper")
|
||||||
|
public class AppApiApplication {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(AppApiApplication.class, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
package com.tacit.app.config;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.models.OpenAPI;
|
||||||
|
import io.swagger.v3.oas.models.info.Info;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Swagger配置类
|
||||||
|
* 使用SpringDoc OpenAPI配置API文档
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
public class SwaggerConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public OpenAPI appApiOpenAPI() {
|
||||||
|
return new OpenAPI()
|
||||||
|
.info(new Info()
|
||||||
|
.title("Tacit App API")
|
||||||
|
.description("App接口服务API文档")
|
||||||
|
.version("1.0.0"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
package com.tacit.app.controller;
|
||||||
|
|
||||||
|
import com.tacit.app.entity.User;
|
||||||
|
import com.tacit.app.entity.dto.LoginRequest;
|
||||||
|
import com.tacit.app.entity.dto.LoginResponse;
|
||||||
|
import com.tacit.app.service.UserService;
|
||||||
|
import com.tacit.common.entity.ResponseResult;
|
||||||
|
import com.tacit.common.feign.AdminFeignClient;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
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.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/auth")
|
||||||
|
@Tag(name = "认证管理", description = "用户认证相关接口")
|
||||||
|
public class AuthController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserService userService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private AdminFeignClient adminFeignClient;
|
||||||
|
|
||||||
|
@Operation(summary = "用户登录", description = "用户登录接口")
|
||||||
|
@PostMapping("/login")
|
||||||
|
public ResponseResult<LoginResponse> login(@RequestBody LoginRequest loginRequest) {
|
||||||
|
LoginResponse loginResponse = userService.login(loginRequest);
|
||||||
|
return ResponseResult.success(loginResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "用户注册", description = "用户注册接口")
|
||||||
|
@PostMapping("/register")
|
||||||
|
public ResponseResult<Boolean> register(@RequestBody User user) {
|
||||||
|
boolean result = userService.register(user);
|
||||||
|
return ResponseResult.success(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "测试Feign调用", description = "测试通过Feign调用管理台服务")
|
||||||
|
@GetMapping("/test-feign/{userId}")
|
||||||
|
public ResponseResult<Object> testFeign(@PathVariable Long userId) {
|
||||||
|
return adminFeignClient.getUserById(userId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
package com.tacit.app.controller;
|
||||||
|
|
||||||
|
import com.tacit.app.entity.User;
|
||||||
|
import com.tacit.app.service.UserService;
|
||||||
|
import com.tacit.common.entity.ResponseResult;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/user")
|
||||||
|
@Tag(name = "用户管理", description = "用户相关接口")
|
||||||
|
public class UserController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserService userService;
|
||||||
|
|
||||||
|
@Operation(summary = "获取用户信息", description = "根据用户ID获取用户信息")
|
||||||
|
@GetMapping("/info/{userId}")
|
||||||
|
@PreAuthorize("hasRole('USER')")
|
||||||
|
public ResponseResult<User> getUserInfo(@PathVariable Long userId) {
|
||||||
|
User user = userService.getUserInfo(userId);
|
||||||
|
return ResponseResult.success(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "更新用户信息", description = "更新用户个人信息")
|
||||||
|
@PutMapping("/update")
|
||||||
|
@PreAuthorize("hasRole('USER')")
|
||||||
|
public ResponseResult<Boolean> updateUserInfo(@RequestBody User user) {
|
||||||
|
boolean result = userService.updateUserInfo(user);
|
||||||
|
return ResponseResult.success(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
package com.tacit.app.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@TableName("t_user")
|
||||||
|
public class User implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@TableId(type = IdType.AUTO)
|
||||||
|
private Long id;
|
||||||
|
private String username;
|
||||||
|
private String password;
|
||||||
|
private String nickname;
|
||||||
|
private String email;
|
||||||
|
private String phone;
|
||||||
|
private Integer status;
|
||||||
|
private String role;
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
private LocalDateTime updateTime;
|
||||||
|
private Integer delFlag;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
package com.tacit.app.entity.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class LoginRequest {
|
||||||
|
private String username;
|
||||||
|
private String password;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
package com.tacit.app.entity.dto;
|
||||||
|
|
||||||
|
import com.tacit.app.entity.User;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class LoginResponse {
|
||||||
|
private User user;
|
||||||
|
private String token;
|
||||||
|
private Long expireTime;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
package com.tacit.app.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.tacit.app.entity.User;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface UserMapper extends BaseMapper<User> {
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
package com.tacit.app.service;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
|
import com.tacit.app.entity.User;
|
||||||
|
import com.tacit.app.entity.dto.LoginRequest;
|
||||||
|
import com.tacit.app.entity.dto.LoginResponse;
|
||||||
|
|
||||||
|
public interface UserService extends IService<User> {
|
||||||
|
/**
|
||||||
|
* 用户登录
|
||||||
|
* @param loginRequest 登录请求参数
|
||||||
|
* @return 登录响应结果
|
||||||
|
*/
|
||||||
|
LoginResponse login(LoginRequest loginRequest);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户注册
|
||||||
|
* @param user 用户信息
|
||||||
|
* @return 注册结果
|
||||||
|
*/
|
||||||
|
boolean register(User user);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据用户ID获取用户信息
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @return 用户信息
|
||||||
|
*/
|
||||||
|
User getUserInfo(Long userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新用户信息
|
||||||
|
* @param user 用户信息
|
||||||
|
* @return 更新结果
|
||||||
|
*/
|
||||||
|
boolean updateUserInfo(User user);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,115 @@
|
||||||
|
package com.tacit.app.service.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
import com.tacit.app.entity.User;
|
||||||
|
import com.tacit.app.entity.dto.LoginRequest;
|
||||||
|
import com.tacit.app.entity.dto.LoginResponse;
|
||||||
|
import com.tacit.app.mapper.UserMapper;
|
||||||
|
import com.tacit.app.service.UserService;
|
||||||
|
import com.tacit.common.constant.CommonConstant;
|
||||||
|
import com.tacit.common.exception.BusinessException;
|
||||||
|
import com.tacit.common.utils.JwtUtils;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserMapper userMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private PasswordEncoder passwordEncoder;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LoginResponse login(LoginRequest loginRequest) {
|
||||||
|
// 根据用户名查询用户
|
||||||
|
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
|
||||||
|
queryWrapper.eq("username", loginRequest.getUsername())
|
||||||
|
.eq("del_flag", 0);
|
||||||
|
User user = userMapper.selectOne(queryWrapper);
|
||||||
|
|
||||||
|
// 检查用户是否存在
|
||||||
|
if (user == null) {
|
||||||
|
throw new BusinessException("用户名或密码错误");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查用户状态
|
||||||
|
if (user.getStatus() == 0) {
|
||||||
|
throw new BusinessException("用户已禁用");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证密码
|
||||||
|
if (!passwordEncoder.matches(loginRequest.getPassword(), user.getPassword())) {
|
||||||
|
throw new BusinessException("用户名或密码错误");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成JWT令牌
|
||||||
|
Map<String, Object> claims = new HashMap<>();
|
||||||
|
claims.put("userId", user.getId());
|
||||||
|
claims.put("username", user.getUsername());
|
||||||
|
claims.put("role", user.getRole());
|
||||||
|
String token = JwtUtils.generateToken(claims);
|
||||||
|
|
||||||
|
// 构造登录响应
|
||||||
|
LoginResponse loginResponse = new LoginResponse();
|
||||||
|
loginResponse.setUser(user);
|
||||||
|
loginResponse.setToken(token);
|
||||||
|
loginResponse.setExpireTime(System.currentTimeMillis() + CommonConstant.JWT_EXPIRE_TIME);
|
||||||
|
|
||||||
|
return loginResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean register(User user) {
|
||||||
|
// 检查用户名是否已存在
|
||||||
|
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
|
||||||
|
queryWrapper.eq("username", user.getUsername());
|
||||||
|
User existingUser = userMapper.selectOne(queryWrapper);
|
||||||
|
if (existingUser != null) {
|
||||||
|
throw new BusinessException("用户名已存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加密密码
|
||||||
|
user.setPassword(passwordEncoder.encode(user.getPassword()));
|
||||||
|
// 设置默认角色
|
||||||
|
user.setRole(CommonConstant.ROLE_USER);
|
||||||
|
// 设置默认状态
|
||||||
|
user.setStatus(1);
|
||||||
|
// 设置删除标记
|
||||||
|
user.setDelFlag(0);
|
||||||
|
|
||||||
|
// 保存用户
|
||||||
|
return save(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public User getUserInfo(Long userId) {
|
||||||
|
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
|
||||||
|
queryWrapper.eq("id", userId)
|
||||||
|
.eq("del_flag", 0);
|
||||||
|
return userMapper.selectOne(queryWrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean updateUserInfo(User user) {
|
||||||
|
// 只更新允许修改的字段
|
||||||
|
User existingUser = getUserInfo(user.getId());
|
||||||
|
if (existingUser == null) {
|
||||||
|
throw new BusinessException("用户不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新用户信息
|
||||||
|
existingUser.setNickname(user.getNickname());
|
||||||
|
existingUser.setEmail(user.getEmail());
|
||||||
|
existingUser.setPhone(user.getPhone());
|
||||||
|
|
||||||
|
return updateById(existingUser);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
server:
|
||||||
|
port: 8082
|
||||||
|
|
||||||
|
spring:
|
||||||
|
datasource:
|
||||||
|
driver-class-name: com.oceanbase.jdbc.Driver
|
||||||
|
url: jdbc:oceanbase://localhost:2881/tacit?useUnicode=true&characterEncoding=utf-8&useSSL=false
|
||||||
|
username: root
|
||||||
|
password: password
|
||||||
|
|
||||||
|
mybatis-plus:
|
||||||
|
configuration:
|
||||||
|
map-underscore-to-camel-case: true
|
||||||
|
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||||||
|
mapper-locations: classpath*:mapper/**/*.xml
|
||||||
|
type-aliases-package: com.tacit.app.entity
|
||||||
|
|
||||||
|
# Swagger Configuration
|
||||||
|
springdoc:
|
||||||
|
api-docs:
|
||||||
|
enabled: true
|
||||||
|
path: /v3/api-docs
|
||||||
|
swagger-ui:
|
||||||
|
enabled: true
|
||||||
|
path: /swagger-ui.html
|
||||||
|
|
||||||
|
# Logging Configuration
|
||||||
|
logging:
|
||||||
|
level:
|
||||||
|
com.tacit.app: debug
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
spring:
|
||||||
|
application:
|
||||||
|
name: tacit-app-api
|
||||||
|
cloud:
|
||||||
|
nacos:
|
||||||
|
discovery:
|
||||||
|
server-addr: localhost:8848
|
||||||
|
namespace: public
|
||||||
|
config:
|
||||||
|
server-addr: localhost:8848
|
||||||
|
namespace: public
|
||||||
|
file-extension: yml
|
||||||
|
group: DEFAULT_GROUP
|
||||||
|
refresh-enabled: true
|
||||||
|
profiles:
|
||||||
|
active: dev
|
||||||
|
|
@ -0,0 +1,196 @@
|
||||||
|
package com.tacit.app.service.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
|
import com.tacit.app.entity.User;
|
||||||
|
import com.tacit.app.entity.dto.LoginRequest;
|
||||||
|
import com.tacit.app.entity.dto.LoginResponse;
|
||||||
|
import com.tacit.app.mapper.UserMapper;
|
||||||
|
import com.tacit.common.constant.CommonConstant;
|
||||||
|
import com.tacit.common.exception.BusinessException;
|
||||||
|
import com.tacit.common.utils.JwtUtils;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UserServiceImpl单元测试
|
||||||
|
*/
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
public class UserServiceImplTest {
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private UserServiceImpl userService;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private UserMapper userMapper;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private PasswordEncoder passwordEncoder;
|
||||||
|
|
||||||
|
private User testUser;
|
||||||
|
private LoginRequest testLoginRequest;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void setUp() {
|
||||||
|
// 初始化测试用户
|
||||||
|
testUser = new User();
|
||||||
|
testUser.setId(1L);
|
||||||
|
testUser.setUsername("testuser");
|
||||||
|
testUser.setPassword("encryptedPassword");
|
||||||
|
testUser.setRole(CommonConstant.ROLE_USER);
|
||||||
|
testUser.setStatus(1);
|
||||||
|
testUser.setDelFlag(0);
|
||||||
|
|
||||||
|
// 初始化登录请求
|
||||||
|
testLoginRequest = new LoginRequest();
|
||||||
|
testLoginRequest.setUsername("testuser");
|
||||||
|
testLoginRequest.setPassword("password123");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLoginSuccess() {
|
||||||
|
// 模拟查询用户
|
||||||
|
when(userMapper.selectOne(any(QueryWrapper.class))).thenReturn(testUser);
|
||||||
|
// 模拟密码验证
|
||||||
|
when(passwordEncoder.matches(testLoginRequest.getPassword(), testUser.getPassword())).thenReturn(true);
|
||||||
|
|
||||||
|
// 执行登录
|
||||||
|
LoginResponse loginResponse = userService.login(testLoginRequest);
|
||||||
|
|
||||||
|
// 验证结果
|
||||||
|
assertNotNull(loginResponse);
|
||||||
|
assertNotNull(loginResponse.getToken());
|
||||||
|
assertEquals(testUser, loginResponse.getUser());
|
||||||
|
verify(userMapper, times(1)).selectOne(any(QueryWrapper.class));
|
||||||
|
verify(passwordEncoder, times(1)).matches(anyString(), anyString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLoginUserNotFound() {
|
||||||
|
// 模拟用户不存在
|
||||||
|
when(userMapper.selectOne(any(QueryWrapper.class))).thenReturn(null);
|
||||||
|
|
||||||
|
// 验证抛出异常
|
||||||
|
BusinessException exception = assertThrows(BusinessException.class, () -> {
|
||||||
|
userService.login(testLoginRequest);
|
||||||
|
});
|
||||||
|
assertEquals("用户名或密码错误", exception.getMessage());
|
||||||
|
verify(userMapper, times(1)).selectOne(any(QueryWrapper.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLoginUserDisabled() {
|
||||||
|
// 设置用户为禁用状态
|
||||||
|
testUser.setStatus(0);
|
||||||
|
when(userMapper.selectOne(any(QueryWrapper.class))).thenReturn(testUser);
|
||||||
|
|
||||||
|
// 验证抛出异常
|
||||||
|
BusinessException exception = assertThrows(BusinessException.class, () -> {
|
||||||
|
userService.login(testLoginRequest);
|
||||||
|
});
|
||||||
|
assertEquals("用户已禁用", exception.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLoginPasswordError() {
|
||||||
|
// 模拟密码验证失败
|
||||||
|
when(userMapper.selectOne(any(QueryWrapper.class))).thenReturn(testUser);
|
||||||
|
when(passwordEncoder.matches(testLoginRequest.getPassword(), testUser.getPassword())).thenReturn(false);
|
||||||
|
|
||||||
|
// 验证抛出异常
|
||||||
|
BusinessException exception = assertThrows(BusinessException.class, () -> {
|
||||||
|
userService.login(testLoginRequest);
|
||||||
|
});
|
||||||
|
assertEquals("用户名或密码错误", exception.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRegisterSuccess() {
|
||||||
|
// 模拟用户名不存在
|
||||||
|
when(userMapper.selectOne(any(QueryWrapper.class))).thenReturn(null);
|
||||||
|
// 模拟密码加密
|
||||||
|
when(passwordEncoder.encode(testUser.getPassword())).thenReturn("encryptedPassword");
|
||||||
|
// 模拟保存成功
|
||||||
|
when(userService.save(testUser)).thenReturn(true);
|
||||||
|
|
||||||
|
// 执行注册
|
||||||
|
boolean result = userService.register(testUser);
|
||||||
|
|
||||||
|
// 验证结果
|
||||||
|
assertTrue(result);
|
||||||
|
assertEquals(CommonConstant.ROLE_USER, testUser.getRole());
|
||||||
|
assertEquals(1, testUser.getStatus());
|
||||||
|
assertEquals(0, testUser.getDelFlag());
|
||||||
|
verify(userMapper, times(1)).selectOne(any(QueryWrapper.class));
|
||||||
|
verify(passwordEncoder, times(1)).encode(anyString());
|
||||||
|
verify(userService, times(1)).save(testUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRegisterUsernameExists() {
|
||||||
|
// 模拟用户名已存在
|
||||||
|
when(userMapper.selectOne(any(QueryWrapper.class))).thenReturn(testUser);
|
||||||
|
|
||||||
|
// 验证抛出异常
|
||||||
|
BusinessException exception = assertThrows(BusinessException.class, () -> {
|
||||||
|
userService.register(testUser);
|
||||||
|
});
|
||||||
|
assertEquals("用户名已存在", exception.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetUserInfo() {
|
||||||
|
// 模拟查询用户
|
||||||
|
when(userMapper.selectOne(any(QueryWrapper.class))).thenReturn(testUser);
|
||||||
|
|
||||||
|
// 执行查询
|
||||||
|
User user = userService.getUserInfo(1L);
|
||||||
|
|
||||||
|
// 验证结果
|
||||||
|
assertNotNull(user);
|
||||||
|
assertEquals(testUser.getId(), user.getId());
|
||||||
|
verify(userMapper, times(1)).selectOne(any(QueryWrapper.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUpdateUserInfo() {
|
||||||
|
// 模拟查询用户
|
||||||
|
when(userMapper.selectOne(any(QueryWrapper.class))).thenReturn(testUser);
|
||||||
|
// 模拟更新成功
|
||||||
|
when(userService.updateById(testUser)).thenReturn(true);
|
||||||
|
|
||||||
|
// 修改用户信息
|
||||||
|
testUser.setNickname("新昵称");
|
||||||
|
testUser.setEmail("new@example.com");
|
||||||
|
|
||||||
|
// 执行更新
|
||||||
|
boolean result = userService.updateUserInfo(testUser);
|
||||||
|
|
||||||
|
// 验证结果
|
||||||
|
assertTrue(result);
|
||||||
|
assertEquals("新昵称", testUser.getNickname());
|
||||||
|
assertEquals("new@example.com", testUser.getEmail());
|
||||||
|
verify(userMapper, times(1)).selectOne(any(QueryWrapper.class));
|
||||||
|
verify(userService, times(1)).updateById(testUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUpdateUserInfoUserNotFound() {
|
||||||
|
// 模拟用户不存在
|
||||||
|
when(userMapper.selectOne(any(QueryWrapper.class))).thenReturn(null);
|
||||||
|
|
||||||
|
// 验证抛出异常
|
||||||
|
BusinessException exception = assertThrows(BusinessException.class, () -> {
|
||||||
|
userService.updateUserInfo(testUser);
|
||||||
|
});
|
||||||
|
assertEquals("用户不存在", exception.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
server:
|
||||||
|
port: 8082
|
||||||
|
|
||||||
|
spring:
|
||||||
|
datasource:
|
||||||
|
driver-class-name: com.oceanbase.jdbc.Driver
|
||||||
|
url: jdbc:oceanbase://localhost:2881/tacit?useUnicode=true&characterEncoding=utf-8&useSSL=false
|
||||||
|
username: root
|
||||||
|
password: password
|
||||||
|
|
||||||
|
mybatis-plus:
|
||||||
|
configuration:
|
||||||
|
map-underscore-to-camel-case: true
|
||||||
|
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||||||
|
mapper-locations: classpath*:mapper/**/*.xml
|
||||||
|
type-aliases-package: com.tacit.app.entity
|
||||||
|
|
||||||
|
# Swagger Configuration
|
||||||
|
springdoc:
|
||||||
|
api-docs:
|
||||||
|
enabled: true
|
||||||
|
path: /v3/api-docs
|
||||||
|
swagger-ui:
|
||||||
|
enabled: true
|
||||||
|
path: /swagger-ui.html
|
||||||
|
|
||||||
|
# Logging Configuration
|
||||||
|
logging:
|
||||||
|
level:
|
||||||
|
com.tacit.app: debug
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
spring:
|
||||||
|
application:
|
||||||
|
name: tacit-app-api
|
||||||
|
cloud:
|
||||||
|
nacos:
|
||||||
|
discovery:
|
||||||
|
server-addr: localhost:8848
|
||||||
|
namespace: public
|
||||||
|
config:
|
||||||
|
server-addr: localhost:8848
|
||||||
|
namespace: public
|
||||||
|
file-extension: yml
|
||||||
|
group: DEFAULT_GROUP
|
||||||
|
refresh-enabled: true
|
||||||
|
profiles:
|
||||||
|
active: dev
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,98 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<parent>
|
||||||
|
<artifactId>tacit-parent</artifactId>
|
||||||
|
<groupId>com.tacit</groupId>
|
||||||
|
<version>1.0.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<artifactId>tacit-common</artifactId>
|
||||||
|
<name>Tacit Common</name>
|
||||||
|
<description>Common module for Tacit microservices</description>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<!-- Spring Boot -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Spring Cloud -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-starter-openfeign</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Spring Cloud Alibaba -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- MyBatis Plus -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.baomidou</groupId>
|
||||||
|
<artifactId>mybatis-plus-boot-starter</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Database -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.oceanbase</groupId>
|
||||||
|
<artifactId>oceanbase-client</artifactId>
|
||||||
|
<version>2.4.0</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- JJWT -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.jsonwebtoken</groupId>
|
||||||
|
<artifactId>jjwt-api</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.jsonwebtoken</groupId>
|
||||||
|
<artifactId>jjwt-impl</artifactId>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.jsonwebtoken</groupId>
|
||||||
|
<artifactId>jjwt-jackson</artifactId>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Lombok -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Validation -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-validation</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
<version>${spring-boot.version}</version>
|
||||||
|
<configuration>
|
||||||
|
<skip>true</skip>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
package com.tacit.common.constant;
|
||||||
|
|
||||||
|
public class CommonConstant {
|
||||||
|
// 状态常量
|
||||||
|
public static final Integer STATUS_ENABLE = 1;
|
||||||
|
public static final Integer STATUS_DISABLE = 0;
|
||||||
|
|
||||||
|
// 删除状态
|
||||||
|
public static final Integer DEL_FLAG_NORMAL = 0;
|
||||||
|
public static final Integer DEL_FLAG_DELETED = 1;
|
||||||
|
|
||||||
|
// JWT相关常量
|
||||||
|
public static final String JWT_HEADER = "Authorization";
|
||||||
|
public static final String JWT_PREFIX = "Bearer ";
|
||||||
|
public static final String JWT_SECRET = "tacit_app_secret_key_2024";
|
||||||
|
// 7天
|
||||||
|
public static final Long JWT_EXPIRE_TIME = 7 * 24 * 60 * 60 * 1000L;
|
||||||
|
|
||||||
|
// 服务名称常量
|
||||||
|
public static final String SERVICE_GATEWAY = "tacit-gateway";
|
||||||
|
public static final String SERVICE_ADMIN = "tacit-admin";
|
||||||
|
public static final String SERVICE_APP_API = "tacit-app-api";
|
||||||
|
|
||||||
|
// 角色常量
|
||||||
|
public static final String ROLE_ADMIN = "admin";
|
||||||
|
public static final String ROLE_USER = "user";
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
package com.tacit.common.entity;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class ResponseResult<T> implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private Integer code;
|
||||||
|
private String message;
|
||||||
|
private T data;
|
||||||
|
|
||||||
|
private ResponseResult() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public ResponseResult(Integer code, String message, T data) {
|
||||||
|
this.code = code;
|
||||||
|
this.message = message;
|
||||||
|
this.data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> ResponseResult<T> success() {
|
||||||
|
return new ResponseResult<>(200, "操作成功", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> ResponseResult<T> success(T data) {
|
||||||
|
return new ResponseResult<>(200, "操作成功", data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> ResponseResult<T> success(String message, T data) {
|
||||||
|
return new ResponseResult<>(200, message, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> ResponseResult<T> fail() {
|
||||||
|
return new ResponseResult<>(500, "操作失败", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> ResponseResult<T> fail(String message) {
|
||||||
|
return new ResponseResult<>(500, message, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> ResponseResult<T> fail(Integer code, String message) {
|
||||||
|
return new ResponseResult<>(code, message, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
package com.tacit.common.exception;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public class BusinessException extends RuntimeException {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private Integer code;
|
||||||
|
|
||||||
|
public BusinessException() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public BusinessException(String message) {
|
||||||
|
super(message);
|
||||||
|
this.code = 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BusinessException(Integer code, String message) {
|
||||||
|
super(message);
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BusinessException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
this.code = 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BusinessException(Integer code, String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
package com.tacit.common.feign;
|
||||||
|
|
||||||
|
import com.tacit.common.entity.ResponseResult;
|
||||||
|
import org.springframework.cloud.openfeign.FeignClient;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
|
||||||
|
@FeignClient(name = "tacit-admin", contextId = "adminFeignClient")
|
||||||
|
public interface AdminFeignClient {
|
||||||
|
|
||||||
|
@GetMapping("/user/info/{id}")
|
||||||
|
ResponseResult<Object> getUserById(@PathVariable("id") Long id);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
package com.tacit.common.feign;
|
||||||
|
|
||||||
|
import com.tacit.common.entity.ResponseResult;
|
||||||
|
import org.springframework.cloud.openfeign.FeignClient;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
|
||||||
|
@FeignClient(name = "tacit-app-api", contextId = "appApiFeignClient")
|
||||||
|
public interface AppApiFeignClient {
|
||||||
|
|
||||||
|
@GetMapping("/user/info/{userId}")
|
||||||
|
ResponseResult<Object> getUserInfo(@PathVariable Long userId);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
package com.tacit.common.handler;
|
||||||
|
|
||||||
|
import com.tacit.common.entity.ResponseResult;
|
||||||
|
import com.tacit.common.exception.BusinessException;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||||
|
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||||
|
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||||
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||||
|
import org.springframework.web.bind.annotation.ResponseBody;
|
||||||
|
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||||
|
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@ControllerAdvice
|
||||||
|
@ResponseBody
|
||||||
|
@Slf4j
|
||||||
|
public class GlobalExceptionHandler {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理业务异常
|
||||||
|
*/
|
||||||
|
@ExceptionHandler(BusinessException.class)
|
||||||
|
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||||
|
public ResponseResult<Object> handleBusinessException(BusinessException e) {
|
||||||
|
log.error("业务异常: {}", e.getMessage());
|
||||||
|
return ResponseResult.fail(e.getCode() != null ? e.getCode() : 400, e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理请求参数校验异常
|
||||||
|
*/
|
||||||
|
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||||
|
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||||
|
public ResponseResult<Object> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
|
||||||
|
log.error("请求参数校验异常: {}", e.getMessage());
|
||||||
|
String message = Objects.requireNonNull(e.getBindingResult().getFieldError()).getDefaultMessage();
|
||||||
|
return ResponseResult.fail(400, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理请求参数类型不匹配异常
|
||||||
|
*/
|
||||||
|
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
|
||||||
|
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||||
|
public ResponseResult<Object> handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e) {
|
||||||
|
log.error("请求参数类型不匹配异常: {}", e.getMessage());
|
||||||
|
return ResponseResult.fail(400, "请求参数类型不匹配");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理JSON解析异常
|
||||||
|
*/
|
||||||
|
@ExceptionHandler(HttpMessageNotReadableException.class)
|
||||||
|
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||||
|
public ResponseResult<Object> handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {
|
||||||
|
log.error("JSON解析异常: {}", e.getMessage());
|
||||||
|
return ResponseResult.fail(400, "JSON格式错误");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理其他异常
|
||||||
|
*/
|
||||||
|
@ExceptionHandler(Exception.class)
|
||||||
|
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||||
|
public ResponseResult<Object> handleException(Exception e) {
|
||||||
|
log.error("系统异常: {}", e.getMessage(), e);
|
||||||
|
return ResponseResult.fail(500, "系统内部错误");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,112 @@
|
||||||
|
package com.tacit.common.utils;
|
||||||
|
|
||||||
|
import com.tacit.common.constant.CommonConstant;
|
||||||
|
import io.jsonwebtoken.*;
|
||||||
|
import io.jsonwebtoken.security.Keys;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import javax.crypto.SecretKey;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class JwtUtils {
|
||||||
|
|
||||||
|
private static final SecretKey SECRET_KEY = Keys.hmacShaKeyFor(CommonConstant.JWT_SECRET.getBytes(StandardCharsets.UTF_8));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成JWT令牌
|
||||||
|
* @param claims 自定义声明
|
||||||
|
* @return JWT令牌
|
||||||
|
*/
|
||||||
|
public static String generateToken(Map<String, Object> claims) {
|
||||||
|
Date now = new Date();
|
||||||
|
Date expireDate = new Date(now.getTime() + CommonConstant.JWT_EXPIRE_TIME);
|
||||||
|
|
||||||
|
return Jwts.builder()
|
||||||
|
.setClaims(claims)
|
||||||
|
.setIssuedAt(now)
|
||||||
|
.setExpiration(expireDate)
|
||||||
|
.signWith(SECRET_KEY, SignatureAlgorithm.HS256)
|
||||||
|
.compact();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析JWT令牌
|
||||||
|
* @param token JWT令牌
|
||||||
|
* @return 自定义声明
|
||||||
|
*/
|
||||||
|
public static Claims parseToken(String token) {
|
||||||
|
try {
|
||||||
|
return Jwts.parserBuilder()
|
||||||
|
.setSigningKey(SECRET_KEY)
|
||||||
|
.build()
|
||||||
|
.parseClaimsJws(token)
|
||||||
|
.getBody();
|
||||||
|
} catch (ExpiredJwtException e) {
|
||||||
|
log.error("JWT令牌已过期: {}", e.getMessage());
|
||||||
|
throw new RuntimeException("令牌已过期", e);
|
||||||
|
} catch (UnsupportedJwtException e) {
|
||||||
|
log.error("不支持的JWT令牌: {}", e.getMessage());
|
||||||
|
throw new RuntimeException("不支持的令牌格式", e);
|
||||||
|
} catch (MalformedJwtException e) {
|
||||||
|
log.error("JWT令牌格式错误: {}", e.getMessage());
|
||||||
|
throw new RuntimeException("令牌格式错误", e);
|
||||||
|
} catch (SecurityException e) {
|
||||||
|
log.error("JWT令牌验证失败: {}", e.getMessage());
|
||||||
|
throw new RuntimeException("令牌验证失败", e);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("JWT令牌解析异常: {}", e.getMessage());
|
||||||
|
throw new RuntimeException("令牌解析异常", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从JWT令牌中获取用户ID
|
||||||
|
* @param token JWT令牌
|
||||||
|
* @return 用户ID
|
||||||
|
*/
|
||||||
|
public static Long getUserIdFromToken(String token) {
|
||||||
|
Claims claims = parseToken(token);
|
||||||
|
return Long.parseLong(claims.get("userId").toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从JWT令牌中获取用户名
|
||||||
|
* @param token JWT令牌
|
||||||
|
* @return 用户名
|
||||||
|
*/
|
||||||
|
public static String getUsernameFromToken(String token) {
|
||||||
|
Claims claims = parseToken(token);
|
||||||
|
return claims.get("username").toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从JWT令牌中获取角色
|
||||||
|
* @param token JWT令牌
|
||||||
|
* @return 角色
|
||||||
|
*/
|
||||||
|
public static String getRoleFromToken(String token) {
|
||||||
|
Claims claims = parseToken(token);
|
||||||
|
return claims.get("role").toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证JWT令牌是否有效
|
||||||
|
* @param token JWT令牌
|
||||||
|
* @return 是否有效
|
||||||
|
*/
|
||||||
|
public static boolean validateToken(String token) {
|
||||||
|
try {
|
||||||
|
Jwts.parserBuilder()
|
||||||
|
.setSigningKey(SECRET_KEY)
|
||||||
|
.build()
|
||||||
|
.parseClaimsJws(token);
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("JWT令牌验证失败: {}", e.getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,82 @@
|
||||||
|
package com.tacit.common.entity;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ResponseResult类单元测试
|
||||||
|
*/
|
||||||
|
public class ResponseResultTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSuccessWithData() {
|
||||||
|
String data = "test data";
|
||||||
|
ResponseResult<String> result = ResponseResult.success(data);
|
||||||
|
|
||||||
|
assertEquals(200, result.getCode());
|
||||||
|
assertEquals("操作成功", result.getMessage());
|
||||||
|
assertEquals(data, result.getData());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSuccessWithoutData() {
|
||||||
|
ResponseResult<Object> result = ResponseResult.success();
|
||||||
|
|
||||||
|
assertEquals(200, result.getCode());
|
||||||
|
assertEquals("操作成功", result.getMessage());
|
||||||
|
assertNull(result.getData());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFailWithMessage() {
|
||||||
|
String message = "自定义错误消息";
|
||||||
|
ResponseResult<Object> result = ResponseResult.fail(message);
|
||||||
|
|
||||||
|
assertEquals(500, result.getCode());
|
||||||
|
assertEquals(message, result.getMessage());
|
||||||
|
assertNull(result.getData());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFailDefault() {
|
||||||
|
ResponseResult<Object> result = ResponseResult.fail();
|
||||||
|
|
||||||
|
assertEquals(500, result.getCode());
|
||||||
|
assertEquals("操作失败", result.getMessage());
|
||||||
|
assertNull(result.getData());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testFailWithCodeAndMessage() {
|
||||||
|
int code = 400;
|
||||||
|
String message = "参数错误";
|
||||||
|
ResponseResult<Object> result = ResponseResult.fail(code, message);
|
||||||
|
|
||||||
|
assertFalse(result.isSuccess());
|
||||||
|
assertEquals(code, result.getCode());
|
||||||
|
assertEquals(message, result.getMessage());
|
||||||
|
assertNull(result.getData());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEqualsAndHashCode() {
|
||||||
|
ResponseResult<String> result1 = ResponseResult.success("test");
|
||||||
|
ResponseResult<String> result2 = ResponseResult.success("test");
|
||||||
|
ResponseResult<String> result3 = ResponseResult.success("different");
|
||||||
|
|
||||||
|
assertEquals(result1, result2);
|
||||||
|
assertNotEquals(result1, result3);
|
||||||
|
assertEquals(result1.hashCode(), result2.hashCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testToString() {
|
||||||
|
ResponseResult<String> result = ResponseResult.success("test");
|
||||||
|
String toString = result.toString();
|
||||||
|
|
||||||
|
assertNotNull(toString);
|
||||||
|
assertTrue(toString.contains("success"));
|
||||||
|
assertTrue(toString.contains("test"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
package com.tacit.common.utils;
|
||||||
|
|
||||||
|
import com.tacit.common.constant.CommonConstant;
|
||||||
|
import io.jsonwebtoken.Claims;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JWT工具类单元测试
|
||||||
|
*/
|
||||||
|
public class JwtUtilsTest {
|
||||||
|
|
||||||
|
private String token;
|
||||||
|
private Map<String, Object> claims;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
public void setUp() {
|
||||||
|
claims = new HashMap<>();
|
||||||
|
claims.put("userId", 1L);
|
||||||
|
claims.put("username", "testuser");
|
||||||
|
claims.put("role", "admin");
|
||||||
|
token = JwtUtils.generateToken(claims);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGenerateToken() {
|
||||||
|
assertNotNull(token);
|
||||||
|
assertTrue(token.length() > 0);
|
||||||
|
assertTrue(token.contains("."));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParseToken() {
|
||||||
|
Claims parsedClaims = JwtUtils.parseToken(token);
|
||||||
|
assertNotNull(parsedClaims);
|
||||||
|
assertEquals(1L, parsedClaims.get("userId"));
|
||||||
|
assertEquals("testuser", parsedClaims.get("username"));
|
||||||
|
assertEquals("admin", parsedClaims.get("role"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testValidateToken() {
|
||||||
|
boolean isValid = JwtUtils.validateToken(token);
|
||||||
|
assertTrue(isValid);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testValidateTokenWithInvalidToken() {
|
||||||
|
boolean isValid = JwtUtils.validateToken("invalid.token.here");
|
||||||
|
assertFalse(isValid);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetUserIdFromToken() {
|
||||||
|
Long userId = JwtUtils.getUserIdFromToken(token);
|
||||||
|
assertEquals(1L, userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetUsernameFromToken() {
|
||||||
|
String username = JwtUtils.getUsernameFromToken(token);
|
||||||
|
assertEquals("testuser", username);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetRoleFromToken() {
|
||||||
|
String role = JwtUtils.getRoleFromToken(token);
|
||||||
|
assertEquals("admin", role);
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,79 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<parent>
|
||||||
|
<artifactId>tacit-parent</artifactId>
|
||||||
|
<groupId>com.tacit</groupId>
|
||||||
|
<version>1.0.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<artifactId>tacit-gateway</artifactId>
|
||||||
|
<name>Tacit Gateway</name>
|
||||||
|
<description>Gateway Service for Tacit Microservices</description>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<!-- Spring Cloud Gateway -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-starter-gateway</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Spring Cloud Alibaba -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba.cloud</groupId>
|
||||||
|
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Spring Security -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-security</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Common Module -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.tacit</groupId>
|
||||||
|
<artifactId>tacit-common</artifactId>
|
||||||
|
<version>${project.parent.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Lombok -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Commons Lang3 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.commons</groupId>
|
||||||
|
<artifactId>commons-lang3</artifactId>
|
||||||
|
<version>3.14.0</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Logging -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-logging</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
<version>${spring-boot.version}</version>
|
||||||
|
<configuration>
|
||||||
|
<skip>false</skip>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
package com.tacit.gateway;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
@EnableDiscoveryClient
|
||||||
|
public class GatewayApplication {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(GatewayApplication.class, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,104 @@
|
||||||
|
package com.tacit.gateway.filter;
|
||||||
|
|
||||||
|
import com.tacit.common.constant.CommonConstant;
|
||||||
|
import com.tacit.common.utils.JwtUtils;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
||||||
|
import org.springframework.cloud.gateway.filter.GlobalFilter;
|
||||||
|
import org.springframework.core.Ordered;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||||
|
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.server.ServerWebExchange;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
public class JwtAuthenticationFilter implements GlobalFilter, Ordered {
|
||||||
|
|
||||||
|
// 不需要认证的路径
|
||||||
|
private static final List<String> WHITE_LIST = List.of(
|
||||||
|
"/api/auth/login",
|
||||||
|
"/api/auth/register",
|
||||||
|
"/swagger-ui",
|
||||||
|
"/v3/api-docs"
|
||||||
|
);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
||||||
|
ServerHttpRequest request = exchange.getRequest();
|
||||||
|
String path = request.getURI().getPath();
|
||||||
|
|
||||||
|
// 检查是否在白名单中
|
||||||
|
if (isWhiteList(path)) {
|
||||||
|
return chain.filter(exchange);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取Authorization头
|
||||||
|
HttpHeaders headers = request.getHeaders();
|
||||||
|
String authorization = headers.getFirst(HttpHeaders.AUTHORIZATION);
|
||||||
|
|
||||||
|
// 检查Authorization头是否存在
|
||||||
|
if (StringUtils.isBlank(authorization)) {
|
||||||
|
return unauthorizedResponse(exchange, "缺少认证令牌");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查Authorization头格式
|
||||||
|
if (!authorization.startsWith(CommonConstant.JWT_PREFIX)) {
|
||||||
|
return unauthorizedResponse(exchange, "认证令牌格式错误");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提取JWT令牌
|
||||||
|
String token = authorization.substring(CommonConstant.JWT_PREFIX.length());
|
||||||
|
|
||||||
|
// 验证JWT令牌
|
||||||
|
try {
|
||||||
|
JwtUtils.validateToken(token);
|
||||||
|
|
||||||
|
// 从令牌中获取用户信息并添加到请求头
|
||||||
|
Long userId = JwtUtils.getUserIdFromToken(token);
|
||||||
|
String username = JwtUtils.getUsernameFromToken(token);
|
||||||
|
String role = JwtUtils.getRoleFromToken(token);
|
||||||
|
|
||||||
|
// 将用户信息添加到请求头
|
||||||
|
ServerHttpRequest mutatedRequest = request.mutate()
|
||||||
|
.header("X-User-Id", String.valueOf(userId))
|
||||||
|
.header("X-Username", username)
|
||||||
|
.header("X-Role", role)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return chain.filter(exchange.mutate().request(mutatedRequest).build());
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("JWT认证失败: {}", e.getMessage());
|
||||||
|
return unauthorizedResponse(exchange, e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查路径是否在白名单中
|
||||||
|
*/
|
||||||
|
private boolean isWhiteList(String path) {
|
||||||
|
return WHITE_LIST.stream().anyMatch(path::startsWith);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回未授权响应
|
||||||
|
*/
|
||||||
|
private Mono<Void> unauthorizedResponse(ServerWebExchange exchange, String message) {
|
||||||
|
ServerHttpResponse response = exchange.getResponse();
|
||||||
|
response.setStatusCode(HttpStatus.UNAUTHORIZED);
|
||||||
|
response.getHeaders().add(HttpHeaders.CONTENT_TYPE, "application/json");
|
||||||
|
String responseBody = String.format("{\"code\": 401, \"message\": \"%s\"}", message);
|
||||||
|
return response.writeWith(Mono.just(response.bufferFactory().wrap(responseBody.getBytes())));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getOrder() {
|
||||||
|
return -100;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
server:
|
||||||
|
port: 8080
|
||||||
|
|
||||||
|
spring:
|
||||||
|
cloud:
|
||||||
|
gateway:
|
||||||
|
discovery:
|
||||||
|
locator:
|
||||||
|
enabled: true
|
||||||
|
lower-case-service-id: true
|
||||||
|
routes:
|
||||||
|
# Admin Service Route
|
||||||
|
- id: tacit-admin
|
||||||
|
uri: lb://tacit-admin
|
||||||
|
predicates:
|
||||||
|
- Path=/admin/**
|
||||||
|
filters:
|
||||||
|
- StripPrefix=1
|
||||||
|
|
||||||
|
# App API Service Route
|
||||||
|
- id: tacit-app-api
|
||||||
|
uri: lb://tacit-app-api
|
||||||
|
predicates:
|
||||||
|
- Path=/api/**
|
||||||
|
filters:
|
||||||
|
- StripPrefix=1
|
||||||
|
- JwtAuthenticationFilter
|
||||||
|
|
||||||
|
# Swagger UI Routes
|
||||||
|
- id: swagger-admin
|
||||||
|
uri: lb://tacit-admin
|
||||||
|
predicates:
|
||||||
|
- Path=/swagger-admin/**
|
||||||
|
filters:
|
||||||
|
- StripPrefix=1
|
||||||
|
|
||||||
|
- id: swagger-app-api
|
||||||
|
uri: lb://tacit-app-api
|
||||||
|
predicates:
|
||||||
|
- Path=/swagger-app-api/**
|
||||||
|
filters:
|
||||||
|
- StripPrefix=1
|
||||||
|
|
||||||
|
# Logging Configuration
|
||||||
|
logging:
|
||||||
|
level:
|
||||||
|
org.springframework.cloud.gateway: debug
|
||||||
|
org.springframework.http.server.reactive: debug
|
||||||
|
org.springframework.web.reactive: debug
|
||||||
|
reactor.netty: debug
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
spring:
|
||||||
|
application:
|
||||||
|
name: tacit-gateway
|
||||||
|
cloud:
|
||||||
|
nacos:
|
||||||
|
discovery:
|
||||||
|
server-addr: localhost:8848
|
||||||
|
namespace: public
|
||||||
|
config:
|
||||||
|
server-addr: localhost:8848
|
||||||
|
namespace: public
|
||||||
|
file-extension: yml
|
||||||
|
group: DEFAULT_GROUP
|
||||||
|
refresh-enabled: true
|
||||||
|
profiles:
|
||||||
|
active: dev
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
server:
|
||||||
|
port: 8080
|
||||||
|
|
||||||
|
spring:
|
||||||
|
cloud:
|
||||||
|
gateway:
|
||||||
|
discovery:
|
||||||
|
locator:
|
||||||
|
enabled: true
|
||||||
|
lower-case-service-id: true
|
||||||
|
routes:
|
||||||
|
# Admin Service Route
|
||||||
|
- id: tacit-admin
|
||||||
|
uri: lb://tacit-admin
|
||||||
|
predicates:
|
||||||
|
- Path=/admin/**
|
||||||
|
filters:
|
||||||
|
- StripPrefix=1
|
||||||
|
|
||||||
|
# App API Service Route
|
||||||
|
- id: tacit-app-api
|
||||||
|
uri: lb://tacit-app-api
|
||||||
|
predicates:
|
||||||
|
- Path=/api/**
|
||||||
|
filters:
|
||||||
|
- StripPrefix=1
|
||||||
|
- JwtAuthenticationFilter
|
||||||
|
|
||||||
|
# Swagger UI Routes
|
||||||
|
- id: swagger-admin
|
||||||
|
uri: lb://tacit-admin
|
||||||
|
predicates:
|
||||||
|
- Path=/swagger-admin/**
|
||||||
|
filters:
|
||||||
|
- StripPrefix=1
|
||||||
|
|
||||||
|
- id: swagger-app-api
|
||||||
|
uri: lb://tacit-app-api
|
||||||
|
predicates:
|
||||||
|
- Path=/swagger-app-api/**
|
||||||
|
filters:
|
||||||
|
- StripPrefix=1
|
||||||
|
|
||||||
|
# Logging Configuration
|
||||||
|
logging:
|
||||||
|
level:
|
||||||
|
org.springframework.cloud.gateway: debug
|
||||||
|
org.springframework.http.server.reactive: debug
|
||||||
|
org.springframework.web.reactive: debug
|
||||||
|
reactor.netty: debug
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
spring:
|
||||||
|
application:
|
||||||
|
name: tacit-gateway
|
||||||
|
cloud:
|
||||||
|
nacos:
|
||||||
|
discovery:
|
||||||
|
server-addr: localhost:8848
|
||||||
|
namespace: public
|
||||||
|
config:
|
||||||
|
server-addr: localhost:8848
|
||||||
|
namespace: public
|
||||||
|
file-extension: yml
|
||||||
|
group: DEFAULT_GROUP
|
||||||
|
refresh-enabled: true
|
||||||
|
profiles:
|
||||||
|
active: dev
|
||||||
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue