From 04d650a366d372cdf14e3c1200a5377441a453e7 Mon Sep 17 00:00:00 2001 From: luoweijian <1329394916@qq.com> Date: Wed, 28 Jan 2026 16:23:38 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B5=84=E6=96=99=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 53 +++++++- .../com/project/base/domain/dto/BaseDTO.java | 1 - .../base/domain/entity/BaseEntity.java | 5 +- .../auth/DingTalkAuthenticationProvider.java | 68 +++++++++++ .../auth/DingTalkAuthenticationToken.java | 25 ++++ .../ding/auth/JwtAuthenticationFilter.java | 61 ++++++++++ .../project/ding/config/DingProperties.java | 14 +++ .../config/{DingConfig.java => DtConfig.java} | 23 ++-- .../project/ding/config/SecurityConfig.java | 46 +++++++ .../ding/controller/DepartmentController.java | 38 ------ .../ding/controller/LoginController.java | 27 ++++ .../ding/controller/SyncController.java | 2 +- .../ding/controller/UserController.java | 2 +- .../ding/domain/dto/DepartmentDTO.java | 1 + .../com/project/ding/domain/dto/LoginDTO.java | 14 +++ .../com/project/ding/domain/dto/UserDTO.java | 9 +- .../domain/entity/AdminWhiteListEntity.java | 40 ++++++ .../ding/domain/entity/DepartmentEntity.java | 7 +- .../ding/domain/entity/SyncLogEntity.java | 6 + .../ding/domain/entity/UserEntity.java | 5 +- .../domain/service/AuthDomainService.java | 10 ++ .../service/impl/AuthDomainServiceImpl.java | 60 +++++++++ .../ding/mapper/AdminWhiteListMapper.java | 10 ++ .../java/com/project/ding/utils/DingUtil.java | 21 +++- .../java/com/project/ding/utils/JwtUtils.java | 29 +++++ .../InformationApplicationService.java | 14 +++ .../InformationApplicationServiceImpl.java | 28 +++++ .../controller/BusinessGroupController.java | 2 +- .../controller/InformationController.java | 27 +++- .../controller/ProductLineController.java | 2 +- .../domain/dto/BusinessGroupDTO.java | 2 + .../domain/dto/InformationDTO.java | 4 +- .../domain/dto/ProductLineDTO.java | 2 + .../domain/entity/BusinessGroupEntity.java | 10 +- .../domain/entity/InformationEntity.java | 8 +- .../domain/entity/ProductLineEntity.java | 11 +- .../enums/InformationParseStatusEnum.java | 15 +++ .../domain/param/BatchCheckParam.java | 11 ++ .../domain/param/CheckDuplicatesParam.java | 32 +++++ .../domain/param/FileCheckItem.java | 9 ++ .../UploadInformationDomainService.java | 11 +- .../domain/service/UploadService.java | 28 ----- .../UploadInformationDomainServiceImpl.java | 115 ++++++++++++++---- .../service/impl/UploadServiceImpl.java | 43 ------- .../project/information/utils/MinIoUtils.java | 86 ++++++++++++- src/main/resources/application.yml | 17 ++- 46 files changed, 864 insertions(+), 190 deletions(-) create mode 100644 src/main/java/com/project/ding/auth/DingTalkAuthenticationProvider.java create mode 100644 src/main/java/com/project/ding/auth/DingTalkAuthenticationToken.java create mode 100644 src/main/java/com/project/ding/auth/JwtAuthenticationFilter.java create mode 100644 src/main/java/com/project/ding/config/DingProperties.java rename src/main/java/com/project/ding/config/{DingConfig.java => DtConfig.java} (55%) create mode 100644 src/main/java/com/project/ding/config/SecurityConfig.java delete mode 100644 src/main/java/com/project/ding/controller/DepartmentController.java create mode 100644 src/main/java/com/project/ding/controller/LoginController.java create mode 100644 src/main/java/com/project/ding/domain/dto/LoginDTO.java create mode 100644 src/main/java/com/project/ding/domain/entity/AdminWhiteListEntity.java create mode 100644 src/main/java/com/project/ding/domain/service/AuthDomainService.java create mode 100644 src/main/java/com/project/ding/domain/service/impl/AuthDomainServiceImpl.java create mode 100644 src/main/java/com/project/ding/mapper/AdminWhiteListMapper.java create mode 100644 src/main/java/com/project/ding/utils/JwtUtils.java create mode 100644 src/main/java/com/project/information/application/InformationApplicationService.java create mode 100644 src/main/java/com/project/information/application/impl/InformationApplicationServiceImpl.java create mode 100644 src/main/java/com/project/information/domain/enums/InformationParseStatusEnum.java create mode 100644 src/main/java/com/project/information/domain/param/BatchCheckParam.java create mode 100644 src/main/java/com/project/information/domain/param/CheckDuplicatesParam.java create mode 100644 src/main/java/com/project/information/domain/param/FileCheckItem.java delete mode 100644 src/main/java/com/project/information/domain/service/UploadService.java delete mode 100644 src/main/java/com/project/information/domain/service/impl/UploadServiceImpl.java diff --git a/pom.xml b/pom.xml index 52e743f..32b9b26 100644 --- a/pom.xml +++ b/pom.xml @@ -32,6 +32,9 @@ 8.0.3 4.8.1 + 4.3.0 + 0.11.5 + 3.0.13 1.18.30 @@ -120,10 +123,34 @@ hutool-all 5.8.3 + + + org.springframework.boot + spring-boot-starter-security + + + + + io.jsonwebtoken + jjwt-api + ${jjwt.version} + + + io.jsonwebtoken + jjwt-impl + ${jjwt.version} + runtime + + + io.jsonwebtoken + jjwt-jackson + ${jjwt.version} + runtime + com.baomidou dynamic-datasource-spring-boot3-starter - 4.2.0 + ${dynamic-datasource.version} org.mybatis @@ -177,4 +204,28 @@ + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + repackage + + + + + + + org.projectlombok + lombok + + + + + + diff --git a/src/main/java/com/project/base/domain/dto/BaseDTO.java b/src/main/java/com/project/base/domain/dto/BaseDTO.java index 044db63..4289518 100644 --- a/src/main/java/com/project/base/domain/dto/BaseDTO.java +++ b/src/main/java/com/project/base/domain/dto/BaseDTO.java @@ -11,7 +11,6 @@ import java.util.function.Supplier; @Data public class BaseDTO { - private Long id; private Long creatorId; diff --git a/src/main/java/com/project/base/domain/entity/BaseEntity.java b/src/main/java/com/project/base/domain/entity/BaseEntity.java index 895bbb4..ee6320c 100644 --- a/src/main/java/com/project/base/domain/entity/BaseEntity.java +++ b/src/main/java/com/project/base/domain/entity/BaseEntity.java @@ -17,10 +17,7 @@ import java.util.function.Supplier; @Data public class BaseEntity implements Serializable { - @Id - @TableId(value = "id" , type = IdType.ASSIGN_ID) - @Column(name = "id" , columnDefinition="bigint(20)") - private Long id; + @TableField(value = "creator_id" , fill = FieldFill.INSERT) @Column(name = "creator_id" , columnDefinition="bigint(20) comment '创建用户id'") diff --git a/src/main/java/com/project/ding/auth/DingTalkAuthenticationProvider.java b/src/main/java/com/project/ding/auth/DingTalkAuthenticationProvider.java new file mode 100644 index 0000000..456aa8a --- /dev/null +++ b/src/main/java/com/project/ding/auth/DingTalkAuthenticationProvider.java @@ -0,0 +1,68 @@ +package com.project.ding.auth; + +import cn.hutool.core.util.StrUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.project.ding.domain.dto.UserDTO; +import com.project.ding.domain.entity.AdminWhiteListEntity; +import com.project.ding.domain.entity.UserEntity; +import com.project.ding.mapper.AdminWhiteListMapper; +import com.project.ding.mapper.UserMapper; +import com.project.ding.utils.DingUserSyncUtil; +import com.project.ding.utils.DingUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.stereotype.Component; + +import java.util.*; + +@Component +public class DingTalkAuthenticationProvider implements AuthenticationProvider { + @Autowired + private DingUtil dingUtil; // 封装获取 userId 接口 + @Autowired + private UserMapper userMapper; + @Autowired + private AdminWhiteListMapper whitelistMapper; + + @Autowired + private DingUserSyncUtil dingUserSyncUtil; + + @Override + public Authentication authenticate(Authentication authentication) throws AuthenticationException { + String authCode = (String) authentication.getPrincipal(); + // 1. 换取钉钉 UserId + String dingId = dingUtil.getUserIdByCode(authCode); + if (StrUtil.isBlank(dingId)) { + throw new BadCredentialsException("钉钉授权失败"); + } + + UserEntity user = userMapper.selectById(Long.valueOf(dingId)); + if (Objects.isNull(user)) { + // 直接插入这个用户并且触发一次异步的全量用户同步 + UserDTO userDTO = dingUtil.getUserById(dingId); + if (Objects.isNull(userDTO)) { + throw new BadCredentialsException("获取用户信息异常,请联系管理员"); + } + userMapper.batchUpsert(Collections.singletonList(userDTO.toEntity(UserEntity::new))); + user = userMapper.selectById(Long.valueOf(dingId)); + dingUserSyncUtil.triggerSync(false); + } + + // 3. 分配角色:默认考生,白名单为管理员 + List authorities = new ArrayList<>(); + authorities.add(new SimpleGrantedAuthority("ROLE_CANDIDATE")); + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(AdminWhiteListEntity::getUserId , user.getId()); + AdminWhiteListEntity existOne = whitelistMapper.selectOne(queryWrapper); + if (Objects.nonNull(existOne)) { + authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN")); + } + return new DingTalkAuthenticationToken(user, authorities); + } + + @Override public boolean supports(Class a) { return DingTalkAuthenticationToken.class.isAssignableFrom(a); } +} \ No newline at end of file diff --git a/src/main/java/com/project/ding/auth/DingTalkAuthenticationToken.java b/src/main/java/com/project/ding/auth/DingTalkAuthenticationToken.java new file mode 100644 index 0000000..b88ec97 --- /dev/null +++ b/src/main/java/com/project/ding/auth/DingTalkAuthenticationToken.java @@ -0,0 +1,25 @@ +package com.project.ding.auth; + +import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; + +import java.util.Collection; + +public class DingTalkAuthenticationToken extends AbstractAuthenticationToken { + private final Object principal; // 登录后存 UserEntity,登录前存 authCode + + public DingTalkAuthenticationToken(Object principal) { + super(null); + this.principal = principal; + setAuthenticated(false); + } + + public DingTalkAuthenticationToken(Object principal, Collection authorities) { + super(authorities); + this.principal = principal; + setAuthenticated(true); + } + + @Override public Object getCredentials() { return null; } + @Override public Object getPrincipal() { return principal; } +} \ No newline at end of file diff --git a/src/main/java/com/project/ding/auth/JwtAuthenticationFilter.java b/src/main/java/com/project/ding/auth/JwtAuthenticationFilter.java new file mode 100644 index 0000000..d32b78b --- /dev/null +++ b/src/main/java/com/project/ding/auth/JwtAuthenticationFilter.java @@ -0,0 +1,61 @@ +package com.project.ding.auth; + +import com.project.ding.utils.JwtUtils; +import io.jsonwebtoken.Claims; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.authority.SimpleGrantedAuthority; // 确保有这行导入 +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; +import java.util.List; + +@Component +public class JwtAuthenticationFilter extends OncePerRequestFilter { + + @Autowired + private JwtUtils jwtUtils; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + + String header = request.getHeader("Authorization"); + + if (StringUtils.hasText(header) && header.startsWith("Bearer ")) { + String token = header.substring(7); + try { + Claims claims = jwtUtils.parseToken(token); + String userId = claims.getSubject(); + + // 1. 获取原始的 roles 列表 + Object rolesObj = claims.get("roles"); + + if (userId != null && rolesObj instanceof List) { + // 2. 显式转换并映射,增加 Object::toString 保证类型安全 + List authorities = ((List) rolesObj).stream() + .map(Object::toString) // 强制转为 String,解决构造器匹配问题 + .map(SimpleGrantedAuthority::new) // 现在这里不会报红了 + .toList(); + + // 3. 构建并设置认证信息 + UsernamePasswordAuthenticationToken authentication = + new UsernamePasswordAuthenticationToken(userId, null, authorities); + SecurityContextHolder.getContext().setAuthentication(authentication); + } + } catch (Exception e) { + // Token解析失败不处理,后续 Security 拦截器会返回 403 + logger.error("JWT authentication failed: " + e.getMessage()); + } + } + + filterChain.doFilter(request, response); + } +} \ No newline at end of file diff --git a/src/main/java/com/project/ding/config/DingProperties.java b/src/main/java/com/project/ding/config/DingProperties.java new file mode 100644 index 0000000..2b14ce2 --- /dev/null +++ b/src/main/java/com/project/ding/config/DingProperties.java @@ -0,0 +1,14 @@ +package com.project.ding.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Data +@Component +@ConfigurationProperties(prefix = "ding") +public class DingProperties { + private String appKey; + private String appSecret; + private Long agentId; +} \ No newline at end of file diff --git a/src/main/java/com/project/ding/config/DingConfig.java b/src/main/java/com/project/ding/config/DtConfig.java similarity index 55% rename from src/main/java/com/project/ding/config/DingConfig.java rename to src/main/java/com/project/ding/config/DtConfig.java index a5bd050..bddbbcc 100644 --- a/src/main/java/com/project/ding/config/DingConfig.java +++ b/src/main/java/com/project/ding/config/DtConfig.java @@ -3,21 +3,30 @@ package com.project.ding.config; import com.github.tingyugetc520.ali.dingtalk.api.DtService; import com.github.tingyugetc520.ali.dingtalk.api.impl.DtServiceImpl; import com.github.tingyugetc520.ali.dingtalk.config.impl.DtDefaultConfigImpl; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration -public class DingConfig { +public class DtConfig { + + @Autowired + private DingProperties dingTalkProperties; + @Bean public DtService dtService() { DtDefaultConfigImpl config = new DtDefaultConfigImpl(); - config.setAppKey("ding4eootvq4jzas96lr"); - config.setAppSecret("UI6XcD8GFPE_W_yo2eQkMuoSsTEf1whpZYEXrsDA7CV7bkJp40B3VNcETWS1aGgg"); - // 如果是企业内部应用,通常还需要 AgentId , -// config.setAgentId(4145005949L); + + // 直接从属性对象中取值 + config.setAppKey(dingTalkProperties.getAppKey()); + config.setAppSecret(dingTalkProperties.getAppSecret()); + + if (dingTalkProperties.getAgentId() != null) { + config.setAgentId(dingTalkProperties.getAgentId()); + } DtService dtService = new DtServiceImpl(); dtService.setDtConfigStorage(config); - return dtService;// + return dtService; } -} +} \ No newline at end of file diff --git a/src/main/java/com/project/ding/config/SecurityConfig.java b/src/main/java/com/project/ding/config/SecurityConfig.java new file mode 100644 index 0000000..6f1c36a --- /dev/null +++ b/src/main/java/com/project/ding/config/SecurityConfig.java @@ -0,0 +1,46 @@ +package com.project.ding.config; + +import com.project.ding.auth.DingTalkAuthenticationProvider; +import com.project.ding.auth.JwtAuthenticationFilter; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +@Configuration +@EnableWebSecurity +@EnableMethodSecurity +public class SecurityConfig { + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http, + DingTalkAuthenticationProvider provider, + JwtAuthenticationFilter jwtFilter) throws Exception { + return http + .csrf(AbstractHttpConfigurer::disable) + .sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .authorizeHttpRequests(auth -> auth + // 登录放行 + .requestMatchers("/api/login/**").permitAll() + // 管理端锁定 + .requestMatchers("/api/admin/**").hasRole("ADMIN") + // 其余(考生端)需登录 + .anyRequest().authenticated() + ) + .authenticationProvider(provider) + .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class) + .build(); + } + + @Bean + public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception { + return config.getAuthenticationManager(); + } +} \ No newline at end of file diff --git a/src/main/java/com/project/ding/controller/DepartmentController.java b/src/main/java/com/project/ding/controller/DepartmentController.java deleted file mode 100644 index 211e211..0000000 --- a/src/main/java/com/project/ding/controller/DepartmentController.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.project.ding.controller; - - -import com.github.tingyugetc520.ali.dingtalk.api.DtService; -import com.project.ding.domain.dto.DepartmentDTO; -import com.project.ding.domain.dto.DingUserDTO; -import com.project.ding.utils.DingUtil; -import jakarta.servlet.http.HttpServletResponse; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; - -import java.util.ArrayList; -import java.util.List; - - -@RestController -@Slf4j -public class DepartmentController { - @Autowired - private DingUtil dingUtil; - @Autowired - private DtService dtService; - @GetMapping("/test") - public void test(HttpServletResponse response) throws Exception { - List list = dingUtil.getAllDepartment(); - - List userList = new ArrayList<>(); - for (int i = 0; i < list.size(); i++) { - List userInDepartment = dingUtil.getUserIdInDepartment(list.get(i).getId()); - userList.addAll(userInDepartment); - } - for (int i = 0; i < 10; i++) { - System.out.println(111); - } - } -} diff --git a/src/main/java/com/project/ding/controller/LoginController.java b/src/main/java/com/project/ding/controller/LoginController.java new file mode 100644 index 0000000..746bd39 --- /dev/null +++ b/src/main/java/com/project/ding/controller/LoginController.java @@ -0,0 +1,27 @@ +package com.project.ding.controller; + +import com.project.base.domain.result.Result; +import com.project.ding.domain.dto.LoginDTO; +import com.project.ding.domain.service.AuthDomainService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/login") +public class LoginController { + + @Autowired + private AuthDomainService authDomainService; + + /** + * 钉钉免登 + */ + @PostMapping("/ding") + public Result login(@RequestParam("authCode") String authCode) { + // 逻辑全部下沉到 Service + return Result.success(authDomainService.loginByDing(authCode)); + } +} \ No newline at end of file diff --git a/src/main/java/com/project/ding/controller/SyncController.java b/src/main/java/com/project/ding/controller/SyncController.java index 7c7ea5c..44fd1f1 100644 --- a/src/main/java/com/project/ding/controller/SyncController.java +++ b/src/main/java/com/project/ding/controller/SyncController.java @@ -9,7 +9,7 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController -@RequestMapping("/ding") +@RequestMapping("/api/ding") public class SyncController { @Autowired private DingUserSyncUtil dingUserSyncUtil; diff --git a/src/main/java/com/project/ding/controller/UserController.java b/src/main/java/com/project/ding/controller/UserController.java index 769fd55..b62571c 100644 --- a/src/main/java/com/project/ding/controller/UserController.java +++ b/src/main/java/com/project/ding/controller/UserController.java @@ -14,7 +14,7 @@ import org.springframework.web.bind.annotation.RestController; @RestController @Slf4j -@RequestMapping("/dingUser") +@RequestMapping("/api/admin/dingUser") public class UserController { @Autowired private UserApplicationService userApplicationService; diff --git a/src/main/java/com/project/ding/domain/dto/DepartmentDTO.java b/src/main/java/com/project/ding/domain/dto/DepartmentDTO.java index ef09bc9..bc33955 100644 --- a/src/main/java/com/project/ding/domain/dto/DepartmentDTO.java +++ b/src/main/java/com/project/ding/domain/dto/DepartmentDTO.java @@ -8,6 +8,7 @@ import java.util.List; @Data public class DepartmentDTO extends BaseDTO { + private Long id; private String name; private Long parentId; diff --git a/src/main/java/com/project/ding/domain/dto/LoginDTO.java b/src/main/java/com/project/ding/domain/dto/LoginDTO.java new file mode 100644 index 0000000..937959b --- /dev/null +++ b/src/main/java/com/project/ding/domain/dto/LoginDTO.java @@ -0,0 +1,14 @@ +package com.project.ding.domain.dto; + +import lombok.Builder; +import lombok.Data; + +import java.util.List; + +@Data +@Builder +public class LoginDTO { + private String token; // JWT 令牌 + private String name; // 用户姓名 + private List roles; // 用户角色列表 +} \ No newline at end of file diff --git a/src/main/java/com/project/ding/domain/dto/UserDTO.java b/src/main/java/com/project/ding/domain/dto/UserDTO.java index f49fb48..593e9df 100644 --- a/src/main/java/com/project/ding/domain/dto/UserDTO.java +++ b/src/main/java/com/project/ding/domain/dto/UserDTO.java @@ -4,15 +4,17 @@ import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.util.DesensitizedUtil; import com.fasterxml.jackson.annotation.JsonFormat; import com.project.base.domain.dto.BaseDTO; -import io.vavr.control.Try; import lombok.Data; import java.util.Date; import java.util.List; +import java.util.Objects; import java.util.stream.Collectors; @Data public class UserDTO extends BaseDTO { + private String id; + private String unionId; @@ -57,11 +59,14 @@ public class UserDTO extends BaseDTO { public static UserDTO fromDingUserDTO(DingUserDTO dto) { + if (Objects.isNull(dto)) { + return null; + } UserDTO userDTO = new UserDTO(); BeanUtil.copyProperties(dto , userDTO); - userDTO.setId(Try.of(() -> Long.valueOf(dto.getUserId())).getOrElse(-1L)); userDTO.setDeptIdStr(dto.getDeptIdList().stream().map(Object::toString) .collect(Collectors.joining(","))); + userDTO.setId(dto.getUserId()); return userDTO; } diff --git a/src/main/java/com/project/ding/domain/entity/AdminWhiteListEntity.java b/src/main/java/com/project/ding/domain/entity/AdminWhiteListEntity.java new file mode 100644 index 0000000..83c7645 --- /dev/null +++ b/src/main/java/com/project/ding/domain/entity/AdminWhiteListEntity.java @@ -0,0 +1,40 @@ +package com.project.ding.domain.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.project.base.domain.entity.BaseEntity; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.hibernate.annotations.Comment; + +/** + * 管理员白名单表 + */ +@Data +@Entity +@Table(name = "evaluator_admin_white_list") +@TableName("evaluator_admin_white_list") +@EqualsAndHashCode(callSuper = true) +public class AdminWhiteListEntity extends BaseEntity { + + @TableId(value = "id" , type = IdType.ASSIGN_ID) + @Id + private Long id; + + @Column(name = "user_id", nullable = false) + @Comment("用户ID (关联evaluator_user的id)") + private Long userId; + + @Column(name = "user_name", length = 100) + @Comment("用户姓名 (冗余字段,方便管理后台直接查看)") + private String userName; + + @Column(name = "admin_type") + @Comment("管理员类型") + private Integer adminType = 0; +} \ No newline at end of file diff --git a/src/main/java/com/project/ding/domain/entity/DepartmentEntity.java b/src/main/java/com/project/ding/domain/entity/DepartmentEntity.java index 4af423a..121a561 100644 --- a/src/main/java/com/project/ding/domain/entity/DepartmentEntity.java +++ b/src/main/java/com/project/ding/domain/entity/DepartmentEntity.java @@ -6,10 +6,7 @@ import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; import com.project.base.domain.entity.BaseEntity; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.Index; -import jakarta.persistence.Table; +import jakarta.persistence.*; import lombok.Data; import lombok.EqualsAndHashCode; import org.hibernate.annotations.JdbcTypeCode; @@ -24,7 +21,9 @@ import java.util.List; @TableName(value = "evaluator_department" , autoResultMap = true) @EqualsAndHashCode(callSuper = true) public class DepartmentEntity extends BaseEntity { + @TableId(type = IdType.INPUT) + @Id private Long id; @Column(name = "name" , columnDefinition="varchar(255) comment '部门名称'") diff --git a/src/main/java/com/project/ding/domain/entity/SyncLogEntity.java b/src/main/java/com/project/ding/domain/entity/SyncLogEntity.java index cb4664e..bdf4549 100644 --- a/src/main/java/com/project/ding/domain/entity/SyncLogEntity.java +++ b/src/main/java/com/project/ding/domain/entity/SyncLogEntity.java @@ -1,10 +1,13 @@ package com.project.ding.domain.entity; +import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import com.project.base.domain.entity.BaseEntity; import jakarta.persistence.Column; import jakarta.persistence.Entity; +import jakarta.persistence.Id; import jakarta.persistence.Table; import lombok.Data; import lombok.EqualsAndHashCode; @@ -19,6 +22,9 @@ import java.util.Date; @TableName("evaluator_sync_log") @EqualsAndHashCode(callSuper = true) public class SyncLogEntity extends BaseEntity { + @TableId(value = "id" , type = IdType.ASSIGN_ID) + @Id + private Long id; @Comment("同步开始时间") @Column(name = "start_time") diff --git a/src/main/java/com/project/ding/domain/entity/UserEntity.java b/src/main/java/com/project/ding/domain/entity/UserEntity.java index 8f838d3..1cbd400 100644 --- a/src/main/java/com/project/ding/domain/entity/UserEntity.java +++ b/src/main/java/com/project/ding/domain/entity/UserEntity.java @@ -8,6 +8,7 @@ import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; import com.project.base.domain.entity.BaseEntity; import jakarta.persistence.Column; import jakarta.persistence.Entity; +import jakarta.persistence.Id; import jakarta.persistence.Table; import lombok.Data; import lombok.EqualsAndHashCode; @@ -26,7 +27,9 @@ import java.util.List; public class UserEntity extends BaseEntity { @TableId(type = IdType.INPUT) - private Long id; + @Column(length = 500) + @Id + private String id; @Column(name = "union_id" , columnDefinition="varchar(255) comment '企业架构内唯一标识'") @TableField("union_id") diff --git a/src/main/java/com/project/ding/domain/service/AuthDomainService.java b/src/main/java/com/project/ding/domain/service/AuthDomainService.java new file mode 100644 index 0000000..9b6fe60 --- /dev/null +++ b/src/main/java/com/project/ding/domain/service/AuthDomainService.java @@ -0,0 +1,10 @@ +package com.project.ding.domain.service; + +import com.project.ding.domain.dto.LoginDTO; + +public interface AuthDomainService { + /** + * 钉钉免登逻辑 + */ + LoginDTO loginByDing(String authCode); +} \ No newline at end of file diff --git a/src/main/java/com/project/ding/domain/service/impl/AuthDomainServiceImpl.java b/src/main/java/com/project/ding/domain/service/impl/AuthDomainServiceImpl.java new file mode 100644 index 0000000..11b5634 --- /dev/null +++ b/src/main/java/com/project/ding/domain/service/impl/AuthDomainServiceImpl.java @@ -0,0 +1,60 @@ +package com.project.ding.domain.service.impl; + +import com.project.base.domain.exception.BusinessErrorException; +import com.project.ding.auth.DingTalkAuthenticationToken; +import com.project.ding.domain.dto.LoginDTO; +import com.project.ding.domain.entity.UserEntity; +import com.project.ding.domain.service.AuthDomainService; +import com.project.ding.utils.JwtUtils; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.stereotype.Service; +import org.springframework.security.core.GrantedAuthority; + +import java.util.List; + +@Service +@Slf4j +public class AuthDomainServiceImpl implements AuthDomainService { + + @Autowired + private AuthenticationManager authenticationManager; + + @Autowired + private JwtUtils jwtUtils; + + @Override + public LoginDTO loginByDing(String authCode) { + // 1. 构建未认证的内部 Token + DingTalkAuthenticationToken authToken = new DingTalkAuthenticationToken(authCode); + + // 2. 委托 AuthenticationManager 进行认证 + // 这一步会进入你之前写的 DingTalkAuthenticationProvider + Authentication authentication; + try { + authentication = authenticationManager.authenticate(authToken); + } catch (AuthenticationException e) { + log.warn("钉钉认证失败: {}", e.getMessage()); + throw new BusinessErrorException("认证失败:" + e.getMessage()); // 抛出自定义业务异常 + } + + // 3. 认证成功后,从安全上下文中提取用户信息和权限 + UserEntity user = (UserEntity) authentication.getPrincipal(); + List roles = authentication.getAuthorities().stream() + .map(GrantedAuthority::getAuthority) + .toList(); + + // 4. 生成 JWT + String token = jwtUtils.createToken(user.getId(), roles); + + // 5. 组装并返回结果 + return LoginDTO.builder() + .token(token) + .name(user.getName()) + .roles(roles) + .build(); + } +} \ No newline at end of file diff --git a/src/main/java/com/project/ding/mapper/AdminWhiteListMapper.java b/src/main/java/com/project/ding/mapper/AdminWhiteListMapper.java new file mode 100644 index 0000000..2a61ab0 --- /dev/null +++ b/src/main/java/com/project/ding/mapper/AdminWhiteListMapper.java @@ -0,0 +1,10 @@ +package com.project.ding.mapper; + + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.project.ding.domain.entity.AdminWhiteListEntity; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface AdminWhiteListMapper extends BaseMapper { +} diff --git a/src/main/java/com/project/ding/utils/DingUtil.java b/src/main/java/com/project/ding/utils/DingUtil.java index 1fbb217..5d174e2 100644 --- a/src/main/java/com/project/ding/utils/DingUtil.java +++ b/src/main/java/com/project/ding/utils/DingUtil.java @@ -16,9 +16,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.ArrayList; -import java.util.HashSet; import java.util.List; -import java.util.Set; import java.util.stream.Collectors; @Component @@ -39,6 +37,12 @@ public class DingUtil { return res; } + + public String getUserIdByCode(String code) { + return Try.of(() -> dtService.getOauth2Service().getUserInfo(code).getUserId()) + .getOrElse(""); + } + public List getAllDingUserDTO() throws Exception { List list = getAllDepartment(); @@ -81,6 +85,19 @@ public class DingUtil { return u1; } + + public UserDTO getUserById(String id) { + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("userid" , id); + String url = dtService.getDtConfigStorage().getApiUrl("/topapi/v2/user/get"); + String responseContent = Try.of(() -> dtService.post(url, jsonObject)).getOrElse(""); + DingUserDTO dingUserDTO = Try.of(() -> new ObjectMapper().convertValue( + JsonPath.read(responseContent, "$.result"), + new TypeReference() { + })).getOrNull(); + + return UserDTO.fromDingUserDTO(dingUserDTO); + } public List getUserIdInDepartment(Long id) throws Exception { JsonObject jsonObject = new JsonObject(); diff --git a/src/main/java/com/project/ding/utils/JwtUtils.java b/src/main/java/com/project/ding/utils/JwtUtils.java new file mode 100644 index 0000000..d1757e3 --- /dev/null +++ b/src/main/java/com/project/ding/utils/JwtUtils.java @@ -0,0 +1,29 @@ +package com.project.ding.utils; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.security.Keys; +import org.springframework.stereotype.Component; + +import java.security.Key; +import java.util.Date; +import java.util.List; + +@Component +public class JwtUtils { + private static final Key KEY = Keys.secretKeyFor(SignatureAlgorithm.HS256); + private static final long EXPIRE = 86400000; // 24小时 + + public String createToken(String userId, List roles) { + return Jwts.builder() + .setSubject(userId) + .claim("roles", roles) + .setExpiration(new Date(System.currentTimeMillis() + EXPIRE)) + .signWith(KEY).compact(); + } + + public Claims parseToken(String token) { + return Jwts.parserBuilder().setSigningKey(KEY).build().parseClaimsJws(token).getBody(); + } +} \ No newline at end of file diff --git a/src/main/java/com/project/information/application/InformationApplicationService.java b/src/main/java/com/project/information/application/InformationApplicationService.java new file mode 100644 index 0000000..fd0f36e --- /dev/null +++ b/src/main/java/com/project/information/application/InformationApplicationService.java @@ -0,0 +1,14 @@ +package com.project.information.application; + +import com.project.base.domain.result.Result; +import com.project.information.domain.param.FileCheckItem; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +public interface InformationApplicationService { + Result checkDuplicates(Long subLineId, List files) throws Exception; + + + Result batchUploadAndOverwrite(MultipartFile[] files, Long subLineId) throws Exception; +} diff --git a/src/main/java/com/project/information/application/impl/InformationApplicationServiceImpl.java b/src/main/java/com/project/information/application/impl/InformationApplicationServiceImpl.java new file mode 100644 index 0000000..ab6d93c --- /dev/null +++ b/src/main/java/com/project/information/application/impl/InformationApplicationServiceImpl.java @@ -0,0 +1,28 @@ +package com.project.information.application.impl; + +import com.project.base.domain.result.Result; +import com.project.information.application.InformationApplicationService; +import com.project.information.domain.param.FileCheckItem; +import com.project.information.domain.service.UploadInformationDomainService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +@Service +public class InformationApplicationServiceImpl implements InformationApplicationService { + + @Autowired + private UploadInformationDomainService uploadInformationDomainService; + + @Override + public Result checkDuplicates(Long subLineId, List files) throws Exception { + return uploadInformationDomainService.checkDuplicates(subLineId , files); + } + + @Override + public Result batchUploadAndOverwrite(MultipartFile[] files, Long subLineId) throws Exception { + return uploadInformationDomainService.batchUploadAndOverwrite(files , subLineId); + } +} diff --git a/src/main/java/com/project/information/controller/BusinessGroupController.java b/src/main/java/com/project/information/controller/BusinessGroupController.java index 372bf8b..eda430b 100644 --- a/src/main/java/com/project/information/controller/BusinessGroupController.java +++ b/src/main/java/com/project/information/controller/BusinessGroupController.java @@ -16,7 +16,7 @@ import java.util.List; @RestController @Slf4j -@RequestMapping("/businessGroup") +@RequestMapping("/api/admin/businessGroup") public class BusinessGroupController { @Autowired private BusinessGroupApplicationService businessGroupApplicationService; diff --git a/src/main/java/com/project/information/controller/InformationController.java b/src/main/java/com/project/information/controller/InformationController.java index 72b9066..4d84e60 100644 --- a/src/main/java/com/project/information/controller/InformationController.java +++ b/src/main/java/com/project/information/controller/InformationController.java @@ -1,13 +1,34 @@ package com.project.information.controller; +import com.project.base.domain.result.Result; +import com.project.information.application.InformationApplicationService; +import com.project.information.application.ProductLineApplicationService; +import com.project.information.domain.dto.ProductLineDTO; +import com.project.information.domain.param.CheckDuplicatesParam; +import com.project.information.domain.param.FileCheckItem; +import com.project.information.domain.param.ProductLineParam; import lombok.extern.slf4j.Slf4j; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; @RestController @Slf4j -@RequestMapping("/information") +@RequestMapping("/api/admin/information") public class InformationController { + @Autowired + private InformationApplicationService informationApplicationService; + + @PostMapping("/checkDuplicates") + public Result checkDuplicates(CheckDuplicatesParam param) throws Exception { + return informationApplicationService.checkDuplicates(param.getSubLineId(), param.getFileList()); + } + @PostMapping("/batchUploadAndOverwrite") + public Result batchUploadAndOverwrite(MultipartFile[] files, Long subLineId) throws Exception { + return informationApplicationService.batchUploadAndOverwrite(files , subLineId); + } } diff --git a/src/main/java/com/project/information/controller/ProductLineController.java b/src/main/java/com/project/information/controller/ProductLineController.java index 11f1426..5867371 100644 --- a/src/main/java/com/project/information/controller/ProductLineController.java +++ b/src/main/java/com/project/information/controller/ProductLineController.java @@ -17,7 +17,7 @@ import java.util.List; @RestController @Slf4j -@RequestMapping("/productLine") +@RequestMapping("/api/admin/productLine") public class ProductLineController { @Autowired diff --git a/src/main/java/com/project/information/domain/dto/BusinessGroupDTO.java b/src/main/java/com/project/information/domain/dto/BusinessGroupDTO.java index f2bd3fb..722533b 100644 --- a/src/main/java/com/project/information/domain/dto/BusinessGroupDTO.java +++ b/src/main/java/com/project/information/domain/dto/BusinessGroupDTO.java @@ -5,6 +5,8 @@ import lombok.Data; @Data public class BusinessGroupDTO extends BaseDTO { + private Long id; + private String name; private String sort; } diff --git a/src/main/java/com/project/information/domain/dto/InformationDTO.java b/src/main/java/com/project/information/domain/dto/InformationDTO.java index ed98ea0..0c69fe9 100644 --- a/src/main/java/com/project/information/domain/dto/InformationDTO.java +++ b/src/main/java/com/project/information/domain/dto/InformationDTO.java @@ -5,13 +5,13 @@ import lombok.Data; @Data public class InformationDTO extends BaseDTO { + private Long id; + private Long subLineId; private String subLineName; private String name; private String filePath; private Integer parseStatus; - private Integer partCount; - private String uploadId; } diff --git a/src/main/java/com/project/information/domain/dto/ProductLineDTO.java b/src/main/java/com/project/information/domain/dto/ProductLineDTO.java index 7de7023..4524cfc 100644 --- a/src/main/java/com/project/information/domain/dto/ProductLineDTO.java +++ b/src/main/java/com/project/information/domain/dto/ProductLineDTO.java @@ -8,6 +8,8 @@ import java.util.List; @Data public class ProductLineDTO extends BaseDTO { + private Long id; + private String name; private Integer sort = 0; private Boolean leaf; diff --git a/src/main/java/com/project/information/domain/entity/BusinessGroupEntity.java b/src/main/java/com/project/information/domain/entity/BusinessGroupEntity.java index 65b1d3b..b5a9ff9 100644 --- a/src/main/java/com/project/information/domain/entity/BusinessGroupEntity.java +++ b/src/main/java/com/project/information/domain/entity/BusinessGroupEntity.java @@ -1,12 +1,11 @@ package com.project.information.domain.entity; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import com.project.base.domain.entity.BaseEntity; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.Index; -import jakarta.persistence.Table; +import jakarta.persistence.*; import lombok.Data; import lombok.EqualsAndHashCode; import org.hibernate.annotations.Comment; @@ -17,6 +16,9 @@ import org.hibernate.annotations.Comment; @TableName(value = "evaluator_business_group") @EqualsAndHashCode(callSuper = true) public class BusinessGroupEntity extends BaseEntity { + @TableId(value = "id" , type = IdType.ASSIGN_ID) + @Id + private Long id; @Column(name = "name" , columnDefinition="varchar(255) comment '事业部名称'") private String name; diff --git a/src/main/java/com/project/information/domain/entity/InformationEntity.java b/src/main/java/com/project/information/domain/entity/InformationEntity.java index 2b348c5..f95571a 100644 --- a/src/main/java/com/project/information/domain/entity/InformationEntity.java +++ b/src/main/java/com/project/information/domain/entity/InformationEntity.java @@ -1,10 +1,13 @@ package com.project.information.domain.entity; +import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import com.project.base.domain.entity.BaseEntity; import jakarta.persistence.Column; import jakarta.persistence.Entity; +import jakarta.persistence.Id; import jakarta.persistence.Table; import lombok.Data; import lombok.EqualsAndHashCode; @@ -16,6 +19,9 @@ import org.hibernate.annotations.Comment; @TableName(value = "evaluator_information") @EqualsAndHashCode(callSuper = true) public class InformationEntity extends BaseEntity { + @TableId(value = "id" , type = IdType.ASSIGN_ID) + @Id + private Long id; @Column(name = "sub_line_id") @Comment("所属子产品线ID") @TableField("sub_line_id") @@ -39,6 +45,6 @@ public class InformationEntity extends BaseEntity { @Column(name = "parse_status") @TableField("parse_status") - @Comment("排序权重") + @Comment("文件处理状态") private Integer parseStatus; } diff --git a/src/main/java/com/project/information/domain/entity/ProductLineEntity.java b/src/main/java/com/project/information/domain/entity/ProductLineEntity.java index 17e8a3f..e294f2f 100644 --- a/src/main/java/com/project/information/domain/entity/ProductLineEntity.java +++ b/src/main/java/com/project/information/domain/entity/ProductLineEntity.java @@ -1,13 +1,12 @@ package com.project.information.domain.entity; +import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import com.project.base.domain.entity.BaseEntity; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.Index; -import jakarta.persistence.Table; +import jakarta.persistence.*; import lombok.Data; import lombok.EqualsAndHashCode; import org.hibernate.annotations.Comment; @@ -18,6 +17,10 @@ import org.hibernate.annotations.Comment; @TableName(value = "evaluator_product_line") @EqualsAndHashCode(callSuper = true) public class ProductLineEntity extends BaseEntity { + @TableId(value = "id" , type = IdType.ASSIGN_ID) + @Id + private Long id; + @Column(name = "name" , columnDefinition="varchar(550) comment '部门名称'") private String name; diff --git a/src/main/java/com/project/information/domain/enums/InformationParseStatusEnum.java b/src/main/java/com/project/information/domain/enums/InformationParseStatusEnum.java new file mode 100644 index 0000000..75ba8a8 --- /dev/null +++ b/src/main/java/com/project/information/domain/enums/InformationParseStatusEnum.java @@ -0,0 +1,15 @@ +package com.project.information.domain.enums; + +import com.project.base.domain.enums.HasValueEnum; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public enum InformationParseStatusEnum implements HasValueEnum { + NotStart(0) , + InProgress(1) , + Success(2) , + Fail(-1); + @Getter + private final Integer value; +} diff --git a/src/main/java/com/project/information/domain/param/BatchCheckParam.java b/src/main/java/com/project/information/domain/param/BatchCheckParam.java new file mode 100644 index 0000000..7c8eb6a --- /dev/null +++ b/src/main/java/com/project/information/domain/param/BatchCheckParam.java @@ -0,0 +1,11 @@ +package com.project.information.domain.param; + +import lombok.Data; + +import java.util.List; + +@Data +public class BatchCheckParam { + private Long productLineId; + private List files; +} \ No newline at end of file diff --git a/src/main/java/com/project/information/domain/param/CheckDuplicatesParam.java b/src/main/java/com/project/information/domain/param/CheckDuplicatesParam.java new file mode 100644 index 0000000..c7b66d9 --- /dev/null +++ b/src/main/java/com/project/information/domain/param/CheckDuplicatesParam.java @@ -0,0 +1,32 @@ +package com.project.information.domain.param; + + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.project.base.domain.exception.BusinessErrorException; +import lombok.Data; +import org.springframework.util.StringUtils; + +import java.util.Collections; +import java.util.List; + +@Data +public class CheckDuplicatesParam { + private Long subLineId; + private String files; // 先接收为字符串 + + // 手动解析为List的方法 + public List getFileList() { + if (StringUtils.isEmpty(files)) { + return Collections.emptyList(); + } + try { + return new ObjectMapper().readValue( + files, + new TypeReference>() {}); + } catch (JsonProcessingException e) { + // 处理解析异常 + throw new BusinessErrorException("Json解析失败"); + } + }} diff --git a/src/main/java/com/project/information/domain/param/FileCheckItem.java b/src/main/java/com/project/information/domain/param/FileCheckItem.java new file mode 100644 index 0000000..8be7b58 --- /dev/null +++ b/src/main/java/com/project/information/domain/param/FileCheckItem.java @@ -0,0 +1,9 @@ +package com.project.information.domain.param; + +import lombok.Data; + +@Data +public class FileCheckItem { + private String name; + private Long size; // 单位:Byte +} \ No newline at end of file diff --git a/src/main/java/com/project/information/domain/service/UploadInformationDomainService.java b/src/main/java/com/project/information/domain/service/UploadInformationDomainService.java index 4754cb7..ff376f5 100644 --- a/src/main/java/com/project/information/domain/service/UploadInformationDomainService.java +++ b/src/main/java/com/project/information/domain/service/UploadInformationDomainService.java @@ -1,11 +1,14 @@ package com.project.information.domain.service; import com.project.base.domain.result.Result; -import com.project.information.domain.dto.FileDTO; -import com.project.information.domain.dto.InformationDTO; -import com.project.information.domain.dto.InitMultipartDTO; +import com.project.information.domain.param.FileCheckItem; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; public interface UploadInformationDomainService { + Result checkDuplicates(Long subLineId, List files) throws Exception; + - Result initMultiPartUpload(InformationDTO dto) throws Exception; + Result batchUploadAndOverwrite(MultipartFile[] files, Long subLineId) throws Exception; } diff --git a/src/main/java/com/project/information/domain/service/UploadService.java b/src/main/java/com/project/information/domain/service/UploadService.java deleted file mode 100644 index d00e743..0000000 --- a/src/main/java/com/project/information/domain/service/UploadService.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.project.information.domain.service; - -import com.project.information.domain.dto.StreamInfoDTO; - -import java.util.Map; - -public interface UploadService { - - /** - * 分片上传初始化 - * - * @param path 路径 - * @param filename 文件名 - * @param partCount 分片数量 - * @param contentType / - * @return / - */ - Map initMultiPartUpload(String path, String filename, Integer partCount, String contentType); - - /** - * 完成分片上传 - * - * @param objectName 文件名 - * @param uploadId 标识 - * @return / - */ - StreamInfoDTO mergeMultipartUpload(String objectName, String uploadId); -} \ No newline at end of file diff --git a/src/main/java/com/project/information/domain/service/impl/UploadInformationDomainServiceImpl.java b/src/main/java/com/project/information/domain/service/impl/UploadInformationDomainServiceImpl.java index f99172c..50df81d 100644 --- a/src/main/java/com/project/information/domain/service/impl/UploadInformationDomainServiceImpl.java +++ b/src/main/java/com/project/information/domain/service/impl/UploadInformationDomainServiceImpl.java @@ -1,56 +1,117 @@ package com.project.information.domain.service.impl; -import cn.hutool.core.bean.BeanUtil; -import cn.hutool.core.bean.copier.CopyOptions; -import cn.hutool.core.util.RandomUtil; -import cn.hutool.core.util.StrUtil; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.io.file.FileNameUtil; +import cn.hutool.core.util.IdUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; -import com.project.base.domain.exception.MissingParameterException; +import com.project.base.domain.exception.BusinessErrorException; import com.project.base.domain.result.Result; -import com.project.base.domain.result.ResultCodeEnum; -import com.project.information.domain.dto.FileDTO; import com.project.information.domain.dto.InformationDTO; -import com.project.information.domain.dto.InitMultipartDTO; import com.project.information.domain.entity.InformationEntity; +import com.project.information.domain.entity.ProductLineEntity; +import com.project.information.domain.enums.InformationParseStatusEnum; +import com.project.information.domain.param.FileCheckItem; import com.project.information.domain.service.InformationBaseService; +import com.project.information.domain.service.ProductLineBaseService; import com.project.information.domain.service.UploadInformationDomainService; -import com.project.information.domain.service.UploadService; +import com.project.information.utils.MinIoUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; -import java.util.Map; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; import java.util.Objects; @Service public class UploadInformationDomainServiceImpl implements UploadInformationDomainService { - @Autowired - private UploadService uploadService; - @Autowired private InformationBaseService informationBaseService; + @Autowired + private ProductLineBaseService productLineBaseService; + @Autowired + private MinIoUtils minIoUtils; - + private static final long MAX_SIZE = 50 * 1024 * 1024; // 50MB 限制 @Override - public Result initMultiPartUpload(InformationDTO dto) throws Exception { - if (StrUtil.isBlank(dto.getName()) || Objects.isNull(dto.getPartCount()) || Objects.isNull(dto.getSubLineId())) { - throw new MissingParameterException(ResultCodeEnum.MISSING_PARAMETER.getMessage()); + public Result checkDuplicates(Long subLineId, List files) throws Exception { + if (Objects.isNull(subLineId)) { + throw new BusinessErrorException("所属子产品线id不能为空"); + } + if (CollUtil.isEmpty(files)) { + throw new BusinessErrorException("上传文件集合不能为空"); + } + ProductLineEntity productLine = productLineBaseService.getById(subLineId); + if (Objects.isNull(productLine) || !productLine.getLeaf()) { + throw new BusinessErrorException("不存在的子产品线"); } - String path = dto.getSubLineId().toString(); + List overSizeList = files.stream() + .filter(f -> f.getSize() > MAX_SIZE) + .map(FileCheckItem::getName) + .toList(); - LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); - queryWrapper.eq(InformationEntity::getSubLineId , dto.getSubLineId()); - queryWrapper.eq(InformationEntity::getName , dto.getName()); - InformationEntity existOne = informationBaseService.getOne(queryWrapper); - if (Objects.nonNull(existOne)) { + if (CollUtil.isNotEmpty(overSizeList)) { + // 发现超大文件,直接中断,不查数据库 + throw new BusinessErrorException(String.format( + "以下文件超过50MB限制,禁止上传:【%s】", + String.join("、", overSizeList))); + } + List fileNames = files.stream().map(FileCheckItem::getName).toList(); + List duplicatesList = informationBaseService.list(new LambdaQueryWrapper() + .select(InformationEntity::getName) + .eq(InformationEntity::getSubLineId, subLineId) + .in(InformationEntity::getName, fileNames)) + .stream() + .map(InformationEntity::getName) + .toList(); + if (CollUtil.isNotEmpty(duplicatesList)) { + throw new BusinessErrorException(String.format("你上传的文件集合中【%s】已存在,会进行覆盖操作,是否继续?" , String.join("," , duplicatesList))); } + return Result.success("校验成功"); + } - Map stringObjectMap = uploadService.initMultiPartUpload(path, dto.getName() , dto.getPartCount() , "application/octet-stream"); - InitMultipartDTO initMultipartDTO = BeanUtil.mapToBean(stringObjectMap, InitMultipartDTO.class, true, CopyOptions.create()); - initMultipartDTO.setObjectName(path + "/" + dto.getName()); - return Result.success(initMultipartDTO); + @Override + @Transactional(rollbackFor = Exception.class) + public Result batchUploadAndOverwrite(MultipartFile[] files, Long subLineId) throws Exception { + if (Objects.isNull(subLineId)) { + throw new BusinessErrorException("所属子产品线id不能为空"); + } + if (Objects.isNull(files) || files.length == 0) { + throw new BusinessErrorException("上传文件集合不能为空"); + } + ProductLineEntity productLine = productLineBaseService.getById(subLineId); + if (Objects.isNull(productLine) || !productLine.getLeaf()) { + throw new BusinessErrorException("不存在的子产品线"); + } + List successFiles = new ArrayList<>(); + for (MultipartFile file : files) { + String fileName = file.getOriginalFilename(); + // 删掉原来的 + informationBaseService.remove(new LambdaQueryWrapper() + .eq(InformationEntity::getSubLineId, subLineId) + .eq(InformationEntity::getName, fileName)); + String filePath = String.format("%s/%s/%s.%s", + subLineId, + DateUtil.format(new Date(), "yyyyMMdd"), + IdUtil.fastSimpleUUID(), + FileNameUtil.extName(fileName)); + minIoUtils.uploadFile(file.getInputStream() , filePath); + successFiles.add(fileName); + InformationDTO informationDTO = new InformationDTO(); + informationDTO.setSubLineId(subLineId); + informationDTO.setSubLineName(productLine.getName()); + informationDTO.setName(fileName); + informationDTO.setFilePath(filePath); + informationDTO.setParseStatus(InformationParseStatusEnum.NotStart.getValue()); + informationBaseService.save(informationDTO.toEntity(InformationEntity::new)); + } + return Result.success(String.format("上传成功:【%s】" , String.join("," , successFiles))); } } diff --git a/src/main/java/com/project/information/domain/service/impl/UploadServiceImpl.java b/src/main/java/com/project/information/domain/service/impl/UploadServiceImpl.java deleted file mode 100644 index 85e641b..0000000 --- a/src/main/java/com/project/information/domain/service/impl/UploadServiceImpl.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.project.information.domain.service.impl; - -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import com.project.information.domain.dto.StreamInfoDTO; -import com.project.information.domain.service.UploadService; -import com.project.information.utils.MinIoUtils; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; - -import java.util.Map; - -@Service -@RequiredArgsConstructor -public class UploadServiceImpl implements UploadService { - - private final MinIoUtils minIoUtils; - - @Override - public Map initMultiPartUpload(String path, String filename, Integer partCount, String contentType) { - path = path.replaceAll("/+", "/"); - if (path.indexOf("/") == 0) { - path = path.substring(1); - } - String filePath = path + "/" + filename; - - Map result; - // 单文件,直接上传 - if (partCount == 1) { - String uploadObjectUrl = minIoUtils.getUploadObjectUrl(filePath); - result = ImmutableMap.of("uploadUrls", ImmutableList.of(uploadObjectUrl)); - } else {//多文件,分片上传 - result = minIoUtils.initMultiPartUpload(filePath, partCount, contentType); - } - - return result; - } - - @Override - public StreamInfoDTO mergeMultipartUpload(String objectName, String uploadId) { - return minIoUtils.mergeMultipartUpload(objectName, uploadId); - } -} \ No newline at end of file diff --git a/src/main/java/com/project/information/utils/MinIoUtils.java b/src/main/java/com/project/information/utils/MinIoUtils.java index fa37d12..982f43a 100644 --- a/src/main/java/com/project/information/utils/MinIoUtils.java +++ b/src/main/java/com/project/information/utils/MinIoUtils.java @@ -5,18 +5,15 @@ import com.google.common.collect.HashMultimap; import com.project.information.config.CustomMinioClient; import com.project.information.config.MinioProperties; import com.project.information.domain.dto.StreamInfoDTO; -import io.minio.GetObjectArgs; -import io.minio.GetPresignedObjectUrlArgs; -import io.minio.ListPartsResponse; -import io.minio.MinioClient; +import io.minio.*; import io.minio.http.Method; import io.minio.messages.Part; import jakarta.annotation.PostConstruct; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Configuration; -import org.springframework.stereotype.Component; +import java.io.InputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -124,4 +121,83 @@ public class MinIoUtils { return null; } } + /** + * 简化的InputStream上传方法(自动设置ContentType) + * + * @param inputStream 文件输入流 + * @param fileName 文件名 + * @return + */ + public String uploadFile(InputStream inputStream, String fileName) throws Exception{ + String contentType = getContentTypeByFileName(fileName); + uploadInputStream(inputStream, fileName, contentType); + return fileName; + } + + /** + * 根据文件扩展名自动设置ContentType + * + * @param fileName 文件名 + * @return ContentType + */ + private String getContentTypeByFileName(String fileName) { + if (StrUtil.isBlank(fileName)) { + return "application/octet-stream"; + } + + String extension = fileName.toLowerCase(); + if (extension.endsWith(".xlsx")) { + return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; + } else if (extension.endsWith(".xls")) { + return "application/vnd.ms-excel"; + } else if (extension.endsWith(".docx")) { + return "application/vnd.openxmlformats-officedocument.wordprocessingml.document"; + } else if (extension.endsWith(".doc")) { + return "application/msword"; + } else if (extension.endsWith(".pdf")) { + return "application/pdf"; + } else if (extension.endsWith(".txt")) { + return "text/plain"; + } else if (extension.endsWith(".jpg") || extension.endsWith(".jpeg")) { + return "image/jpeg"; + } else if (extension.endsWith(".png")) { + return "image/png"; + } else { + return "application/octet-stream"; + } + } + + public void uploadInputStream(InputStream inputStream, String objectName, String contentType) throws Exception{ + if (inputStream == null) { + log.error("文件上传失败,输入流不能为空"); + return; + } + if (StrUtil.isBlank(objectName)) { + log.error("文件上传失败,文件名不能为空"); + return; + } + + // 设置默认内容类型 + if (StrUtil.isBlank(contentType)) { + contentType = "application/octet-stream"; + } + + // 设置请求头 + HashMultimap headers = HashMultimap.create(); + headers.put("Content-Type", contentType); + + // 上传文件 + customMinioClient.putObject( + PutObjectArgs.builder() + .bucket(minioProperties.getBucket()) + .object(objectName) + .stream(inputStream, -1, 10485760) // 10MB part size + .headers(headers) + .build() + ); + + log.info("文件上传成功,"+objectName); + + + } } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index b683466..5d81f67 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -10,11 +10,14 @@ spring: driverClassName: com.mysql.cj.jdbc.Driver password: Itc@123456 url: jdbc:mysql://172.16.204.50:3306/ai_evaluator?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true +# url: jdbc:mysql://8.129.84.155:3306/ai_evaluator?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true username: root # Redis data: redis: host: 172.16.204.50 + +# host: 8.129.84.155 port: 6379 password: 123456 database: 3 @@ -47,10 +50,10 @@ spring: generator: write-numbers-as-strings: true minio: - endpoint: ${MINIO_ENDPOINT:http://172.16.124.14:9000} - accessKey: ${MINIO_ASSESSKEY:X42cCp42U4BSJHPAV1Aq} - secretKey: ${MINIO_SECRETKEY:099NCOOSklhJyeJE6C73YxbbUT4Y4dRJK0RafAgv} - bucket: ${MINIO_BUCKET:proposalworkshop} + endpoint: ${MINIO_ENDPOINT:http://172.16.204.50:9000} + accessKey: ${MINIO_ASSESSKEY:DTKYZDZM1i31XOvd24SP} + secretKey: ${MINIO_SECRETKEY:PnfLPcJbvaUboZIwYZAADPB0pDtPZgbi0QiLSs3C} + bucket: ${MINIO_BUCKET:ai-evaluator} mybatis-plus: configuration: map-underscore-to-camel-case: true @@ -59,4 +62,8 @@ mybatis-plus: milvus: host: 172.16.204.50 - port: 19530 \ No newline at end of file + port: 19530 +ding: + appKey: ding7ocyghkjyowg38ck + appSecret: x1SWUdGfiLzjeRAoicGQO49J73sI59gwgPXLLkcoI6DY4M0UZDDND4QT2laNtT22 + agentId: 4145005949 \ No newline at end of file