main主干首次提交,包含用户认证模块
This commit is contained in:
38
.gitignore
vendored
Normal file
38
.gitignore
vendored
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# ==========================================
|
||||||
|
# 1. Maven/Java 构建产物 (一键忽略整个目录)
|
||||||
|
# ==========================================
|
||||||
|
target/
|
||||||
|
*.class
|
||||||
|
*.jar
|
||||||
|
*.war
|
||||||
|
*.ear
|
||||||
|
docs/
|
||||||
|
specs/
|
||||||
|
src/test/
|
||||||
|
CLAUDE.md
|
||||||
|
# ==========================================
|
||||||
|
# 2. IDE 配置文件
|
||||||
|
# ==========================================
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
*.iws
|
||||||
|
.agents/
|
||||||
|
.history/
|
||||||
|
logs/
|
||||||
|
|
||||||
|
# ==========================================
|
||||||
|
# 3. 项目特定工具目录 (根据你的文件列表)
|
||||||
|
# ==========================================
|
||||||
|
# 忽略 Specifiy 工具生成的所有配置和脚本
|
||||||
|
.specify/
|
||||||
|
|
||||||
|
# 忽略 Claude 本地设置和技能文件
|
||||||
|
.claude/
|
||||||
|
|
||||||
|
# ==========================================
|
||||||
|
# 4. 操作系统文件
|
||||||
|
# ==========================================
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
129
pom.xml
Normal file
129
pom.xml
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<groupId>com.labelsys</groupId>
|
||||||
|
<artifactId>label-backend</artifactId>
|
||||||
|
<version>1.0.0-SNAPSHOT</version>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
<description>LabelSys backend</description>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<java.version>21</java.version>
|
||||||
|
<spring.boot.version>3.1.5</spring.boot.version>
|
||||||
|
<mybatis-plus.version>3.5.3.1</mybatis-plus.version>
|
||||||
|
<springdoc-openapi.version>2.3.0</springdoc-openapi.version>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencyManagement>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-dependencies</artifactId>
|
||||||
|
<version>${spring.boot.version}</version>
|
||||||
|
<type>pom</type>
|
||||||
|
<scope>import</scope>
|
||||||
|
</dependency>
|
||||||
|
<!-- AWS SDK v2 BOM -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>software.amazon.awssdk</groupId>
|
||||||
|
<artifactId>bom</artifactId>
|
||||||
|
<version>2.26.31</version>
|
||||||
|
<type>pom</type>
|
||||||
|
<scope>import</scope>
|
||||||
|
</dependency>
|
||||||
|
<!-- Testcontainers BOM -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.testcontainers</groupId>
|
||||||
|
<artifactId>testcontainers-bom</artifactId>
|
||||||
|
<version>1.20.1</version>
|
||||||
|
<type>pom</type>
|
||||||
|
<scope>import</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</dependencyManagement>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-validation</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.baomidou</groupId>
|
||||||
|
<artifactId>mybatis-plus-boot-starter</artifactId>
|
||||||
|
<version>${mybatis-plus.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.security</groupId>
|
||||||
|
<artifactId>spring-security-crypto</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springdoc</groupId>
|
||||||
|
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||||||
|
<version>${springdoc-openapi.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.postgresql</groupId>
|
||||||
|
<artifactId>postgresql</artifactId>
|
||||||
|
<version>42.2.24</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.h2database</groupId>
|
||||||
|
<artifactId>h2</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<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>
|
||||||
|
<annotationProcessorPaths>
|
||||||
|
<path>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<version>1.18.30</version>
|
||||||
|
</path>
|
||||||
|
</annotationProcessorPaths>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
<version>${spring.boot.version}</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<goals>
|
||||||
|
<goal>repackage</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.labelsys.backend;
|
||||||
|
|
||||||
|
import org.mybatis.spring.annotation.MapperScan;
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
@MapperScan("com.labelsys.backend.mapper")
|
||||||
|
public class LabelsysBackendApplication {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(LabelsysBackendApplication.class, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.labelsys.backend.annotation;
|
||||||
|
|
||||||
|
import com.labelsys.backend.enums.UserPosition;
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
@Target({ElementType.METHOD, ElementType.TYPE})
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface RequirePosition {
|
||||||
|
|
||||||
|
UserPosition value();
|
||||||
|
}
|
||||||
30
src/main/java/com/labelsys/backend/common/Result.java
Normal file
30
src/main/java/com/labelsys/backend/common/Result.java
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package com.labelsys.backend.common;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Schema(description = "统一返回结果")
|
||||||
|
public class Result<T> {
|
||||||
|
|
||||||
|
@Schema(description = "业务状态码")
|
||||||
|
private Integer code;
|
||||||
|
|
||||||
|
@Schema(description = "返回消息")
|
||||||
|
private String message;
|
||||||
|
|
||||||
|
@Schema(description = "返回数据")
|
||||||
|
private T data;
|
||||||
|
|
||||||
|
public static <T> Result<T> success() {
|
||||||
|
return new Result<>(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage(), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> Result<T> success(T data) {
|
||||||
|
return new Result<>(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage(), data);
|
||||||
|
}
|
||||||
|
}
|
||||||
19
src/main/java/com/labelsys/backend/common/ResultCode.java
Normal file
19
src/main/java/com/labelsys/backend/common/ResultCode.java
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package com.labelsys.backend.common;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public enum ResultCode {
|
||||||
|
SUCCESS(200, "操作成功"),
|
||||||
|
BAD_REQUEST(400, "请求参数错误"),
|
||||||
|
UNAUTHORIZED(401, "未登录或登录已过期"),
|
||||||
|
FORBIDDEN(403, "无权限访问"),
|
||||||
|
NOT_FOUND(404, "资源不存在"),
|
||||||
|
CONFLICT(409, "资源冲突"),
|
||||||
|
ERROR(500, "系统异常");
|
||||||
|
|
||||||
|
private final Integer code;
|
||||||
|
private final String message;
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.labelsys.backend.common.exception;
|
||||||
|
|
||||||
|
import com.labelsys.backend.common.ResultCode;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public class BusinessException extends RuntimeException {
|
||||||
|
|
||||||
|
private final ResultCode resultCode;
|
||||||
|
|
||||||
|
public BusinessException(ResultCode resultCode, String message) {
|
||||||
|
super(message);
|
||||||
|
this.resultCode = resultCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.labelsys.backend.common.exception;
|
||||||
|
|
||||||
|
import com.labelsys.backend.common.ResultCode;
|
||||||
|
|
||||||
|
public class ForbiddenException extends BusinessException {
|
||||||
|
|
||||||
|
public ForbiddenException(String message) {
|
||||||
|
super(ResultCode.FORBIDDEN, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
package com.labelsys.backend.common.exception;
|
||||||
|
|
||||||
|
import com.labelsys.backend.common.Result;
|
||||||
|
import com.labelsys.backend.common.ResultCode;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.validation.BindException;
|
||||||
|
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||||
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||||
|
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||||
|
|
||||||
|
@RestControllerAdvice
|
||||||
|
public class GlobalExceptionHandler {
|
||||||
|
|
||||||
|
@ExceptionHandler(BusinessException.class)
|
||||||
|
public Result<Void> handleBusiness(BusinessException exception, HttpServletResponse response) {
|
||||||
|
response.setStatus(toHttpStatus(exception.getResultCode()).value());
|
||||||
|
return new Result<>(exception.getResultCode().getCode(), exception.getMessage(), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler({MethodArgumentNotValidException.class, BindException.class})
|
||||||
|
public Result<Void> handleValidation(Exception exception, HttpServletResponse response) {
|
||||||
|
response.setStatus(HttpStatus.BAD_REQUEST.value());
|
||||||
|
String message;
|
||||||
|
if (exception instanceof MethodArgumentNotValidException methodArgumentNotValidException) {
|
||||||
|
message = methodArgumentNotValidException.getBindingResult().getFieldErrors().stream()
|
||||||
|
.map(error -> error.getField() + error.getDefaultMessage())
|
||||||
|
.collect(Collectors.joining(";"));
|
||||||
|
} else if (exception instanceof BindException bindException) {
|
||||||
|
message = bindException.getBindingResult().getFieldErrors().stream()
|
||||||
|
.map(error -> error.getField() + error.getDefaultMessage())
|
||||||
|
.collect(Collectors.joining(";"));
|
||||||
|
} else {
|
||||||
|
message = ResultCode.BAD_REQUEST.getMessage();
|
||||||
|
}
|
||||||
|
return new Result<>(ResultCode.BAD_REQUEST.getCode(), message, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(Exception.class)
|
||||||
|
public Result<Void> handleUnexpected(Exception exception, HttpServletResponse response) {
|
||||||
|
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
|
||||||
|
return new Result<>(ResultCode.ERROR.getCode(), exception.getMessage(), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private HttpStatus toHttpStatus(ResultCode resultCode) {
|
||||||
|
return switch (resultCode) {
|
||||||
|
case BAD_REQUEST -> HttpStatus.BAD_REQUEST;
|
||||||
|
case UNAUTHORIZED -> HttpStatus.UNAUTHORIZED;
|
||||||
|
case FORBIDDEN -> HttpStatus.FORBIDDEN;
|
||||||
|
case NOT_FOUND -> HttpStatus.NOT_FOUND;
|
||||||
|
case CONFLICT -> HttpStatus.CONFLICT;
|
||||||
|
default -> HttpStatus.INTERNAL_SERVER_ERROR;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package com.labelsys.backend.common.exception;
|
||||||
|
|
||||||
|
import com.labelsys.backend.common.ResultCode;
|
||||||
|
|
||||||
|
public class UnauthorizedException extends BusinessException {
|
||||||
|
|
||||||
|
public UnauthorizedException(String message) {
|
||||||
|
super(ResultCode.UNAUTHORIZED, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
18
src/main/java/com/labelsys/backend/config/OpenApiConfig.java
Normal file
18
src/main/java/com/labelsys/backend/config/OpenApiConfig.java
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package com.labelsys.backend.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;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class OpenApiConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public OpenAPI labelsysOpenApi() {
|
||||||
|
return new OpenAPI().info(new Info()
|
||||||
|
.title("LabelSys 后台认证鉴权接口")
|
||||||
|
.version("0.0.1")
|
||||||
|
.description("公司、员工、认证、岗位权限和数据权限接口"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.labelsys.backend.config;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class SecurityBeanConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public PasswordEncoder passwordEncoder() {
|
||||||
|
return new BCryptPasswordEncoder();
|
||||||
|
}
|
||||||
|
}
|
||||||
19
src/main/java/com/labelsys/backend/config/WebMvcConfig.java
Normal file
19
src/main/java/com/labelsys/backend/config/WebMvcConfig.java
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package com.labelsys.backend.config;
|
||||||
|
|
||||||
|
import com.labelsys.backend.interceptor.AuthInterceptor;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||||
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class WebMvcConfig implements WebMvcConfigurer {
|
||||||
|
|
||||||
|
private final AuthInterceptor authInterceptor;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addInterceptors(InterceptorRegistry registry) {
|
||||||
|
registry.addInterceptor(authInterceptor).addPathPatterns("/api/**");
|
||||||
|
}
|
||||||
|
}
|
||||||
43
src/main/java/com/labelsys/backend/context/LoginUser.java
Normal file
43
src/main/java/com/labelsys/backend/context/LoginUser.java
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
package com.labelsys.backend.context;
|
||||||
|
|
||||||
|
import com.labelsys.backend.entity.SysCompany;
|
||||||
|
import com.labelsys.backend.entity.SysUser;
|
||||||
|
import com.labelsys.backend.enums.UserPosition;
|
||||||
|
import com.labelsys.backend.enums.UserRole;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
|
||||||
|
@Schema(description = "当前登录用户上下文")
|
||||||
|
public record LoginUser(
|
||||||
|
@Schema(description = "用户ID") Long userId,
|
||||||
|
@Schema(description = "公司ID") Long companyId,
|
||||||
|
@Schema(description = "公司编码") String companyCode,
|
||||||
|
@Schema(description = "公司名称") String companyName,
|
||||||
|
@Schema(description = "手机号") String phone,
|
||||||
|
@Schema(description = "用户名,可为空", example = "alpha-reviewer") String username,
|
||||||
|
@Schema(description = "真实姓名") String realName,
|
||||||
|
@Schema(description = "角色,枚举值:EMPLOYEE员工、MANAGER部门经理、ENGINEER总工程师") UserRole role,
|
||||||
|
@Schema(description = "岗位,枚举值:UPLOADER上传者、ANNOTATOR标注员、DATA_TRAINER数据训练师、REVIEWER审核员、ADMIN超级管理员") UserPosition position,
|
||||||
|
@Schema(description = "是否必须修改密码") boolean mustChangePassword,
|
||||||
|
@Schema(description = "会话版本") Integer sessionVersion
|
||||||
|
) {
|
||||||
|
|
||||||
|
public static LoginUser from(SysUser user, SysCompany company) {
|
||||||
|
return new LoginUser(
|
||||||
|
user.getId(),
|
||||||
|
company.getId(),
|
||||||
|
company.getCompanyCode(),
|
||||||
|
company.getCompanyName(),
|
||||||
|
user.getPhone(),
|
||||||
|
user.getUsername(),
|
||||||
|
user.getRealName(),
|
||||||
|
user.getRole(),
|
||||||
|
user.getPosition(),
|
||||||
|
Boolean.TRUE.equals(user.getMustChangePassword()),
|
||||||
|
user.getSessionVersion()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPlatformAdmin() {
|
||||||
|
return "PLATFORM".equals(companyCode) && position == UserPosition.ADMIN;
|
||||||
|
}
|
||||||
|
}
|
||||||
28
src/main/java/com/labelsys/backend/context/UserContext.java
Normal file
28
src/main/java/com/labelsys/backend/context/UserContext.java
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package com.labelsys.backend.context;
|
||||||
|
|
||||||
|
import com.labelsys.backend.common.exception.UnauthorizedException;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public final class UserContext {
|
||||||
|
|
||||||
|
private static final ThreadLocal<LoginUser> HOLDER = new ThreadLocal<>();
|
||||||
|
|
||||||
|
private UserContext() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void set(LoginUser loginUser) {
|
||||||
|
HOLDER.set(loginUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Optional<LoginUser> get() {
|
||||||
|
return Optional.ofNullable(HOLDER.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LoginUser requireUser() {
|
||||||
|
return get().orElseThrow(() -> new UnauthorizedException("未登录或登录已过期"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void clear() {
|
||||||
|
HOLDER.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
package com.labelsys.backend.controller;
|
||||||
|
|
||||||
|
import com.labelsys.backend.common.Result;
|
||||||
|
import com.labelsys.backend.context.LoginUser;
|
||||||
|
import com.labelsys.backend.context.UserContext;
|
||||||
|
import com.labelsys.backend.dto.request.ChangePasswordRequest;
|
||||||
|
import com.labelsys.backend.dto.request.LoginRequest;
|
||||||
|
import com.labelsys.backend.dto.response.CompanyOptionResponse;
|
||||||
|
import com.labelsys.backend.dto.response.CurrentUserResponse;
|
||||||
|
import com.labelsys.backend.dto.response.LoginResponse;
|
||||||
|
import com.labelsys.backend.service.AuthService;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestHeader;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
@Tag(name = "认证管理")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/auth")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class AuthController {
|
||||||
|
|
||||||
|
private final AuthService authService;
|
||||||
|
|
||||||
|
@Operation(summary = "根据手机号查询可登录公司")
|
||||||
|
@GetMapping("/companies")
|
||||||
|
public Result<List<CompanyOptionResponse>> listCompanies(@RequestParam String phone) {
|
||||||
|
return Result.success(authService.listAvailableCompanies(phone));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "登录")
|
||||||
|
@PostMapping("/login")
|
||||||
|
public Result<LoginResponse> login(@Valid @RequestBody LoginRequest request) {
|
||||||
|
return Result.success(authService.login(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "修改密码")
|
||||||
|
@PostMapping("/change-password")
|
||||||
|
public Result<Void> changePassword(@Valid @RequestBody ChangePasswordRequest request) {
|
||||||
|
authService.changePassword(UserContext.requireUser(), request);
|
||||||
|
return Result.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "退出登录")
|
||||||
|
@PostMapping("/logout")
|
||||||
|
public Result<Void> logout(@RequestHeader(HttpHeaders.AUTHORIZATION) String authorization) {
|
||||||
|
authService.logout(extractToken(authorization));
|
||||||
|
return Result.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "获取当前登录态")
|
||||||
|
@GetMapping("/me")
|
||||||
|
public Result<CurrentUserResponse> currentUser() {
|
||||||
|
LoginUser loginUser = UserContext.requireUser();
|
||||||
|
return Result.success(CurrentUserResponse.from(loginUser));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String extractToken(String authorization) {
|
||||||
|
return authorization.substring(7).trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package com.labelsys.backend.controller;
|
||||||
|
|
||||||
|
import com.labelsys.backend.annotation.RequirePosition;
|
||||||
|
import com.labelsys.backend.common.Result;
|
||||||
|
import com.labelsys.backend.context.UserContext;
|
||||||
|
import com.labelsys.backend.dto.request.CreateUserRequest;
|
||||||
|
import com.labelsys.backend.dto.request.UpdateUserAssignmentRequest;
|
||||||
|
import com.labelsys.backend.dto.request.UpdateUserStatusRequest;
|
||||||
|
import com.labelsys.backend.dto.response.UserResponse;
|
||||||
|
import com.labelsys.backend.enums.UserPosition;
|
||||||
|
import com.labelsys.backend.service.UserService;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
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.PutMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
@Tag(name = "公司员工管理")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/company/users")
|
||||||
|
@RequirePosition(UserPosition.ADMIN)
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class CompanyUserController {
|
||||||
|
|
||||||
|
private final UserService userService;
|
||||||
|
|
||||||
|
@Operation(summary = "获取当前公司员工列表")
|
||||||
|
@GetMapping
|
||||||
|
public Result<List<UserResponse>> listUsers() {
|
||||||
|
return Result.success(userService.listCompanyUsers(UserContext.requireUser()).stream().map(UserResponse::from).toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "创建当前公司员工")
|
||||||
|
@PostMapping
|
||||||
|
public Result<UserResponse> createUser(@Valid @RequestBody CreateUserRequest request) {
|
||||||
|
return Result.success(UserResponse.from(userService.createCompanyUser(UserContext.requireUser(), request)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "修改员工角色和岗位")
|
||||||
|
@PutMapping("/{userId}/assignment")
|
||||||
|
public Result<Void> updateAssignment(@PathVariable Long userId, @Valid @RequestBody UpdateUserAssignmentRequest request) {
|
||||||
|
userService.updateAssignment(UserContext.requireUser(), userId, request);
|
||||||
|
return Result.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "修改员工状态")
|
||||||
|
@PutMapping("/{userId}/status")
|
||||||
|
public Result<Void> updateStatus(@PathVariable Long userId, @Valid @RequestBody UpdateUserStatusRequest request) {
|
||||||
|
userService.updateStatus(UserContext.requireUser(), userId, request);
|
||||||
|
return Result.success();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
// package com.labelsys.backend.controller;
|
||||||
|
|
||||||
|
// import com.labelsys.backend.common.Result;
|
||||||
|
// import com.labelsys.backend.context.UserContext;
|
||||||
|
// import com.labelsys.backend.dto.response.MenuResponse;
|
||||||
|
// import com.labelsys.backend.service.MenuService;
|
||||||
|
// import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
// import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
// import java.util.List;
|
||||||
|
// import lombok.RequiredArgsConstructor;
|
||||||
|
// import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
// import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
// import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
// @Tag(name = "菜单管理")
|
||||||
|
// @RestController
|
||||||
|
// @RequestMapping("/api/menus")
|
||||||
|
// @RequiredArgsConstructor
|
||||||
|
// public class MenuController {
|
||||||
|
|
||||||
|
// private final MenuService menuService;
|
||||||
|
|
||||||
|
// @Operation(summary = "获取当前用户菜单")
|
||||||
|
// @GetMapping("/current")
|
||||||
|
// public Result<List<MenuResponse>> currentMenus() {
|
||||||
|
// return Result.success(menuService.listCurrentMenus(UserContext.requireUser()));
|
||||||
|
// }
|
||||||
|
// }
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
package com.labelsys.backend.controller;
|
||||||
|
|
||||||
|
import com.labelsys.backend.annotation.RequirePosition;
|
||||||
|
import com.labelsys.backend.common.Result;
|
||||||
|
import com.labelsys.backend.context.UserContext;
|
||||||
|
import com.labelsys.backend.dto.request.CreateCompanyAdminRequest;
|
||||||
|
import com.labelsys.backend.dto.request.UpdateUserStatusRequest;
|
||||||
|
import com.labelsys.backend.dto.response.UserResponse;
|
||||||
|
import com.labelsys.backend.enums.UserPosition;
|
||||||
|
import com.labelsys.backend.service.UserService;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
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.PutMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
@Tag(name = "平台公司管理员管理")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/platform/company-admins")
|
||||||
|
@RequirePosition(UserPosition.ADMIN)
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class PlatformCompanyAdminController {
|
||||||
|
|
||||||
|
private final UserService userService;
|
||||||
|
|
||||||
|
@Operation(summary = "查询指定公司管理员列表")
|
||||||
|
@GetMapping
|
||||||
|
public Result<List<UserResponse>> listCompanyAdmins(@RequestParam Long companyId) {
|
||||||
|
return Result.success(userService.listCompanyAdmins(UserContext.requireUser(), companyId).stream().map(UserResponse::from).toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "创建公司管理员")
|
||||||
|
@PostMapping
|
||||||
|
public Result<UserResponse> createCompanyAdmin(@Valid @RequestBody CreateCompanyAdminRequest request) {
|
||||||
|
return Result.success(UserResponse.from(userService.createCompanyAdmin(UserContext.requireUser(), request)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "修改公司管理员状态")
|
||||||
|
@PutMapping("/{companyId}/{userId}/status")
|
||||||
|
public Result<Void> updateCompanyAdminStatus(
|
||||||
|
@PathVariable Long companyId,
|
||||||
|
@PathVariable Long userId,
|
||||||
|
@Valid @RequestBody UpdateUserStatusRequest request
|
||||||
|
) {
|
||||||
|
userService.updateCompanyAdminStatus(UserContext.requireUser(), companyId, userId, request);
|
||||||
|
return Result.success();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package com.labelsys.backend.controller;
|
||||||
|
|
||||||
|
import com.labelsys.backend.annotation.RequirePosition;
|
||||||
|
import com.labelsys.backend.common.Result;
|
||||||
|
import com.labelsys.backend.context.UserContext;
|
||||||
|
import com.labelsys.backend.dto.request.CreateCompanyRequest;
|
||||||
|
import com.labelsys.backend.dto.request.UpdateCompanyStatusRequest;
|
||||||
|
import com.labelsys.backend.dto.response.CompanyResponse;
|
||||||
|
import com.labelsys.backend.enums.UserPosition;
|
||||||
|
import com.labelsys.backend.service.CompanyService;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
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.PutMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
@Tag(name = "平台公司管理")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/platform/companies")
|
||||||
|
@RequirePosition(UserPosition.ADMIN)
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class PlatformCompanyController {
|
||||||
|
|
||||||
|
private final CompanyService companyService;
|
||||||
|
|
||||||
|
@Operation(summary = "获取公司列表")
|
||||||
|
@GetMapping
|
||||||
|
public Result<List<CompanyResponse>> listCompanies() {
|
||||||
|
return Result.success(companyService.listCompanies(UserContext.requireUser()).stream().map(CompanyResponse::from).toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "创建公司")
|
||||||
|
@PostMapping
|
||||||
|
public Result<CompanyResponse> createCompany(@Valid @RequestBody CreateCompanyRequest request) {
|
||||||
|
return Result.success(CompanyResponse.from(companyService.createCompany(UserContext.requireUser(), request)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "修改公司状态")
|
||||||
|
@PutMapping("/{companyId}/status")
|
||||||
|
public Result<Void> updateCompanyStatus(@PathVariable Long companyId, @Valid @RequestBody UpdateCompanyStatusRequest request) {
|
||||||
|
companyService.updateStatus(UserContext.requireUser(), companyId, request);
|
||||||
|
return Result.success();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package com.labelsys.backend.dto.request;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
|
||||||
|
@Schema(description = "修改密码请求")
|
||||||
|
public record ChangePasswordRequest(
|
||||||
|
@Schema(description = "旧密码") @NotBlank(message = "不能为空") String oldPassword,
|
||||||
|
@Schema(description = "新密码") @NotBlank(message = "不能为空") String newPassword,
|
||||||
|
@Schema(description = "确认新密码") @NotBlank(message = "不能为空") String confirmPassword
|
||||||
|
) {
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.labelsys.backend.dto.request;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
|
||||||
|
@Schema(description = "创建公司管理员请求")
|
||||||
|
public record CreateCompanyAdminRequest(
|
||||||
|
@Schema(description = "公司ID") @NotNull(message = "不能为空") Long companyId,
|
||||||
|
@Schema(description = "手机号") @NotBlank(message = "不能为空") String phone,
|
||||||
|
@Schema(description = "用户名,前端展示用,可为空", example = "platform-ops") String username,
|
||||||
|
@Schema(description = "真实姓名") @NotBlank(message = "不能为空") String realName
|
||||||
|
) {
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.labelsys.backend.dto.request;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
|
||||||
|
@Schema(description = "创建公司请求")
|
||||||
|
public record CreateCompanyRequest(
|
||||||
|
@Schema(description = "公司编码") @NotBlank(message = "不能为空") String companyCode,
|
||||||
|
@Schema(description = "公司名称") @NotBlank(message = "不能为空") String companyName
|
||||||
|
) {
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.labelsys.backend.dto.request;
|
||||||
|
|
||||||
|
import com.labelsys.backend.enums.UserPosition;
|
||||||
|
import com.labelsys.backend.enums.UserRole;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
|
||||||
|
@Schema(description = "创建员工请求")
|
||||||
|
public record CreateUserRequest(
|
||||||
|
@Schema(description = "手机号") @NotBlank(message = "不能为空") String phone,
|
||||||
|
@Schema(description = "用户名,前端展示用,可为空", example = "alpha-admin") String username,
|
||||||
|
@Schema(description = "真实姓名") @NotBlank(message = "不能为空") String realName,
|
||||||
|
@Schema(description = "角色,枚举值:EMPLOYEE员工、MANAGER部门经理、ENGINEER总工程师") @NotNull(message = "不能为空") UserRole role,
|
||||||
|
@Schema(description = "岗位,枚举值:UPLOADER上传者、ANNOTATOR标注员、DATA_TRAINER数据训练师、REVIEWER审核员、ADMIN超级管理员") @NotNull(message = "不能为空") UserPosition position
|
||||||
|
) {
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package com.labelsys.backend.dto.request;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
|
||||||
|
@Schema(description = "登录请求")
|
||||||
|
public record LoginRequest(
|
||||||
|
@Schema(description = "手机号") @NotBlank(message = "不能为空") String phone,
|
||||||
|
@Schema(description = "公司编码") @NotBlank(message = "不能为空") String companyCode,
|
||||||
|
@Schema(description = "登录密码") @NotBlank(message = "不能为空") String password
|
||||||
|
) {
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.labelsys.backend.dto.request;
|
||||||
|
|
||||||
|
import com.labelsys.backend.enums.CompanyStatus;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
|
||||||
|
@Schema(description = "修改公司状态请求")
|
||||||
|
public record UpdateCompanyStatusRequest(
|
||||||
|
@Schema(description = "公司状态,枚举值:ENABLED启用、DISABLED禁用") @NotNull(message = "不能为空") CompanyStatus status
|
||||||
|
) {
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.labelsys.backend.dto.request;
|
||||||
|
|
||||||
|
import com.labelsys.backend.enums.UserPosition;
|
||||||
|
import com.labelsys.backend.enums.UserRole;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
|
||||||
|
@Schema(description = "修改员工角色岗位请求")
|
||||||
|
public record UpdateUserAssignmentRequest(
|
||||||
|
@Schema(description = "角色,枚举值:EMPLOYEE员工、MANAGER部门经理、ENGINEER总工程师") @NotNull(message = "不能为空") UserRole role,
|
||||||
|
@Schema(description = "岗位,枚举值:UPLOADER上传者、ANNOTATOR标注员、DATA_TRAINER数据训练师、REVIEWER审核员、ADMIN超级管理员") @NotNull(message = "不能为空") UserPosition position
|
||||||
|
) {
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.labelsys.backend.dto.request;
|
||||||
|
|
||||||
|
import com.labelsys.backend.enums.UserStatus;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
|
||||||
|
@Schema(description = "修改员工状态请求")
|
||||||
|
public record UpdateUserStatusRequest(
|
||||||
|
@Schema(description = "用户状态,枚举值:ENABLED启用、DISABLED禁用") @NotNull(message = "不能为空") UserStatus status
|
||||||
|
) {
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.labelsys.backend.dto.response;
|
||||||
|
|
||||||
|
import com.labelsys.backend.entity.SysCompany;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
|
||||||
|
@Schema(description = "可登录公司选项")
|
||||||
|
public record CompanyOptionResponse(
|
||||||
|
@Schema(description = "公司ID") Long companyId,
|
||||||
|
@Schema(description = "公司编码") String companyCode,
|
||||||
|
@Schema(description = "公司名称") String companyName
|
||||||
|
) {
|
||||||
|
public static CompanyOptionResponse from(SysCompany company) {
|
||||||
|
return new CompanyOptionResponse(company.getId(), company.getCompanyCode(), company.getCompanyName());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.labelsys.backend.dto.response;
|
||||||
|
|
||||||
|
import com.labelsys.backend.entity.SysCompany;
|
||||||
|
import com.labelsys.backend.enums.CompanyStatus;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
|
||||||
|
@Schema(description = "公司响应")
|
||||||
|
public record CompanyResponse(
|
||||||
|
@Schema(description = "公司ID") Long companyId,
|
||||||
|
@Schema(description = "公司编码") String companyCode,
|
||||||
|
@Schema(description = "公司名称") String companyName,
|
||||||
|
@Schema(description = "公司状态,枚举值:ENABLED启用、DISABLED禁用") CompanyStatus status
|
||||||
|
) {
|
||||||
|
public static CompanyResponse from(SysCompany company) {
|
||||||
|
return new CompanyResponse(company.getId(), company.getCompanyCode(), company.getCompanyName(), company.getStatus());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package com.labelsys.backend.dto.response;
|
||||||
|
|
||||||
|
import com.labelsys.backend.context.LoginUser;
|
||||||
|
import com.labelsys.backend.enums.UserPosition;
|
||||||
|
import com.labelsys.backend.enums.UserRole;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
|
||||||
|
@Schema(description = "当前登录用户响应")
|
||||||
|
public record CurrentUserResponse(
|
||||||
|
@Schema(description = "用户ID") Long userId,
|
||||||
|
@Schema(description = "公司ID") Long companyId,
|
||||||
|
@Schema(description = "公司编码") String companyCode,
|
||||||
|
@Schema(description = "公司名称") String companyName,
|
||||||
|
@Schema(description = "手机号") String phone,
|
||||||
|
@Schema(description = "用户名,可为空", example = "alpha-admin") String username,
|
||||||
|
@Schema(description = "真实姓名") String realName,
|
||||||
|
@Schema(description = "角色,枚举值:EMPLOYEE员工、MANAGER部门经理、ENGINEER总工程师") UserRole role,
|
||||||
|
@Schema(description = "岗位,枚举值:UPLOADER上传者、ANNOTATOR标注员、DATA_TRAINER数据训练师、REVIEWER审核员、ADMIN超级管理员") UserPosition position,
|
||||||
|
@Schema(description = "是否必须修改密码") boolean mustChangePassword
|
||||||
|
) {
|
||||||
|
public static CurrentUserResponse from(LoginUser loginUser) {
|
||||||
|
return new CurrentUserResponse(
|
||||||
|
loginUser.userId(),
|
||||||
|
loginUser.companyId(),
|
||||||
|
loginUser.companyCode(),
|
||||||
|
loginUser.companyName(),
|
||||||
|
loginUser.phone(),
|
||||||
|
loginUser.username(),
|
||||||
|
loginUser.realName(),
|
||||||
|
loginUser.role(),
|
||||||
|
loginUser.position(),
|
||||||
|
loginUser.mustChangePassword()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package com.labelsys.backend.dto.response;
|
||||||
|
|
||||||
|
import com.labelsys.backend.entity.BizDataRecord;
|
||||||
|
import com.labelsys.backend.enums.UserRole;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
|
||||||
|
@Schema(description = "数据记录响应")
|
||||||
|
public record DataRecordResponse(
|
||||||
|
@Schema(description = "记录ID") Long id,
|
||||||
|
@Schema(description = "公司ID") Long companyId,
|
||||||
|
@Schema(description = "创建人ID") Long creatorId,
|
||||||
|
@Schema(description = "创建人角色,枚举值:EMPLOYEE员工、MANAGER部门经理、ENGINEER总工程师") UserRole creatorRole,
|
||||||
|
@Schema(description = "记录名称") String recordName
|
||||||
|
) {
|
||||||
|
public static DataRecordResponse from(BizDataRecord record) {
|
||||||
|
return new DataRecordResponse(
|
||||||
|
record.getId(),
|
||||||
|
record.getCompanyId(),
|
||||||
|
record.getCreatorId(),
|
||||||
|
record.getCreatorRole(),
|
||||||
|
record.getRecordName()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package com.labelsys.backend.dto.response;
|
||||||
|
|
||||||
|
import com.labelsys.backend.context.LoginUser;
|
||||||
|
import com.labelsys.backend.entity.SysCompany;
|
||||||
|
import com.labelsys.backend.enums.UserPosition;
|
||||||
|
import com.labelsys.backend.enums.UserRole;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
|
||||||
|
@Schema(description = "登录响应")
|
||||||
|
public record LoginResponse(
|
||||||
|
@Schema(description = "访问令牌") String token,
|
||||||
|
@Schema(description = "当前公司信息") CompanyOptionResponse company,
|
||||||
|
@Schema(description = "手机号") String phone,
|
||||||
|
@Schema(description = "用户名,可为空", example = "alpha-admin") String username,
|
||||||
|
@Schema(description = "真实姓名") String realName,
|
||||||
|
@Schema(description = "角色,枚举值:EMPLOYEE员工、MANAGER部门经理、ENGINEER总工程师") UserRole role,
|
||||||
|
@Schema(description = "岗位,枚举值:UPLOADER上传者、ANNOTATOR标注员、DATA_TRAINER数据训练师、REVIEWER审核员、ADMIN超级管理员") UserPosition position,
|
||||||
|
@Schema(description = "是否必须修改密码") boolean mustChangePassword
|
||||||
|
) {
|
||||||
|
public static LoginResponse from(String token, LoginUser loginUser, SysCompany company) {
|
||||||
|
return new LoginResponse(
|
||||||
|
token,
|
||||||
|
CompanyOptionResponse.from(company),
|
||||||
|
loginUser.phone(),
|
||||||
|
loginUser.username(),
|
||||||
|
loginUser.realName(),
|
||||||
|
loginUser.role(),
|
||||||
|
loginUser.position(),
|
||||||
|
loginUser.mustChangePassword()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.labelsys.backend.dto.response;
|
||||||
|
|
||||||
|
import com.labelsys.backend.entity.SysMenu;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
|
||||||
|
@Schema(description = "菜单响应")
|
||||||
|
public record MenuResponse(
|
||||||
|
@Schema(description = "菜单编码") String menuCode,
|
||||||
|
@Schema(description = "菜单名称") String menuName,
|
||||||
|
@Schema(description = "菜单路径") String path,
|
||||||
|
@Schema(description = "排序") Integer sortOrder
|
||||||
|
) {
|
||||||
|
public static MenuResponse from(SysMenu menu) {
|
||||||
|
return new MenuResponse(menu.getMenuCode(), menu.getMenuName(), menu.getPath(), menu.getSortOrder());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package com.labelsys.backend.dto.response;
|
||||||
|
|
||||||
|
import com.labelsys.backend.entity.SysUser;
|
||||||
|
import com.labelsys.backend.enums.UserPosition;
|
||||||
|
import com.labelsys.backend.enums.UserRole;
|
||||||
|
import com.labelsys.backend.enums.UserStatus;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
|
||||||
|
@Schema(description = "用户响应")
|
||||||
|
public record UserResponse(
|
||||||
|
@Schema(description = "用户ID") Long userId,
|
||||||
|
@Schema(description = "公司ID") Long companyId,
|
||||||
|
@Schema(description = "手机号") String phone,
|
||||||
|
@Schema(description = "用户名,可为空", example = "alpha-admin") String username,
|
||||||
|
@Schema(description = "真实姓名") String realName,
|
||||||
|
@Schema(description = "角色,枚举值:EMPLOYEE员工、MANAGER部门经理、ENGINEER总工程师") UserRole role,
|
||||||
|
@Schema(description = "岗位,枚举值:UPLOADER上传者、ANNOTATOR标注员、DATA_TRAINER数据训练师、REVIEWER审核员、ADMIN超级管理员") UserPosition position,
|
||||||
|
@Schema(description = "用户状态,枚举值:ENABLED启用、DISABLED禁用") UserStatus status,
|
||||||
|
@Schema(description = "是否必须修改密码") boolean mustChangePassword
|
||||||
|
) {
|
||||||
|
public static UserResponse from(SysUser user) {
|
||||||
|
return new UserResponse(
|
||||||
|
user.getId(),
|
||||||
|
user.getCompanyId(),
|
||||||
|
user.getPhone(),
|
||||||
|
user.getUsername(),
|
||||||
|
user.getRealName(),
|
||||||
|
user.getRole(),
|
||||||
|
user.getPosition(),
|
||||||
|
user.getStatus(),
|
||||||
|
Boolean.TRUE.equals(user.getMustChangePassword())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
27
src/main/java/com/labelsys/backend/entity/BizDataRecord.java
Normal file
27
src/main/java/com/labelsys/backend/entity/BizDataRecord.java
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package com.labelsys.backend.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import com.labelsys.backend.enums.UserRole;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@TableName("biz_data_record")
|
||||||
|
public class BizDataRecord {
|
||||||
|
@TableId(type = IdType.INPUT)
|
||||||
|
private Long id;
|
||||||
|
private Long companyId;
|
||||||
|
private Long creatorId;
|
||||||
|
private UserRole creatorRole;
|
||||||
|
private String recordName;
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
private LocalDateTime updatedAt;
|
||||||
|
}
|
||||||
26
src/main/java/com/labelsys/backend/entity/SysCompany.java
Normal file
26
src/main/java/com/labelsys/backend/entity/SysCompany.java
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package com.labelsys.backend.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import com.labelsys.backend.enums.CompanyStatus;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@TableName("sys_company")
|
||||||
|
public class SysCompany {
|
||||||
|
@TableId(type = IdType.INPUT)
|
||||||
|
private Long id;
|
||||||
|
private String companyCode;
|
||||||
|
private String companyName;
|
||||||
|
private CompanyStatus status;
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
private LocalDateTime updatedAt;
|
||||||
|
}
|
||||||
28
src/main/java/com/labelsys/backend/entity/SysMenu.java
Normal file
28
src/main/java/com/labelsys/backend/entity/SysMenu.java
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package com.labelsys.backend.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@TableName("sys_menu")
|
||||||
|
public class SysMenu {
|
||||||
|
@TableId(type = IdType.INPUT)
|
||||||
|
private Long id;
|
||||||
|
private Long companyId;
|
||||||
|
private String permissionCode;
|
||||||
|
private String menuCode;
|
||||||
|
private String menuName;
|
||||||
|
private String path;
|
||||||
|
private Integer sortOrder;
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
private LocalDateTime updatedAt;
|
||||||
|
}
|
||||||
35
src/main/java/com/labelsys/backend/entity/SysUser.java
Normal file
35
src/main/java/com/labelsys/backend/entity/SysUser.java
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package com.labelsys.backend.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import com.labelsys.backend.enums.UserPosition;
|
||||||
|
import com.labelsys.backend.enums.UserRole;
|
||||||
|
import com.labelsys.backend.enums.UserStatus;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@TableName("sys_user")
|
||||||
|
public class SysUser {
|
||||||
|
@TableId(type = IdType.INPUT)
|
||||||
|
private Long id;
|
||||||
|
private Long companyId;
|
||||||
|
private String phone;
|
||||||
|
private String username;
|
||||||
|
private UserRole role;
|
||||||
|
private UserPosition position;
|
||||||
|
private String realName;
|
||||||
|
private String passwordHash;
|
||||||
|
private Boolean mustChangePassword;
|
||||||
|
private UserStatus status;
|
||||||
|
private Integer sessionVersion;
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
private LocalDateTime updatedAt;
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.labelsys.backend.enums;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
|
||||||
|
@Schema(description = "公司状态,枚举值:ENABLED启用、DISABLED禁用")
|
||||||
|
public enum CompanyStatus {
|
||||||
|
ENABLED,
|
||||||
|
DISABLED
|
||||||
|
}
|
||||||
22
src/main/java/com/labelsys/backend/enums/UserPosition.java
Normal file
22
src/main/java/com/labelsys/backend/enums/UserPosition.java
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package com.labelsys.backend.enums;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Schema(description = "岗位枚举,枚举值:UPLOADER上传者、ANNOTATOR标注员、DATA_TRAINER数据训练师、REVIEWER审核员、ADMIN超级管理员")
|
||||||
|
public enum UserPosition {
|
||||||
|
UPLOADER(1),
|
||||||
|
ANNOTATOR(2),
|
||||||
|
DATA_TRAINER(3),
|
||||||
|
REVIEWER(4),
|
||||||
|
ADMIN(5);
|
||||||
|
|
||||||
|
private final int level;
|
||||||
|
|
||||||
|
public boolean canAccess(UserPosition required) {
|
||||||
|
return this.level >= required.level;
|
||||||
|
}
|
||||||
|
}
|
||||||
16
src/main/java/com/labelsys/backend/enums/UserRole.java
Normal file
16
src/main/java/com/labelsys/backend/enums/UserRole.java
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package com.labelsys.backend.enums;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Schema(description = "角色枚举,枚举值:EMPLOYEE员工、MANAGER部门经理、ENGINEER总工程师")
|
||||||
|
public enum UserRole {
|
||||||
|
EMPLOYEE(1),
|
||||||
|
MANAGER(2),
|
||||||
|
ENGINEER(3);
|
||||||
|
|
||||||
|
private final int level;
|
||||||
|
}
|
||||||
9
src/main/java/com/labelsys/backend/enums/UserStatus.java
Normal file
9
src/main/java/com/labelsys/backend/enums/UserStatus.java
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package com.labelsys.backend.enums;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
|
||||||
|
@Schema(description = "用户状态,枚举值:ENABLED启用、DISABLED禁用")
|
||||||
|
public enum UserStatus {
|
||||||
|
ENABLED,
|
||||||
|
DISABLED
|
||||||
|
}
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
package com.labelsys.backend.interceptor;
|
||||||
|
|
||||||
|
import com.labelsys.backend.annotation.RequirePosition;
|
||||||
|
import com.labelsys.backend.common.exception.ForbiddenException;
|
||||||
|
import com.labelsys.backend.common.exception.UnauthorizedException;
|
||||||
|
import com.labelsys.backend.context.LoginUser;
|
||||||
|
import com.labelsys.backend.context.UserContext;
|
||||||
|
import com.labelsys.backend.entity.SysCompany;
|
||||||
|
import com.labelsys.backend.entity.SysUser;
|
||||||
|
import com.labelsys.backend.enums.CompanyStatus;
|
||||||
|
import com.labelsys.backend.enums.UserPosition;
|
||||||
|
import com.labelsys.backend.enums.UserStatus;
|
||||||
|
import com.labelsys.backend.mapper.SysCompanyMapper;
|
||||||
|
import com.labelsys.backend.mapper.SysUserMapper;
|
||||||
|
import com.labelsys.backend.service.session.TokenSessionRepository;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.Set;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.method.HandlerMethod;
|
||||||
|
import org.springframework.web.servlet.HandlerInterceptor;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class AuthInterceptor implements HandlerInterceptor {
|
||||||
|
|
||||||
|
private static final Set<String> OPEN_PATHS = Set.of(
|
||||||
|
"/label/api/auth/companies",
|
||||||
|
"/label/api/auth/login",
|
||||||
|
"/label/swagger-ui.html",
|
||||||
|
"/label/v3/api-docs",
|
||||||
|
"/label/v3/api-docs/swagger-config"
|
||||||
|
);
|
||||||
|
|
||||||
|
private static final Set<String> ALLOWED_WHEN_MUST_CHANGE_PASSWORD = Set.of(
|
||||||
|
"/label/api/auth/change-password",
|
||||||
|
"/label/api/auth/logout",
|
||||||
|
"/label/api/auth/me"
|
||||||
|
);
|
||||||
|
|
||||||
|
private final TokenSessionRepository tokenSessionRepository;
|
||||||
|
private final SysUserMapper sysUserMapper;
|
||||||
|
private final SysCompanyMapper sysCompanyMapper;
|
||||||
|
private final Duration sessionTtl;
|
||||||
|
|
||||||
|
public AuthInterceptor(
|
||||||
|
TokenSessionRepository tokenSessionRepository,
|
||||||
|
SysUserMapper sysUserMapper,
|
||||||
|
SysCompanyMapper sysCompanyMapper,
|
||||||
|
@Value("${labelsys.session.ttl:PT2H}") Duration sessionTtl
|
||||||
|
) {
|
||||||
|
this.tokenSessionRepository = tokenSessionRepository;
|
||||||
|
this.sysUserMapper = sysUserMapper;
|
||||||
|
this.sysCompanyMapper = sysCompanyMapper;
|
||||||
|
this.sessionTtl = sessionTtl;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
|
||||||
|
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
String path = request.getRequestURI();
|
||||||
|
if (path.startsWith("/swagger-ui") || path.startsWith("/v3/api-docs") || OPEN_PATHS.contains(path)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(handler instanceof HandlerMethod handlerMethod)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
String token = extractToken(request.getHeader("Authorization"));
|
||||||
|
LoginUser loginUser = tokenSessionRepository.find(token)
|
||||||
|
.orElseThrow(() -> new UnauthorizedException("未登录或登录已过期"));
|
||||||
|
|
||||||
|
SysUser user = sysUserMapper.findByIdAndCompanyId(loginUser.userId(), loginUser.companyId());
|
||||||
|
SysCompany company = sysCompanyMapper.findById(loginUser.companyId());
|
||||||
|
if (user == null || company == null || user.getStatus() != UserStatus.ENABLED || company.getStatus() != CompanyStatus.ENABLED) {
|
||||||
|
throw new UnauthorizedException("未登录或登录已过期");
|
||||||
|
}
|
||||||
|
if (!user.getSessionVersion().equals(loginUser.sessionVersion())) {
|
||||||
|
throw new UnauthorizedException("登录状态已失效,请重新登录");
|
||||||
|
}
|
||||||
|
if (Boolean.TRUE.equals(user.getMustChangePassword()) && !ALLOWED_WHEN_MUST_CHANGE_PASSWORD.contains(path)) {
|
||||||
|
throw new ForbiddenException("首次登录后请先修改密码");
|
||||||
|
}
|
||||||
|
|
||||||
|
LoginUser refreshedUser = LoginUser.from(user, company);
|
||||||
|
UserContext.set(refreshedUser);
|
||||||
|
tokenSessionRepository.refresh(token, sessionTtl);
|
||||||
|
|
||||||
|
RequirePosition requirePosition = resolveRequirePosition(handlerMethod);
|
||||||
|
if (requirePosition != null && !refreshedUser.position().canAccess(requirePosition.value())) {
|
||||||
|
throw new ForbiddenException("当前岗位无权限访问");
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
|
||||||
|
UserContext.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String extractToken(String authorization) {
|
||||||
|
if (authorization == null || !authorization.startsWith("Bearer ")) {
|
||||||
|
throw new UnauthorizedException("未登录或登录已过期");
|
||||||
|
}
|
||||||
|
return authorization.substring(7).trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
private RequirePosition resolveRequirePosition(HandlerMethod handlerMethod) {
|
||||||
|
RequirePosition methodAnnotation = handlerMethod.getMethodAnnotation(RequirePosition.class);
|
||||||
|
if (methodAnnotation != null) {
|
||||||
|
return methodAnnotation;
|
||||||
|
}
|
||||||
|
return handlerMethod.getBeanType().getAnnotation(RequirePosition.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.labelsys.backend.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.labelsys.backend.entity.BizDataRecord;
|
||||||
|
import java.util.List;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
public interface BizDataRecordMapper extends BaseMapper<BizDataRecord> {
|
||||||
|
|
||||||
|
List<BizDataRecord> listVisibleByEmployee(@Param("companyId") Long companyId, @Param("creatorId") Long creatorId);
|
||||||
|
|
||||||
|
List<BizDataRecord> listVisibleByManager(@Param("companyId") Long companyId);
|
||||||
|
|
||||||
|
List<BizDataRecord> listVisibleByEngineer(@Param("companyId") Long companyId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package com.labelsys.backend.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.labelsys.backend.entity.SysCompany;
|
||||||
|
import com.labelsys.backend.enums.CompanyStatus;
|
||||||
|
import java.util.List;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
public interface SysCompanyMapper extends BaseMapper<SysCompany> {
|
||||||
|
|
||||||
|
int insert(SysCompany company);
|
||||||
|
|
||||||
|
SysCompany findById(@Param("id") Long id);
|
||||||
|
|
||||||
|
SysCompany findByCompanyCode(@Param("companyCode") String companyCode);
|
||||||
|
|
||||||
|
List<SysCompany> findEnabledCompaniesByPhone(@Param("phone") String phone);
|
||||||
|
|
||||||
|
List<SysCompany> listAll();
|
||||||
|
|
||||||
|
int updateStatus(@Param("id") Long id, @Param("status") CompanyStatus status);
|
||||||
|
|
||||||
|
void deleteAll();
|
||||||
|
}
|
||||||
11
src/main/java/com/labelsys/backend/mapper/SysMenuMapper.java
Normal file
11
src/main/java/com/labelsys/backend/mapper/SysMenuMapper.java
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package com.labelsys.backend.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.labelsys.backend.entity.SysMenu;
|
||||||
|
import java.util.List;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
public interface SysMenuMapper extends BaseMapper<SysMenu> {
|
||||||
|
|
||||||
|
List<SysMenu> listCurrentMenus(@Param("companyId") Long companyId, @Param("positionCodes") List<String> positionCodes);
|
||||||
|
}
|
||||||
44
src/main/java/com/labelsys/backend/mapper/SysUserMapper.java
Normal file
44
src/main/java/com/labelsys/backend/mapper/SysUserMapper.java
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
package com.labelsys.backend.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.labelsys.backend.entity.SysUser;
|
||||||
|
import com.labelsys.backend.enums.UserPosition;
|
||||||
|
import com.labelsys.backend.enums.UserRole;
|
||||||
|
import com.labelsys.backend.enums.UserStatus;
|
||||||
|
import java.util.List;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
public interface SysUserMapper extends BaseMapper<SysUser> {
|
||||||
|
|
||||||
|
int insert(SysUser user);
|
||||||
|
|
||||||
|
SysUser findById(@Param("id") Long id);
|
||||||
|
|
||||||
|
SysUser findByIdAndCompanyId(@Param("id") Long id, @Param("companyId") Long companyId);
|
||||||
|
|
||||||
|
SysUser findByCompanyIdAndPhone(@Param("companyId") Long companyId, @Param("phone") String phone);
|
||||||
|
|
||||||
|
List<SysUser> listByCompanyId(@Param("companyId") Long companyId);
|
||||||
|
|
||||||
|
List<SysUser> listCompanyAdmins(@Param("companyId") Long companyId);
|
||||||
|
|
||||||
|
int updatePassword(
|
||||||
|
@Param("id") Long id,
|
||||||
|
@Param("companyId") Long companyId,
|
||||||
|
@Param("passwordHash") String passwordHash,
|
||||||
|
@Param("mustChangePassword") boolean mustChangePassword
|
||||||
|
);
|
||||||
|
|
||||||
|
int updateAssignment(
|
||||||
|
@Param("id") Long id,
|
||||||
|
@Param("companyId") Long companyId,
|
||||||
|
@Param("role") UserRole role,
|
||||||
|
@Param("position") UserPosition position
|
||||||
|
);
|
||||||
|
|
||||||
|
int updateStatus(@Param("id") Long id, @Param("companyId") Long companyId, @Param("status") UserStatus status);
|
||||||
|
|
||||||
|
int bumpSessionVersion(@Param("id") Long id, @Param("companyId") Long companyId);
|
||||||
|
|
||||||
|
void deleteAll();
|
||||||
|
}
|
||||||
98
src/main/java/com/labelsys/backend/service/AuthService.java
Normal file
98
src/main/java/com/labelsys/backend/service/AuthService.java
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
package com.labelsys.backend.service;
|
||||||
|
|
||||||
|
import com.labelsys.backend.common.ResultCode;
|
||||||
|
import com.labelsys.backend.common.exception.BusinessException;
|
||||||
|
import com.labelsys.backend.common.exception.UnauthorizedException;
|
||||||
|
import com.labelsys.backend.context.LoginUser;
|
||||||
|
import com.labelsys.backend.dto.request.ChangePasswordRequest;
|
||||||
|
import com.labelsys.backend.dto.request.LoginRequest;
|
||||||
|
import com.labelsys.backend.dto.response.CompanyOptionResponse;
|
||||||
|
import com.labelsys.backend.dto.response.LoginResponse;
|
||||||
|
import com.labelsys.backend.entity.SysCompany;
|
||||||
|
import com.labelsys.backend.entity.SysUser;
|
||||||
|
import com.labelsys.backend.enums.CompanyStatus;
|
||||||
|
import com.labelsys.backend.enums.UserStatus;
|
||||||
|
import com.labelsys.backend.mapper.SysCompanyMapper;
|
||||||
|
import com.labelsys.backend.mapper.SysUserMapper;
|
||||||
|
import com.labelsys.backend.service.session.TokenSessionRepository;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class AuthService {
|
||||||
|
|
||||||
|
private final SysCompanyMapper sysCompanyMapper;
|
||||||
|
private final SysUserMapper sysUserMapper;
|
||||||
|
private final PasswordEncoder passwordEncoder;
|
||||||
|
private final TokenSessionRepository tokenSessionRepository;
|
||||||
|
|
||||||
|
@Value("${labelsys.session.ttl:PT2H}")
|
||||||
|
private Duration sessionTtl;
|
||||||
|
|
||||||
|
public List<CompanyOptionResponse> listAvailableCompanies(String phone) {
|
||||||
|
return sysCompanyMapper.findEnabledCompaniesByPhone(phone).stream()
|
||||||
|
.map(CompanyOptionResponse::from)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public LoginResponse login(LoginRequest request) {
|
||||||
|
SysCompany company = loadEnabledCompany(request.companyCode());
|
||||||
|
SysUser user = loadEnabledUser(company.getId(), request.phone());
|
||||||
|
if (!passwordEncoder.matches(request.password(), user.getPasswordHash())) {
|
||||||
|
throw new UnauthorizedException("手机号、公司或密码错误");
|
||||||
|
}
|
||||||
|
LoginUser loginUser = LoginUser.from(user, company);
|
||||||
|
String token = UUID.randomUUID().toString();
|
||||||
|
tokenSessionRepository.save(token, loginUser, sessionTtl);
|
||||||
|
return LoginResponse.from(token, loginUser, company);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LoginUser getCurrentUser(String token) {
|
||||||
|
return tokenSessionRepository.find(token)
|
||||||
|
.orElseThrow(() -> new UnauthorizedException("未登录或登录已过期"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public void changePassword(LoginUser currentUser, ChangePasswordRequest request) {
|
||||||
|
if (!request.newPassword().equals(request.confirmPassword())) {
|
||||||
|
throw new BusinessException(ResultCode.BAD_REQUEST, "两次输入的新密码不一致");
|
||||||
|
}
|
||||||
|
SysUser user = sysUserMapper.findByIdAndCompanyId(currentUser.userId(), currentUser.companyId());
|
||||||
|
if (user == null || user.getStatus() != UserStatus.ENABLED) {
|
||||||
|
throw new UnauthorizedException("未登录或登录已过期");
|
||||||
|
}
|
||||||
|
if (!passwordEncoder.matches(request.oldPassword(), user.getPasswordHash())) {
|
||||||
|
throw new BusinessException(ResultCode.BAD_REQUEST, "旧密码错误");
|
||||||
|
}
|
||||||
|
sysUserMapper.updatePassword(user.getId(), user.getCompanyId(), passwordEncoder.encode(request.newPassword()), false);
|
||||||
|
sysUserMapper.bumpSessionVersion(user.getId(), user.getCompanyId());
|
||||||
|
tokenSessionRepository.removeAll(user.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void logout(String token) {
|
||||||
|
tokenSessionRepository.remove(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
private SysCompany loadEnabledCompany(String companyCode) {
|
||||||
|
SysCompany company = sysCompanyMapper.findByCompanyCode(companyCode);
|
||||||
|
if (company == null || company.getStatus() != CompanyStatus.ENABLED) {
|
||||||
|
throw new UnauthorizedException("手机号、公司或密码错误");
|
||||||
|
}
|
||||||
|
return company;
|
||||||
|
}
|
||||||
|
|
||||||
|
private SysUser loadEnabledUser(Long companyId, String phone) {
|
||||||
|
SysUser user = sysUserMapper.findByCompanyIdAndPhone(companyId, phone);
|
||||||
|
if (user == null || user.getStatus() != UserStatus.ENABLED) {
|
||||||
|
throw new UnauthorizedException("手机号、公司或密码错误");
|
||||||
|
}
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package com.labelsys.backend.service;
|
||||||
|
|
||||||
|
import com.labelsys.backend.common.ResultCode;
|
||||||
|
import com.labelsys.backend.common.exception.BusinessException;
|
||||||
|
import com.labelsys.backend.common.exception.ForbiddenException;
|
||||||
|
import com.labelsys.backend.context.LoginUser;
|
||||||
|
import com.labelsys.backend.dto.request.CreateCompanyRequest;
|
||||||
|
import com.labelsys.backend.dto.request.UpdateCompanyStatusRequest;
|
||||||
|
import com.labelsys.backend.entity.SysCompany;
|
||||||
|
import com.labelsys.backend.mapper.SysCompanyMapper;
|
||||||
|
import com.labelsys.backend.util.IdGenerator;
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class CompanyService {
|
||||||
|
|
||||||
|
private final SysCompanyMapper sysCompanyMapper;
|
||||||
|
|
||||||
|
public List<SysCompany> listCompanies(LoginUser currentUser) {
|
||||||
|
assertPlatformAdmin(currentUser);
|
||||||
|
return sysCompanyMapper.listAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
public SysCompany createCompany(LoginUser currentUser, CreateCompanyRequest request) {
|
||||||
|
assertPlatformAdmin(currentUser);
|
||||||
|
if (sysCompanyMapper.findByCompanyCode(request.companyCode()) != null) {
|
||||||
|
throw new BusinessException(ResultCode.CONFLICT, "公司编码已存在");
|
||||||
|
}
|
||||||
|
SysCompany company = SysCompany.builder()
|
||||||
|
.id(IdGenerator.nextId())
|
||||||
|
.companyCode(request.companyCode())
|
||||||
|
.companyName(request.companyName())
|
||||||
|
.status(com.labelsys.backend.enums.CompanyStatus.ENABLED)
|
||||||
|
.build();
|
||||||
|
sysCompanyMapper.insert(company);
|
||||||
|
return company;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateStatus(LoginUser currentUser, Long companyId, UpdateCompanyStatusRequest request) {
|
||||||
|
assertPlatformAdmin(currentUser);
|
||||||
|
if (sysCompanyMapper.updateStatus(companyId, request.status()) == 0) {
|
||||||
|
throw new BusinessException(ResultCode.NOT_FOUND, "公司不存在");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertPlatformAdmin(LoginUser currentUser) {
|
||||||
|
if (!currentUser.isPlatformAdmin()) {
|
||||||
|
throw new ForbiddenException("仅平台管理员可操作");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
package com.labelsys.backend.service;
|
||||||
|
|
||||||
|
import com.labelsys.backend.context.LoginUser;
|
||||||
|
import com.labelsys.backend.entity.BizDataRecord;
|
||||||
|
import com.labelsys.backend.enums.UserRole;
|
||||||
|
import com.labelsys.backend.mapper.BizDataRecordMapper;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class DataPermissionService {
|
||||||
|
|
||||||
|
private final BizDataRecordMapper bizDataRecordMapper;
|
||||||
|
|
||||||
|
public List<BizDataRecord> listVisibleRecords(LoginUser currentUser) {
|
||||||
|
return switch (currentUser.role()) {
|
||||||
|
case EMPLOYEE -> bizDataRecordMapper.listVisibleByEmployee(currentUser.companyId(), currentUser.userId());
|
||||||
|
case MANAGER -> bizDataRecordMapper.listVisibleByManager(currentUser.companyId());
|
||||||
|
case ENGINEER -> bizDataRecordMapper.listVisibleByEngineer(currentUser.companyId());
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean canAccessCreator(LoginUser currentUser, Long creatorId, UserRole creatorRole) {
|
||||||
|
return switch (currentUser.role()) {
|
||||||
|
case EMPLOYEE -> currentUser.userId().equals(creatorId);
|
||||||
|
case MANAGER -> creatorRole == UserRole.EMPLOYEE || creatorRole == UserRole.MANAGER;
|
||||||
|
case ENGINEER -> true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通用数据过滤方法
|
||||||
|
*
|
||||||
|
* @param currentUser 当前登录用户
|
||||||
|
* @param allRecords 待过滤的全量数据列表
|
||||||
|
* @param roleExtractor 从数据对象中提取“关联角色”或“创建者角色”的函数
|
||||||
|
* @param ownerIdExtractor 从数据对象中提取“所有者ID”的函数(用于员工只能看自己的情况)
|
||||||
|
* @param <T> 数据类型
|
||||||
|
* @return 过滤后的数据列表
|
||||||
|
*/
|
||||||
|
public <T> List<T> filterByRole(
|
||||||
|
LoginUser currentUser,
|
||||||
|
List<T> allRecords,
|
||||||
|
Function<T, UserRole> roleExtractor,
|
||||||
|
Function<T, Long> ownerIdExtractor) {
|
||||||
|
|
||||||
|
if (allRecords == null || allRecords.isEmpty()) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
UserRole currentRole = currentUser.role();
|
||||||
|
Long currentUserId = currentUser.userId();
|
||||||
|
|
||||||
|
return allRecords.stream()
|
||||||
|
.filter(record -> {
|
||||||
|
UserRole recordRole = roleExtractor.apply(record);
|
||||||
|
Long recordOwnerId = ownerIdExtractor.apply(record);
|
||||||
|
|
||||||
|
return switch (currentRole) {
|
||||||
|
case EMPLOYEE ->
|
||||||
|
// 员工只能查看自己创建/拥有的数据
|
||||||
|
currentUserId.equals(recordOwnerId);
|
||||||
|
|
||||||
|
case MANAGER ->
|
||||||
|
// 经理可以查看员工和经理的数据,不能查看总工程师的数据
|
||||||
|
recordRole == UserRole.EMPLOYEE || recordRole == UserRole.MANAGER;
|
||||||
|
|
||||||
|
case ENGINEER ->
|
||||||
|
// 总工程师可以查看所有数据
|
||||||
|
true;
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 针对 BizDataRecord 的便捷调用方法
|
||||||
|
*/
|
||||||
|
// public List<BizDataRecord> listVisibleRecordsGeneric(LoginUser currentUser, List<BizDataRecord> allRecords) {
|
||||||
|
// return filterByRole(
|
||||||
|
// currentUser,
|
||||||
|
// allRecords,
|
||||||
|
// BizDataRecord::
|
||||||
|
// BizDataRecord::getCreatorRole, // 提取创建者角色
|
||||||
|
// BizDataRecord::getCreatorId // 提取创建者ID
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
}
|
||||||
26
src/main/java/com/labelsys/backend/service/MenuService.java
Normal file
26
src/main/java/com/labelsys/backend/service/MenuService.java
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package com.labelsys.backend.service;
|
||||||
|
|
||||||
|
import com.labelsys.backend.context.LoginUser;
|
||||||
|
import com.labelsys.backend.dto.response.MenuResponse;
|
||||||
|
import com.labelsys.backend.mapper.SysMenuMapper;
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class MenuService {
|
||||||
|
|
||||||
|
private final SysMenuMapper sysMenuMapper;
|
||||||
|
|
||||||
|
public List<MenuResponse> listCurrentMenus(LoginUser currentUser) {
|
||||||
|
List<String> positionCodes = java.util.Arrays.stream(com.labelsys.backend.enums.UserPosition.values())
|
||||||
|
.filter(position -> currentUser.position().canAccess(position))
|
||||||
|
.map(Enum::name)
|
||||||
|
.toList();
|
||||||
|
return sysMenuMapper.listCurrentMenus(currentUser.companyId(), positionCodes)
|
||||||
|
.stream()
|
||||||
|
.map(MenuResponse::from)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
134
src/main/java/com/labelsys/backend/service/UserService.java
Normal file
134
src/main/java/com/labelsys/backend/service/UserService.java
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
package com.labelsys.backend.service;
|
||||||
|
|
||||||
|
import com.labelsys.backend.common.ResultCode;
|
||||||
|
import com.labelsys.backend.common.exception.BusinessException;
|
||||||
|
import com.labelsys.backend.common.exception.ForbiddenException;
|
||||||
|
import com.labelsys.backend.context.LoginUser;
|
||||||
|
import com.labelsys.backend.dto.request.CreateCompanyAdminRequest;
|
||||||
|
import com.labelsys.backend.dto.request.CreateUserRequest;
|
||||||
|
import com.labelsys.backend.dto.request.UpdateUserAssignmentRequest;
|
||||||
|
import com.labelsys.backend.dto.request.UpdateUserStatusRequest;
|
||||||
|
import com.labelsys.backend.entity.SysCompany;
|
||||||
|
import com.labelsys.backend.entity.SysUser;
|
||||||
|
import com.labelsys.backend.enums.CompanyStatus;
|
||||||
|
import com.labelsys.backend.enums.UserPosition;
|
||||||
|
import com.labelsys.backend.enums.UserRole;
|
||||||
|
import com.labelsys.backend.enums.UserStatus;
|
||||||
|
import com.labelsys.backend.mapper.SysCompanyMapper;
|
||||||
|
import com.labelsys.backend.mapper.SysUserMapper;
|
||||||
|
import com.labelsys.backend.service.session.TokenSessionRepository;
|
||||||
|
import com.labelsys.backend.util.IdGenerator;
|
||||||
|
import java.util.List;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class UserService {
|
||||||
|
|
||||||
|
private static final String DEFAULT_PASSWORD = "123456";
|
||||||
|
|
||||||
|
private final SysUserMapper sysUserMapper;
|
||||||
|
private final SysCompanyMapper sysCompanyMapper;
|
||||||
|
private final PasswordEncoder passwordEncoder;
|
||||||
|
private final TokenSessionRepository tokenSessionRepository;
|
||||||
|
|
||||||
|
public List<SysUser> listCompanyUsers(LoginUser currentUser) {
|
||||||
|
assertCompanyAdmin(currentUser);
|
||||||
|
return sysUserMapper.listByCompanyId(currentUser.companyId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<SysUser> listCompanyAdmins(LoginUser currentUser, Long companyId) {
|
||||||
|
assertPlatformAdmin(currentUser);
|
||||||
|
return sysUserMapper.listCompanyAdmins(companyId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SysUser createCompanyAdmin(LoginUser currentUser, CreateCompanyAdminRequest request) {
|
||||||
|
assertPlatformAdmin(currentUser);
|
||||||
|
ensureEnabledCompany(request.companyId());
|
||||||
|
return createUser(
|
||||||
|
request.companyId(),
|
||||||
|
new CreateUserRequest(request.phone(), request.username(), request.realName(), UserRole.EMPLOYEE, UserPosition.ADMIN)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SysUser createCompanyUser(LoginUser currentUser, CreateUserRequest request) {
|
||||||
|
assertCompanyAdmin(currentUser);
|
||||||
|
return createUser(currentUser.companyId(), request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SysUser createCompanyUser(LoginUser currentUser, Long companyId, CreateUserRequest request) {
|
||||||
|
assertPlatformAdmin(currentUser);
|
||||||
|
ensureEnabledCompany(companyId);
|
||||||
|
return createUser(companyId, request);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public void updateAssignment(LoginUser currentUser, Long userId, UpdateUserAssignmentRequest request) {
|
||||||
|
assertCompanyAdmin(currentUser);
|
||||||
|
if (sysUserMapper.updateAssignment(userId, currentUser.companyId(), request.role(), request.position()) == 0) {
|
||||||
|
throw new BusinessException(ResultCode.NOT_FOUND, "用户不存在");
|
||||||
|
}
|
||||||
|
tokenSessionRepository.removeAll(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public void updateStatus(LoginUser currentUser, Long userId, UpdateUserStatusRequest request) {
|
||||||
|
assertCompanyAdmin(currentUser);
|
||||||
|
if (sysUserMapper.updateStatus(userId, currentUser.companyId(), request.status()) == 0) {
|
||||||
|
throw new BusinessException(ResultCode.NOT_FOUND, "用户不存在");
|
||||||
|
}
|
||||||
|
tokenSessionRepository.removeAll(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public void updateCompanyAdminStatus(LoginUser currentUser, Long companyId, Long userId, UpdateUserStatusRequest request) {
|
||||||
|
assertPlatformAdmin(currentUser);
|
||||||
|
if (sysUserMapper.updateStatus(userId, companyId, request.status()) == 0) {
|
||||||
|
throw new BusinessException(ResultCode.NOT_FOUND, "用户不存在");
|
||||||
|
}
|
||||||
|
tokenSessionRepository.removeAll(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private SysUser createUser(Long companyId, CreateUserRequest request) {
|
||||||
|
if (sysUserMapper.findByCompanyIdAndPhone(companyId, request.phone()) != null) {
|
||||||
|
throw new BusinessException(ResultCode.CONFLICT, "同一公司内手机号已存在");
|
||||||
|
}
|
||||||
|
SysUser user = SysUser.builder()
|
||||||
|
.id(IdGenerator.nextId())
|
||||||
|
.companyId(companyId)
|
||||||
|
.phone(request.phone())
|
||||||
|
.username(request.username())
|
||||||
|
.realName(request.realName())
|
||||||
|
.role(request.role())
|
||||||
|
.position(request.position())
|
||||||
|
.passwordHash(passwordEncoder.encode(DEFAULT_PASSWORD))
|
||||||
|
.mustChangePassword(true)
|
||||||
|
.status(UserStatus.ENABLED)
|
||||||
|
.sessionVersion(1)
|
||||||
|
.build();
|
||||||
|
sysUserMapper.insert(user);
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ensureEnabledCompany(Long companyId) {
|
||||||
|
SysCompany company = sysCompanyMapper.findById(companyId);
|
||||||
|
if (company == null || company.getStatus() != CompanyStatus.ENABLED) {
|
||||||
|
throw new BusinessException(ResultCode.NOT_FOUND, "公司不存在或已禁用");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertPlatformAdmin(LoginUser currentUser) {
|
||||||
|
if (!currentUser.isPlatformAdmin()) {
|
||||||
|
throw new ForbiddenException("仅平台管理员可操作");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertCompanyAdmin(LoginUser currentUser) {
|
||||||
|
if (currentUser.isPlatformAdmin() || currentUser.position() != UserPosition.ADMIN) {
|
||||||
|
throw new ForbiddenException("仅公司管理员可操作");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package com.labelsys.backend.service.session;
|
||||||
|
|
||||||
|
import com.labelsys.backend.context.LoginUser;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
@ConditionalOnProperty(prefix = "labelsys.session", name = "store-type", havingValue = "memory")
|
||||||
|
public class InMemoryTokenSessionRepository implements TokenSessionRepository {
|
||||||
|
|
||||||
|
private final Map<String, SessionEntry> sessions = new ConcurrentHashMap<>();
|
||||||
|
private final Map<Long, Set<String>> userTokens = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void save(String token, LoginUser loginUser, Duration ttl) {
|
||||||
|
sessions.put(token, new SessionEntry(loginUser, Instant.now().plus(ttl)));
|
||||||
|
userTokens.computeIfAbsent(loginUser.userId(), ignored -> ConcurrentHashMap.newKeySet()).add(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<LoginUser> find(String token) {
|
||||||
|
SessionEntry entry = sessions.get(token);
|
||||||
|
if (entry == null) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
if (entry.expiresAt().isBefore(Instant.now())) {
|
||||||
|
remove(token);
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
return Optional.of(entry.loginUser());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void refresh(String token, Duration ttl) {
|
||||||
|
SessionEntry entry = sessions.get(token);
|
||||||
|
if (entry != null) {
|
||||||
|
sessions.put(token, new SessionEntry(entry.loginUser(), Instant.now().plus(ttl)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remove(String token) {
|
||||||
|
SessionEntry removed = sessions.remove(token);
|
||||||
|
if (removed != null) {
|
||||||
|
Set<String> tokens = userTokens.get(removed.loginUser().userId());
|
||||||
|
if (tokens != null) {
|
||||||
|
tokens.remove(token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeAll(Long userId) {
|
||||||
|
Set<String> tokens = userTokens.remove(userId);
|
||||||
|
if (tokens != null) {
|
||||||
|
tokens.forEach(sessions::remove);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private record SessionEntry(LoginUser loginUser, Instant expiresAt) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
package com.labelsys.backend.service.session;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.labelsys.backend.common.exception.BusinessException;
|
||||||
|
import com.labelsys.backend.common.ResultCode;
|
||||||
|
import com.labelsys.backend.context.LoginUser;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
|
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
@ConditionalOnProperty(prefix = "labelsys.session", name = "store-type", havingValue = "redis", matchIfMissing = true)
|
||||||
|
public class RedisTokenSessionRepository implements TokenSessionRepository {
|
||||||
|
|
||||||
|
private static final String TOKEN_PREFIX = "token:";
|
||||||
|
private static final String USER_TOKENS_PREFIX = "user:tokens:";
|
||||||
|
|
||||||
|
private final StringRedisTemplate redisTemplate;
|
||||||
|
private final ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
public RedisTokenSessionRepository(StringRedisTemplate redisTemplate, ObjectMapper objectMapper) {
|
||||||
|
this.redisTemplate = redisTemplate;
|
||||||
|
this.objectMapper = objectMapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void save(String token, LoginUser loginUser, Duration ttl) {
|
||||||
|
try {
|
||||||
|
redisTemplate.opsForValue().set(tokenKey(token), objectMapper.writeValueAsString(loginUser), ttl);
|
||||||
|
redisTemplate.opsForSet().add(userTokensKey(loginUser.userId()), token);
|
||||||
|
redisTemplate.expire(userTokensKey(loginUser.userId()), ttl);
|
||||||
|
} catch (JsonProcessingException exception) {
|
||||||
|
throw new BusinessException(ResultCode.ERROR, "登录态序列化失败");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<LoginUser> find(String token) {
|
||||||
|
String value = redisTemplate.opsForValue().get(tokenKey(token));
|
||||||
|
if (value == null) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return Optional.of(objectMapper.readValue(value, LoginUser.class));
|
||||||
|
} catch (JsonProcessingException exception) {
|
||||||
|
remove(token);
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void refresh(String token, Duration ttl) {
|
||||||
|
redisTemplate.expire(tokenKey(token), ttl);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remove(String token) {
|
||||||
|
find(token).ifPresent(loginUser -> redisTemplate.opsForSet().remove(userTokensKey(loginUser.userId()), token));
|
||||||
|
redisTemplate.delete(tokenKey(token));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeAll(Long userId) {
|
||||||
|
Set<String> tokens = redisTemplate.opsForSet().members(userTokensKey(userId));
|
||||||
|
if (tokens != null && !tokens.isEmpty()) {
|
||||||
|
redisTemplate.delete(tokens.stream().map(this::tokenKey).toList());
|
||||||
|
}
|
||||||
|
redisTemplate.delete(userTokensKey(userId));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String tokenKey(String token) {
|
||||||
|
return TOKEN_PREFIX + token;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String userTokensKey(Long userId) {
|
||||||
|
return USER_TOKENS_PREFIX + userId;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.labelsys.backend.service.session;
|
||||||
|
|
||||||
|
import com.labelsys.backend.context.LoginUser;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public interface TokenSessionRepository {
|
||||||
|
|
||||||
|
void save(String token, LoginUser loginUser, Duration ttl);
|
||||||
|
|
||||||
|
Optional<LoginUser> find(String token);
|
||||||
|
|
||||||
|
void refresh(String token, Duration ttl);
|
||||||
|
|
||||||
|
void remove(String token);
|
||||||
|
|
||||||
|
void removeAll(Long userId);
|
||||||
|
}
|
||||||
16
src/main/java/com/labelsys/backend/util/IdGenerator.java
Normal file
16
src/main/java/com/labelsys/backend/util/IdGenerator.java
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package com.labelsys.backend.util;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
public final class IdGenerator {
|
||||||
|
|
||||||
|
private static final AtomicInteger SEQUENCE = new AtomicInteger();
|
||||||
|
|
||||||
|
private IdGenerator() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long nextId() {
|
||||||
|
int sequence = SEQUENCE.updateAndGet(value -> value >= 999 ? 0 : value + 1);
|
||||||
|
return System.currentTimeMillis() * 1000 + sequence;
|
||||||
|
}
|
||||||
|
}
|
||||||
40
src/main/resources/application.yml
Normal file
40
src/main/resources/application.yml
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
server:
|
||||||
|
port: 18082
|
||||||
|
servlet:
|
||||||
|
context-path: /label
|
||||||
|
|
||||||
|
spring:
|
||||||
|
application:
|
||||||
|
name: lablesys-backend
|
||||||
|
datasource:
|
||||||
|
url: ${DB_URL:jdbc:postgresql://39.107.112.174:5432/lablesystem}
|
||||||
|
username: ${DB_USERNAME:postgres}
|
||||||
|
password: ${DB_PASSWORD:postgres!Pw}
|
||||||
|
driver-class-name: org.postgresql.Driver
|
||||||
|
data:
|
||||||
|
redis:
|
||||||
|
host: ${REDIS_HOST:39.107.227.165}
|
||||||
|
port: ${REDIS_PORT:6379}
|
||||||
|
password: ${REDIS_PASSWORD:jsti@2024}
|
||||||
|
timeout: 5s
|
||||||
|
sql:
|
||||||
|
init:
|
||||||
|
mode: never
|
||||||
|
|
||||||
|
mybatis-plus:
|
||||||
|
mapper-locations: classpath*:mapper/*.xml
|
||||||
|
configuration:
|
||||||
|
map-underscore-to-camel-case: true
|
||||||
|
|
||||||
|
springdoc:
|
||||||
|
swagger-ui:
|
||||||
|
path: /swagger-ui.html
|
||||||
|
|
||||||
|
labelsys:
|
||||||
|
session:
|
||||||
|
ttl: PT2H
|
||||||
|
store-type: redis
|
||||||
|
|
||||||
|
logging:
|
||||||
|
level:
|
||||||
|
com.labelsys.backend: DEBUG
|
||||||
35
src/main/resources/logback.xml
Normal file
35
src/main/resources/logback.xml
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<configuration scan="true" scanPeriod="60 seconds">
|
||||||
|
|
||||||
|
<property name="LOG_PATH" value="${LOG_PATH:-logs}"/>
|
||||||
|
<property name="APP_NAME" value="label-backend"/>
|
||||||
|
<property name="LOG_PATTERN"
|
||||||
|
value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n"/>
|
||||||
|
|
||||||
|
<!-- 控制台输出(Docker 日志采集依赖 stdout) -->
|
||||||
|
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
<encoder charset="UTF-8">
|
||||||
|
<pattern>${LOG_PATTERN}</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<!-- 滚动文件:60 MB / 个,按日分组,保留 30 天,总上限 3 GB -->
|
||||||
|
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
|
<file>${LOG_PATH}/${APP_NAME}.log</file>
|
||||||
|
<encoder charset="UTF-8">
|
||||||
|
<pattern>${LOG_PATTERN}</pattern>
|
||||||
|
</encoder>
|
||||||
|
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||||
|
<fileNamePattern>${LOG_PATH}/${APP_NAME}.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
|
||||||
|
<maxFileSize>60MB</maxFileSize>
|
||||||
|
<maxHistory>30</maxHistory>
|
||||||
|
<totalSizeCap>3GB</totalSizeCap>
|
||||||
|
</rollingPolicy>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<root level="INFO">
|
||||||
|
<appender-ref ref="CONSOLE"/>
|
||||||
|
<appender-ref ref="FILE"/>
|
||||||
|
</root>
|
||||||
|
|
||||||
|
</configuration>
|
||||||
40
src/main/resources/mapper/BizDataRecordMapper.xml
Normal file
40
src/main/resources/mapper/BizDataRecordMapper.xml
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.labelsys.backend.mapper.BizDataRecordMapper">
|
||||||
|
<resultMap id="BizDataRecordResultMap" type="com.labelsys.backend.entity.BizDataRecord">
|
||||||
|
<id column="id" property="id"/>
|
||||||
|
<result column="company_id" property="companyId"/>
|
||||||
|
<result column="creator_id" property="creatorId"/>
|
||||||
|
<result column="creator_role" property="creatorRole"/>
|
||||||
|
<result column="record_name" property="recordName"/>
|
||||||
|
<result column="created_at" property="createdAt"/>
|
||||||
|
<result column="updated_at" property="updatedAt"/>
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
<sql id="RecordColumns">
|
||||||
|
id, company_id, creator_id, creator_role, record_name, created_at, updated_at
|
||||||
|
</sql>
|
||||||
|
|
||||||
|
<select id="listVisibleByEmployee" resultMap="BizDataRecordResultMap">
|
||||||
|
select <include refid="RecordColumns"/>
|
||||||
|
from biz_data_record
|
||||||
|
where company_id = #{companyId}
|
||||||
|
and creator_id = #{creatorId}
|
||||||
|
order by id
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="listVisibleByManager" resultMap="BizDataRecordResultMap">
|
||||||
|
select <include refid="RecordColumns"/>
|
||||||
|
from biz_data_record
|
||||||
|
where company_id = #{companyId}
|
||||||
|
and creator_role in ('EMPLOYEE', 'MANAGER')
|
||||||
|
order by id
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="listVisibleByEngineer" resultMap="BizDataRecordResultMap">
|
||||||
|
select <include refid="RecordColumns"/>
|
||||||
|
from biz_data_record
|
||||||
|
where company_id = #{companyId}
|
||||||
|
order by id
|
||||||
|
</select>
|
||||||
|
</mapper>
|
||||||
59
src/main/resources/mapper/SysCompanyMapper.xml
Normal file
59
src/main/resources/mapper/SysCompanyMapper.xml
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.labelsys.backend.mapper.SysCompanyMapper">
|
||||||
|
<resultMap id="SysCompanyResultMap" type="com.labelsys.backend.entity.SysCompany">
|
||||||
|
<id column="id" property="id"/>
|
||||||
|
<result column="company_code" property="companyCode"/>
|
||||||
|
<result column="company_name" property="companyName"/>
|
||||||
|
<result column="status" property="status"/>
|
||||||
|
<result column="created_at" property="createdAt"/>
|
||||||
|
<result column="updated_at" property="updatedAt"/>
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
<sql id="CompanyColumns">
|
||||||
|
id, company_code, company_name, status, created_at, updated_at
|
||||||
|
</sql>
|
||||||
|
|
||||||
|
<insert id="insert" parameterType="com.labelsys.backend.entity.SysCompany">
|
||||||
|
insert into sys_company (id, company_code, company_name, status, created_at, updated_at)
|
||||||
|
values (#{id}, #{companyCode}, #{companyName}, #{status}, current_timestamp, current_timestamp)
|
||||||
|
</insert>
|
||||||
|
|
||||||
|
<select id="findById" resultMap="SysCompanyResultMap">
|
||||||
|
select <include refid="CompanyColumns"/>
|
||||||
|
from sys_company
|
||||||
|
where id = #{id}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="findByCompanyCode" resultMap="SysCompanyResultMap">
|
||||||
|
select <include refid="CompanyColumns"/>
|
||||||
|
from sys_company
|
||||||
|
where company_code = #{companyCode}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="findEnabledCompaniesByPhone" resultMap="SysCompanyResultMap">
|
||||||
|
select distinct c.id, c.company_code, c.company_name, c.status, c.created_at, c.updated_at
|
||||||
|
from sys_company c
|
||||||
|
inner join sys_user u on u.company_id = c.id
|
||||||
|
where u.phone = #{phone}
|
||||||
|
and u.status = 'ENABLED'
|
||||||
|
and c.status = 'ENABLED'
|
||||||
|
order by c.id
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="listAll" resultMap="SysCompanyResultMap">
|
||||||
|
select <include refid="CompanyColumns"/>
|
||||||
|
from sys_company
|
||||||
|
order by id
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<update id="updateStatus">
|
||||||
|
update sys_company
|
||||||
|
set status = #{status}, updated_at = current_timestamp
|
||||||
|
where id = #{id}
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<delete id="deleteAll">
|
||||||
|
delete from sys_company
|
||||||
|
</delete>
|
||||||
|
</mapper>
|
||||||
29
src/main/resources/mapper/SysMenuMapper.xml
Normal file
29
src/main/resources/mapper/SysMenuMapper.xml
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.labelsys.backend.mapper.SysMenuMapper">
|
||||||
|
<resultMap id="SysMenuResultMap" type="com.labelsys.backend.entity.SysMenu">
|
||||||
|
<id column="id" property="id"/>
|
||||||
|
<result column="company_id" property="companyId"/>
|
||||||
|
<result column="permission_code" property="permissionCode"/>
|
||||||
|
<result column="menu_code" property="menuCode"/>
|
||||||
|
<result column="menu_name" property="menuName"/>
|
||||||
|
<result column="path" property="path"/>
|
||||||
|
<result column="sort_order" property="sortOrder"/>
|
||||||
|
<result column="created_at" property="createdAt"/>
|
||||||
|
<result column="updated_at" property="updatedAt"/>
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
<select id="listCurrentMenus" resultMap="SysMenuResultMap">
|
||||||
|
select distinct m.id, m.company_id, m.permission_code, m.menu_code, m.menu_name, m.path, m.sort_order, m.created_at, m.updated_at
|
||||||
|
from sys_menu m
|
||||||
|
inner join sys_position_permission pp
|
||||||
|
on pp.company_id = m.company_id
|
||||||
|
and pp.permission_code = m.permission_code
|
||||||
|
where m.company_id = #{companyId}
|
||||||
|
and pp.position_code in
|
||||||
|
<foreach collection="positionCodes" item="positionCode" open="(" separator="," close=")">
|
||||||
|
#{positionCode}
|
||||||
|
</foreach>
|
||||||
|
order by m.sort_order, m.id
|
||||||
|
</select>
|
||||||
|
</mapper>
|
||||||
94
src/main/resources/mapper/SysUserMapper.xml
Normal file
94
src/main/resources/mapper/SysUserMapper.xml
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||||
|
<mapper namespace="com.labelsys.backend.mapper.SysUserMapper">
|
||||||
|
<resultMap id="SysUserResultMap" type="com.labelsys.backend.entity.SysUser">
|
||||||
|
<id column="id" property="id"/>
|
||||||
|
<result column="company_id" property="companyId"/>
|
||||||
|
<result column="phone" property="phone"/>
|
||||||
|
<result column="username" property="username"/>
|
||||||
|
<result column="role" property="role"/>
|
||||||
|
<result column="position" property="position"/>
|
||||||
|
<result column="real_name" property="realName"/>
|
||||||
|
<result column="password_hash" property="passwordHash"/>
|
||||||
|
<result column="must_change_password" property="mustChangePassword"/>
|
||||||
|
<result column="status" property="status"/>
|
||||||
|
<result column="session_version" property="sessionVersion"/>
|
||||||
|
<result column="created_at" property="createdAt"/>
|
||||||
|
<result column="updated_at" property="updatedAt"/>
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
<sql id="UserColumns">
|
||||||
|
id, company_id, phone, username, role, position, real_name, password_hash, must_change_password,
|
||||||
|
status, session_version, created_at, updated_at
|
||||||
|
</sql>
|
||||||
|
|
||||||
|
<insert id="insert" parameterType="com.labelsys.backend.entity.SysUser">
|
||||||
|
insert into sys_user (
|
||||||
|
id, company_id, phone, username, role, position, real_name, password_hash, must_change_password,
|
||||||
|
status, session_version, created_at, updated_at
|
||||||
|
)
|
||||||
|
values (
|
||||||
|
#{id}, #{companyId}, #{phone}, #{username}, #{role}, #{position}, #{realName}, #{passwordHash}, #{mustChangePassword},
|
||||||
|
#{status}, #{sessionVersion}, current_timestamp, current_timestamp
|
||||||
|
)
|
||||||
|
</insert>
|
||||||
|
|
||||||
|
<select id="findById" resultMap="SysUserResultMap">
|
||||||
|
select <include refid="UserColumns"/> from sys_user where id = #{id}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="findByIdAndCompanyId" resultMap="SysUserResultMap">
|
||||||
|
select <include refid="UserColumns"/> from sys_user where id = #{id} and company_id = #{companyId}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="findByCompanyIdAndPhone" resultMap="SysUserResultMap">
|
||||||
|
select <include refid="UserColumns"/> from sys_user where company_id = #{companyId} and phone = #{phone}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="listByCompanyId" resultMap="SysUserResultMap">
|
||||||
|
select <include refid="UserColumns"/> from sys_user where company_id = #{companyId} order by id
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<select id="listCompanyAdmins" resultMap="SysUserResultMap">
|
||||||
|
select <include refid="UserColumns"/>
|
||||||
|
from sys_user
|
||||||
|
where company_id = #{companyId} and position = 'ADMIN'
|
||||||
|
order by id
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<update id="updatePassword">
|
||||||
|
update sys_user
|
||||||
|
set password_hash = #{passwordHash},
|
||||||
|
must_change_password = #{mustChangePassword},
|
||||||
|
updated_at = current_timestamp
|
||||||
|
where id = #{id} and company_id = #{companyId}
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<update id="updateAssignment">
|
||||||
|
update sys_user
|
||||||
|
set role = #{role},
|
||||||
|
position = #{position},
|
||||||
|
session_version = session_version + 1,
|
||||||
|
updated_at = current_timestamp
|
||||||
|
where id = #{id} and company_id = #{companyId}
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<update id="updateStatus">
|
||||||
|
update sys_user
|
||||||
|
set status = #{status},
|
||||||
|
session_version = session_version + 1,
|
||||||
|
updated_at = current_timestamp
|
||||||
|
where id = #{id} and company_id = #{companyId}
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<update id="bumpSessionVersion">
|
||||||
|
update sys_user
|
||||||
|
set session_version = session_version + 1,
|
||||||
|
updated_at = current_timestamp
|
||||||
|
where id = #{id} and company_id = #{companyId}
|
||||||
|
</update>
|
||||||
|
|
||||||
|
<delete id="deleteAll">
|
||||||
|
delete from sys_user
|
||||||
|
</delete>
|
||||||
|
</mapper>
|
||||||
23
src/main/resources/sql/data.sql
Normal file
23
src/main/resources/sql/data.sql
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
insert into sys_company (id, company_code, company_name, status) values
|
||||||
|
(1, 'PLATFORM', '平台公司', 'ENABLED'),
|
||||||
|
(2, 'ALPHA', '甲公司', 'ENABLED')
|
||||||
|
on conflict do nothing;
|
||||||
|
|
||||||
|
insert into sys_user (id, company_id, phone, username, role, position, real_name, password_hash, must_change_password, status, session_version) values
|
||||||
|
(1, 1, '13900000000', 'platform-admin', 'ENGINEER', 'ADMIN', '平台管理员', '$2a$10$TGPk5rNNhKNJQvTWImw5J.LVzw9HDFWR6hyNJCkLDcp0GU8/vp0aS', false, 'ENABLED', 1),
|
||||||
|
(2, 2, '13800138000', 'alpha-admin', 'EMPLOYEE', 'ADMIN', '甲公司管理员', '$2a$10$/hSD8ch7A9lFWi/DOb8yJOHdlrhV57p95CBv9Uv93Yky7t6c4Rs/S', true, 'ENABLED', 1),
|
||||||
|
(3, 2, '13700000000', 'alpha-annotator', 'EMPLOYEE', 'ANNOTATOR', '甲公司标注员', '$2a$10$bRMZPcIaiB1BUx6HPw6FSODPSuph8kUi8/JZOM6lACwjjhkbBL5mq', false, 'ENABLED', 1),
|
||||||
|
(4, 2, '13600000000', 'alpha-manager', 'MANAGER', 'REVIEWER', '甲公司经理', '$2a$10$bRMZPcIaiB1BUx6HPw6FSODPSuph8kUi8/JZOM6lACwjjhkbBL5mq', false, 'ENABLED', 1),
|
||||||
|
(5, 2, '13500000000', 'alpha-engineer', 'ENGINEER', 'ADMIN', '甲公司工程师', '$2a$10$bRMZPcIaiB1BUx6HPw6FSODPSuph8kUi8/JZOM6lACwjjhkbBL5mq', false, 'ENABLED', 1)
|
||||||
|
on conflict do nothing;
|
||||||
|
|
||||||
|
insert into sys_menu (id, company_id, permission_code, menu_code, menu_name, path, sort_order) values
|
||||||
|
(201, 2, 'USER_MANAGE', 'USER_MANAGE', '员工管理', '/users', 1),
|
||||||
|
(202, 2, 'DATA_RECORD_VIEW', 'DATA_RECORDS', '数据记录', '/data-records', 2)
|
||||||
|
on conflict do nothing;
|
||||||
|
|
||||||
|
insert into biz_data_record (id, company_id, creator_id, creator_role, record_name) values
|
||||||
|
(401, 2, 3, 'EMPLOYEE', '员工创建的数据'),
|
||||||
|
(402, 2, 4, 'MANAGER', '经理创建的数据'),
|
||||||
|
(403, 2, 5, 'ENGINEER', '工程师创建的数据')
|
||||||
|
on conflict do nothing;
|
||||||
47
src/main/resources/sql/schema.sql
Normal file
47
src/main/resources/sql/schema.sql
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
create table if not exists sys_company (
|
||||||
|
id bigint primary key,
|
||||||
|
company_code varchar(64) not null unique,
|
||||||
|
company_name varchar(128) not null,
|
||||||
|
status varchar(32) not null,
|
||||||
|
created_at timestamp default current_timestamp,
|
||||||
|
updated_at timestamp default current_timestamp
|
||||||
|
);
|
||||||
|
|
||||||
|
create table if not exists sys_user (
|
||||||
|
id bigint primary key,
|
||||||
|
company_id bigint not null,
|
||||||
|
phone varchar(32) not null,
|
||||||
|
username varchar(64),
|
||||||
|
role varchar(32) not null,
|
||||||
|
position varchar(32) not null,
|
||||||
|
real_name varchar(64) not null,
|
||||||
|
password_hash varchar(255) not null,
|
||||||
|
must_change_password boolean not null default true,
|
||||||
|
status varchar(32) not null,
|
||||||
|
session_version integer not null default 1,
|
||||||
|
created_at timestamp default current_timestamp,
|
||||||
|
updated_at timestamp default current_timestamp,
|
||||||
|
constraint uq_sys_user_company_phone unique (company_id, phone)
|
||||||
|
);
|
||||||
|
|
||||||
|
create table if not exists sys_menu (
|
||||||
|
id bigint primary key,
|
||||||
|
company_id bigint not null,
|
||||||
|
permission_code varchar(64) not null,
|
||||||
|
menu_code varchar(64) not null,
|
||||||
|
menu_name varchar(128) not null,
|
||||||
|
path varchar(255) not null,
|
||||||
|
sort_order integer not null default 0,
|
||||||
|
created_at timestamp default current_timestamp,
|
||||||
|
updated_at timestamp default current_timestamp
|
||||||
|
);
|
||||||
|
|
||||||
|
create table if not exists biz_data_record (
|
||||||
|
id bigint primary key,
|
||||||
|
company_id bigint not null,
|
||||||
|
creator_id bigint not null,
|
||||||
|
creator_role varchar(32) not null,
|
||||||
|
record_name varchar(255) not null,
|
||||||
|
created_at timestamp default current_timestamp,
|
||||||
|
updated_at timestamp default current_timestamp
|
||||||
|
);
|
||||||
Reference in New Issue
Block a user