commit 31c7245356848a68c40fd4341fdb96a3e6ffa414 Author: luoweijian <1329394916@qq.com> Date: Sat Jan 24 09:30:21 2026 +0800 first-commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5ff6309 --- /dev/null +++ b/.gitignore @@ -0,0 +1,38 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..aa00ffa --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..82dbec8 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,14 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml new file mode 100644 index 0000000..2b63946 --- /dev/null +++ b/.idea/uiDesigner.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..9015450 --- /dev/null +++ b/pom.xml @@ -0,0 +1,139 @@ + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 3.2.3 + + + com.project + ai-evaluator + 1.0-SNAPSHOT + jar + + ai-evaluator + + + + + 17 + + + 3.0.0 + + + 8.0.29 + 1.2.20 + 3.5.5 + + + 8.0.3 + 4.8.1 + + + 3.0.13 + 1.18.30 + + + + com.jayway.jsonpath + json-path + + + org.springframework.boot + spring-boot-starter-web + ${spring-boot.version} + + + org.elasticsearch.client + elasticsearch-rest-high-level-client + + + org.elasticsearch.client + elasticsearch-rest-client + + + org.elasticsearch + elasticsearch + + + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-aop + ${spring-boot.version} + + + mysql + mysql-connector-java + ${mysql.version} + runtime + + + org.projectlombok + lombok + provided + + + com.github.tingyugetc520 + dt-java + 0.1.2 + + + io.vavr + vavr + 0.10.4 + + + com.baomidou + mybatis-plus-boot-starter + 3.5.6 + + + org.mybatis + mybatis-spring + + + + + javax.servlet + javax.servlet-api + 4.0.1 + + + cn.hutool + hutool-all + 5.8.3 + + + com.baomidou + dynamic-datasource-spring-boot3-starter + 4.2.0 + + + org.mybatis + mybatis-spring + 3.0.3 + + + jakarta.persistence + jakarta.persistence-api + 3.1.0 + + + org.hibernate.orm + hibernate-core + 6.4.4.Final + + + diff --git a/src/main/java/com/project/AiEvaluatorApplication.java b/src/main/java/com/project/AiEvaluatorApplication.java new file mode 100644 index 0000000..f00b458 --- /dev/null +++ b/src/main/java/com/project/AiEvaluatorApplication.java @@ -0,0 +1,17 @@ +package com.project; + + +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.scheduling.annotation.EnableAsync; + +@SpringBootApplication +@EnableAsync +@MapperScan({ + "com.project.*.mapper"}) +public class AiEvaluatorApplication { + public static void main( String[] args ) { + SpringApplication.run(AiEvaluatorApplication.class , args); + } +} diff --git a/src/main/java/com/project/base/config/CorsConfig.java b/src/main/java/com/project/base/config/CorsConfig.java new file mode 100644 index 0000000..2bf64b6 --- /dev/null +++ b/src/main/java/com/project/base/config/CorsConfig.java @@ -0,0 +1,21 @@ +package com.project.base.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * Cors解决跨域问题 + */ +@Configuration +public class CorsConfig implements WebMvcConfigurer { + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**") + .allowedOrigins("*") + .allowedMethods("GET", "POST", "Options", "DELETE", "PUT") + .allowedHeaders("*") + .allowCredentials(false) + .maxAge(16800); + } +} \ No newline at end of file diff --git a/src/main/java/com/project/base/config/CustomIdGenerator.java b/src/main/java/com/project/base/config/CustomIdGenerator.java new file mode 100644 index 0000000..b986645 --- /dev/null +++ b/src/main/java/com/project/base/config/CustomIdGenerator.java @@ -0,0 +1,39 @@ +package com.project.base.config; + +import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.concurrent.atomic.AtomicLong; + +/** + * 自定义ID生成器 + */ +@Slf4j +@Component +public class CustomIdGenerator implements IdentifierGenerator { + + /** + * workerId,机器id + * datacenterId,数据标识id + */ + private final SnowflakeIdWorker idWorker = new SnowflakeIdWorker(0, 0); + + /** + * AtomicLong是作用是对长整形进行原子操作。 + * 在32位操作系统中,64位的long 和 double 变量由于会被JVM当作两个分离的32位来进行操作,所以不具有原子性。 + * 而使用AtomicLong能让long的操作保持原子型。 + * @param entity + * @return + */ + @Override + public Long nextId(Object entity) { + //可以将当前传入的class全类名来作为bizKey,或者提取参数来生成bizKey进行分布式Id调用生成. + String bizKey = entity.getClass().getName(); + log.debug("bizKey:{}", bizKey); + AtomicLong al = new AtomicLong(idWorker.nextId()); + final long id = al.get(); + log.debug("为{}生成主键值->:{}", bizKey, id); + return id; + } +} \ No newline at end of file diff --git a/src/main/java/com/project/base/config/MyMetaObjectHandler.java b/src/main/java/com/project/base/config/MyMetaObjectHandler.java new file mode 100644 index 0000000..76a2e0f --- /dev/null +++ b/src/main/java/com/project/base/config/MyMetaObjectHandler.java @@ -0,0 +1,28 @@ +package com.project.base.config; + +import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; +import lombok.extern.slf4j.Slf4j; +import org.apache.ibatis.reflection.MetaObject; +import org.springframework.stereotype.Component; + +import java.util.Date; + +@Slf4j +@Component +public class MyMetaObjectHandler implements MetaObjectHandler { + + @Override + public void insertFill(MetaObject metaObject) { + long timeMillis = System.currentTimeMillis() / 1000 * 1000; + Date currentDate = new Date(timeMillis); + this.strictInsertFill(metaObject, "createTime", Date.class, currentDate); // 起始版本 3.3.0(推荐使用) + this.strictInsertFill(metaObject, "updateTime", Date.class, currentDate); + } + + @Override + public void updateFill(MetaObject metaObject) { + long timeMillis = System.currentTimeMillis() / 1000 * 1000; + Date currentDate = new Date(timeMillis); + this.strictUpdateFill(metaObject, "updateTime", Date.class, currentDate); // 起始版本 3.3.0(推荐使用) + } +} \ No newline at end of file diff --git a/src/main/java/com/project/base/config/SnowflakeIdWorker.java b/src/main/java/com/project/base/config/SnowflakeIdWorker.java new file mode 100644 index 0000000..533955c --- /dev/null +++ b/src/main/java/com/project/base/config/SnowflakeIdWorker.java @@ -0,0 +1,185 @@ +package com.project.base.config; + +/** + * Twitter_Snowflake
+ * SnowFlake的结构如下(每部分用-分开):
+ * 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000
+ * 1位标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0
+ * 41位时间截(毫秒级),注意,41位时间截不是存储当前时间的时间截,而是存储时间截的差值(当前时间截 - 开始时间截) + * 得到的值),这里的的开始时间截,一般是我们的id生成器开始使用的时间,由我们程序来指定的(如下下面程序IdWorker类的startTime属性)。41位的时间截,可以使用69年,年T = (1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69
+ * 10位的数据机器位,可以部署在1024个节点,包括5位datacenterId和5位workerId
+ * 12位序列,毫秒内的计数,12位的计数顺序号支持每个节点每毫秒(同一机器,同一时间截)产生4096个ID序号
+ * 加起来刚好64位,为一个Long型。
+ * SnowFlake的优点是,整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由数据中心ID和机器ID作区分),并且效率较高,经测试,SnowFlake每秒能够产生26万ID左右。 + */ +public class SnowflakeIdWorker { + + // ==============================Fields=========================================== + /** + * 1766395305 + * 开始时间截 2021-01-01 00:00:00 + * https://tool.lu/timestamp/ + */ + private static final long twepoch = 1609430400000L; + + /** + * 机器id所占的位数 + */ + private static final long workerIdBits = 5L; + + /** + * 数据标识id所占的位数 + */ + private static final long datacenterIdBits = 5L; + + /** + * 支持的最大机器id,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数) + */ + private static final long maxWorkerId = -1L ^ (-1L << workerIdBits); + + /** + * 支持的最大数据标识id,结果是31 + */ + private static final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits); + + /** + * 序列在id中占的位数 + */ + private static final long sequenceBits = 12L; + + /** + * 机器ID向左移12位 + */ + private static final long workerIdShift = sequenceBits; + + /** + * 数据标识id向左移17位(12+5) + */ + private static final long datacenterIdShift = sequenceBits + workerIdBits; + + /** + * 时间截向左移22位(5+5+12) + */ + private static final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits; + + /** + * 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095) + */ + private static final long sequenceMask = -1L ^ (-1L << sequenceBits); + + /** + * 工作机器ID(0~31) + */ + private long workerId; + + /** + * 数据中心ID(0~31) + */ + private long datacenterId; + + /** + * 毫秒内序列(0~4095) + */ + private long sequence = 0L; + + /** + * 上次生成ID的时间截 + */ + private long lastTimestamp = -1L; + + //==============================Constructors===================================== + + /** + * 构造函数 + * + * @param workerId 工作ID (0~31) + * @param datacenterId 数据中心ID (0~31) + */ + public SnowflakeIdWorker(long workerId, long datacenterId) { + if (workerId > maxWorkerId || workerId < 0) { + throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId)); + } + if (datacenterId > maxDatacenterId || datacenterId < 0) { + throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId)); + } + this.workerId = workerId; + this.datacenterId = datacenterId; + } + + // ==============================Methods========================================== + + /** + * 获得下一个ID (该方法是线程安全的) + * + * @return SnowflakeId + */ + public synchronized long nextId() { + long timestamp = timeGen(); + + //如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常 + if (timestamp < lastTimestamp) { + throw new RuntimeException( + String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp)); + } + + //如果是同一时间生成的,则进行毫秒内序列 + if (lastTimestamp == timestamp) { + sequence = (sequence + 1) & sequenceMask; + //毫秒内序列溢出 + if (sequence == 0) { + //阻塞到下一个毫秒,获得新的时间戳 + timestamp = tilNextMillis(lastTimestamp); + } + } + //时间戳改变,毫秒内序列重置 + else { + sequence = 0L; + } + + //上次生成ID的时间截 + lastTimestamp = timestamp; + + //移位并通过或运算拼到一起组成64位的ID + return ((timestamp - twepoch) << timestampLeftShift) // + | (datacenterId << datacenterIdShift) // + | (workerId << workerIdShift) // + | sequence; + } + + /** + * 阻塞到下一个毫秒,直到获得新的时间戳 + * + * @param lastTimestamp 上次生成ID的时间截 + * @return 当前时间戳 + */ + protected long tilNextMillis(long lastTimestamp) { + long timestamp = timeGen(); + while (timestamp <= lastTimestamp) { + timestamp = timeGen(); + } + return timestamp; + } + + /** + * 返回以毫秒为单位的当前时间 + * + * @return 当前时间(毫秒) + */ + protected long timeGen() { + return System.currentTimeMillis(); + } + + //==============================Test============================================= + + /** + * 测试 + */ + public static void main(String[] args) { + SnowflakeIdWorker idWorker = new SnowflakeIdWorker(0, 0); + for (int i = 0; i < 1000; i++) { + long id = idWorker.nextId(); + System.out.println(Long.toBinaryString(id)); + System.out.println(id); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/project/base/domain/advice/GlobalExceptionHandlerAdvice.java b/src/main/java/com/project/base/domain/advice/GlobalExceptionHandlerAdvice.java new file mode 100644 index 0000000..d44a7cc --- /dev/null +++ b/src/main/java/com/project/base/domain/advice/GlobalExceptionHandlerAdvice.java @@ -0,0 +1,90 @@ +package com.project.base.domain.advice; + + +import com.project.base.domain.exception.BusinessErrorException; +import com.project.base.domain.exception.MissingParameterException; +import com.project.base.domain.exception.ResourceNotExistException; +import com.project.base.domain.result.Result; +import com.project.base.domain.result.ResultCodeEnum; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.validation.BindException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; +import org.springframework.web.multipart.MaxUploadSizeExceededException; + +@Slf4j +@ControllerAdvice +public class GlobalExceptionHandlerAdvice { + /**-------- 其他错误 --------**/ + + @ExceptionHandler(Exception.class) + @ResponseBody + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public Result error(Exception e) { + e.printStackTrace(); + log.error("全局异常捕获:" + e); + return Result.fail(); + } + + + /**-------- 前端传参类型转换错误 --------**/ + @ExceptionHandler(BindException.class) + @ResponseBody + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public Result error(BindException e) { + e.printStackTrace(); + log.error("全局异常捕获:" + e); + return Result.fail(ResultCodeEnum.MISSING_PARAMETER); + } + + @ExceptionHandler(MethodArgumentTypeMismatchException.class) + @ResponseBody + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + public Result error(MethodArgumentTypeMismatchException e) { + e.printStackTrace(); + log.error("全局异常捕获:" + e); + return Result.fail(ResultCodeEnum.MISSING_PARAMETER); + } + /**-------- 业务错误 --------**/ + @ExceptionHandler(BusinessErrorException.class) + @ResponseBody + @ResponseStatus(HttpStatus.OK) + public Result error(BusinessErrorException e) { + e.printStackTrace(); + log.error("全局异常捕获:" + e); + return Result.fail(ResultCodeEnum.BUSINESS_ERROR , e.getMessage()); + } + + /**-------- 缺少必须参数 --------**/ + @ExceptionHandler(MissingParameterException.class) + @ResponseBody + @ResponseStatus(HttpStatus.OK) + public Result error(MissingParameterException e) { + e.printStackTrace(); + log.error("全局异常捕获:" + e); + return Result.fail(ResultCodeEnum.getResultCodeEnumByCode(e.getCode()) , e.getMessage()); + } + + + @ExceptionHandler(ResourceNotExistException.class) + @ResponseBody + @ResponseStatus(HttpStatus.OK) + public Result error(ResourceNotExistException e) { + log.error("出错了" , e); + log.error("全局异常捕获:" + e); + return Result.fail(ResultCodeEnum.getResultCodeEnumByCode(e.getCode()) , e.getMessage()); + } + + @ExceptionHandler(MaxUploadSizeExceededException.class) + @ResponseBody + @ResponseStatus(HttpStatus.OK) + public Result error(MaxUploadSizeExceededException e) { + e.printStackTrace(); + log.error("全局异常捕获:" + e); + return Result.fail(ResultCodeEnum.MISSING_PARAMETER , e.getMessage()); + } +} diff --git a/src/main/java/com/project/base/domain/dto/BaseDTO.java b/src/main/java/com/project/base/domain/dto/BaseDTO.java new file mode 100644 index 0000000..a4364a7 --- /dev/null +++ b/src/main/java/com/project/base/domain/dto/BaseDTO.java @@ -0,0 +1,23 @@ +package com.project.base.domain.dto; + + +import lombok.Data; + +import java.util.Date; + +@Data +public class BaseDTO { + + private Long id; + + private Long creatorId; + + private Date createTime; + + private Long updaterId; + + private Date updateTime; + + private Boolean deleted; + +} diff --git a/src/main/java/com/project/base/domain/entity/BaseEntity.java b/src/main/java/com/project/base/domain/entity/BaseEntity.java new file mode 100644 index 0000000..80974db --- /dev/null +++ b/src/main/java/com/project/base/domain/entity/BaseEntity.java @@ -0,0 +1,50 @@ +package com.project.base.domain.entity; + +import com.baomidou.mybatisplus.annotation.*; +import com.project.base.domain.enums.StatusEnum; +import jakarta.persistence.Column; +import jakarta.persistence.Id; +import jakarta.persistence.MappedSuperclass; +import lombok.Data; +import org.hibernate.annotations.Comment; + +import java.io.Serializable; +import java.util.Date; + +@MappedSuperclass +@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'") + private Long creatorId; + + @TableField(value = "create_time" , fill = FieldFill.INSERT) + @Comment("创建时间") + @Column(name = "create_time") + private Date createTime; + + @TableField(value = "updater_id" , fill = FieldFill.INSERT_UPDATE) + @Column(name = "updater_id", columnDefinition="bigint(20) comment '更新用户id'") + private Long updaterId; + + @TableField(value = "update_time" , fill = FieldFill.INSERT_UPDATE) + @Comment("更新时间") + @Column(name = "update_time") + private Date updateTime; + + /** + * deleted字段请在数据库中 设置为tinyInt 并且非null 默认值为0 + */ + @TableField("deleted") + @TableLogic + @Comment("逻辑删除位") + @Column(name = "deleted") + private Boolean deleted = StatusEnum.Normal.getValue(); + +} \ No newline at end of file diff --git a/src/main/java/com/project/base/domain/enums/HasValueEnum.java b/src/main/java/com/project/base/domain/enums/HasValueEnum.java new file mode 100644 index 0000000..db82b89 --- /dev/null +++ b/src/main/java/com/project/base/domain/enums/HasValueEnum.java @@ -0,0 +1,24 @@ +package com.project.base.domain.enums; + + +public interface HasValueEnum { + + /** + * 获取枚举名,将会由枚举抽象类默认实现 + * + * @see Enum + */ + String name(); + + /** + * 获取枚举值 + */ + T getValue(); + + /** + * 枚举名比较时,是否需要区分大小写,默认为需要区分 + */ + default boolean caseCompare() { + return true; + } +} diff --git a/src/main/java/com/project/base/domain/enums/StatusEnum.java b/src/main/java/com/project/base/domain/enums/StatusEnum.java new file mode 100644 index 0000000..33cfab2 --- /dev/null +++ b/src/main/java/com/project/base/domain/enums/StatusEnum.java @@ -0,0 +1,17 @@ +package com.project.base.domain.enums; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum StatusEnum implements HasValueEnum{ + + Normal(Boolean.FALSE), Delete(Boolean.TRUE); + + + private final Boolean value; + + + +} \ No newline at end of file diff --git a/src/main/java/com/project/base/domain/exception/BusinessErrorException.java b/src/main/java/com/project/base/domain/exception/BusinessErrorException.java new file mode 100644 index 0000000..4eff757 --- /dev/null +++ b/src/main/java/com/project/base/domain/exception/BusinessErrorException.java @@ -0,0 +1,21 @@ +package com.project.base.domain.exception; + +import com.project.base.domain.result.ResultCodeEnum; +import lombok.Data; + +@Data +public class BusinessErrorException extends RuntimeException{ + private Integer code; + + public BusinessErrorException(String msg) { + super(msg); + this.code = ResultCodeEnum.BUSINESS_ERROR.getCode(); + } + + + public BusinessErrorException(Integer code , String msg) { + super(msg); + this.code = code; + } + +} \ No newline at end of file diff --git a/src/main/java/com/project/base/domain/exception/MissingParameterException.java b/src/main/java/com/project/base/domain/exception/MissingParameterException.java new file mode 100644 index 0000000..cd429b9 --- /dev/null +++ b/src/main/java/com/project/base/domain/exception/MissingParameterException.java @@ -0,0 +1,21 @@ +package com.project.base.domain.exception; + +import com.project.base.domain.result.ResultCodeEnum; +import lombok.Data; + + +@Data +public class MissingParameterException extends RuntimeException { + private final Integer code; + + public MissingParameterException(String msg) { + super(msg); + this.code = ResultCodeEnum.MISSING_PARAMETER.getCode(); + } + + + public MissingParameterException(Integer code , String msg) { + super(msg); + this.code = code; + } +} diff --git a/src/main/java/com/project/base/domain/exception/ResourceNotExistException.java b/src/main/java/com/project/base/domain/exception/ResourceNotExistException.java new file mode 100644 index 0000000..8e9773e --- /dev/null +++ b/src/main/java/com/project/base/domain/exception/ResourceNotExistException.java @@ -0,0 +1,21 @@ +package com.project.base.domain.exception; + + +import com.project.base.domain.result.ResultCodeEnum; +import lombok.Data; + +@Data +public class ResourceNotExistException extends RuntimeException { + private final Integer code; + + public ResourceNotExistException(String msg) { + super(msg); + this.code = ResultCodeEnum.RESOURCE_NOT_EXIST.getCode(); + } + + public ResourceNotExistException(Integer code , String msg) { + super(msg); + this.code = code; + } + +} diff --git a/src/main/java/com/project/base/domain/result/Result.java b/src/main/java/com/project/base/domain/result/Result.java new file mode 100644 index 0000000..9f3e9a0 --- /dev/null +++ b/src/main/java/com/project/base/domain/result/Result.java @@ -0,0 +1,63 @@ +package com.project.base.domain.result; + +import lombok.Getter; +import lombok.Setter; + +import java.io.Serializable; + + + + +@Getter +@Setter +public class Result implements Serializable { + private Integer code = 0;; + + private Boolean success = true; + + private String message; + + + private T data; + + public static Result success(T data) { + Result result = new Result(); + result.setCode(ResultCodeEnum.SUCCESS.getCode()); + result.setMessage("success"); + result.setData(data); + return result; + } + + public static Result success(T data, String message) { + Result result = new Result(); + result.setCode(ResultCodeEnum.SUCCESS.getCode()); + result.setMessage(message); + result.setData(data); + return result; + } + + public static Result fail() { + Result res = new Result(); + res.setCode(ResultCodeEnum.UNKNOWN_ERROR.getCode()); + res.setMessage(ResultCodeEnum.UNKNOWN_ERROR.getMessage()); + res.setSuccess(false); + return res; + } + + public static Result fail(ResultCodeEnum resultCodeEnum) { + Result res = new Result(); + res.setCode(resultCodeEnum.getCode()); + res.setMessage(resultCodeEnum.getMessage()); + res.setSuccess(false); + return res; + } + + public static Result fail(ResultCodeEnum resultCodeEnum , String msg) { + Result res = new Result(); + res.setCode(resultCodeEnum.getCode()); + res.setMessage(msg); + res.setSuccess(false); + return res; + } + +} diff --git a/src/main/java/com/project/base/domain/result/ResultCodeEnum.java b/src/main/java/com/project/base/domain/result/ResultCodeEnum.java new file mode 100644 index 0000000..8305765 --- /dev/null +++ b/src/main/java/com/project/base/domain/result/ResultCodeEnum.java @@ -0,0 +1,41 @@ +package com.project.base.domain.result; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum ResultCodeEnum { + SUCCESS(true , 0 , "成功"), + MISSING_PARAMETER(false , 40001 , "请求参数缺失或格式错误"), + INVALID_REQUEST_BODY(false , 40002 , "无效的请求体"), + SIGNATURE_VERIFICATION_FAIL(false , 40101 , "API签名验证失败"), + TIMESTAMP_INVALID(false ,40102 , "请求时间戳已过期") , + USER_NOT_FIND(false , 40103 , "用户身份信息缺失") , + PERMISSION_DENIED(false , 40301 , "操作权限不足"), + RESOURCE_NOT_EXIST(false , 40401 , "请求的资源不存在") , + INTERNAL_SYSTEM_ERROR(false , 50001 , "系统内部错误,请稍后重试"), + EXTERNAL_CALL_ERROR(false , 50002 , "外部服务调用失败"), + BUSINESS_ERROR(false , 60001 , "业务错误"), + UNKNOWN_ERROR(false , -1 , "未知错误"), + + ; + private Boolean success; + private Integer code; + private String message; + ResultCodeEnum(boolean success, Integer code, String message) { + this.success = success; + this.code = code; + this.message = message; + } + + public static ResultCodeEnum getResultCodeEnumByCode(Integer code) { + for (ResultCodeEnum value : ResultCodeEnum.values()) { + if (value.getCode().equals(code)) { + return value; + } + } + return null; + } + +} diff --git a/src/main/java/com/project/base/domain/utils/TreeUtils.java b/src/main/java/com/project/base/domain/utils/TreeUtils.java new file mode 100644 index 0000000..a40318d --- /dev/null +++ b/src/main/java/com/project/base/domain/utils/TreeUtils.java @@ -0,0 +1,199 @@ +package com.project.base.domain.utils; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.StrUtil; +import com.google.common.collect.Lists; +import lombok.extern.slf4j.Slf4j; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.BiConsumer; +import java.util.function.Function; + +/** + * @Description: 树形结构对象的构建工具类 + * @ClassName: TreeUtils + * @Author: luoweijian + * @Date: 2023/8/7 13:59 + * Copyright (C) 2021-2022 CASEEDER, All Rights Reserved. + * 注意:本内容仅限于内部传阅,禁止外泄以及用于其他的商业目的 + */ +@Slf4j +public class TreeUtils { + + + /** + * 构建前端所需要树结构,主键为 Long 时 + * @param tList 要构建的节点列表 + * @param getIdFn 获取节点标识Fn + * @param getParentIdFn 获取父节点标识Fn + * @param setChild 知道父节点的所有子节点后的Fn + * @return + * @param + * @param + */ + public static List buildLongTree(List tList , Function getIdFn , Function getParentIdFn , BiConsumer> setChild) { + try { + List returnList = new ArrayList<>(); + //主键id集合 + List tempList = new ArrayList<>(); + for (T t : tList) { + Long primaryId = (Long) getIdFn.apply(t); + tempList.add(primaryId); + } + for (T t : tList) { + // 如果是顶级节点, 遍历该父节点的所有子节点 + Long parentId = (Long) getParentIdFn.apply(t); + if (!tempList.contains(parentId)) { + recursionLong(tList, t , getIdFn , getParentIdFn, setChild); + returnList.add(t); + } + } + if (returnList.isEmpty()) { + returnList = tList; + } + return returnList; + } catch (Exception e) { + log.error(String.format("树结构转换失败:%s" , e.getMessage())); + return tList; + } + } + + + + /** + * 递归设置子集数据,主键为 Long 时 + * @param list + * @param o + * @param getIdFn + * @param getParentIdFn + * @param setChild + * @param + * @param + * @throws IllegalAccessException + */ + private static void recursionLong(List list, T o , Function getIdFn , Function getParentIdFn , BiConsumer> setChild) throws IllegalAccessException { + // 得到子节点列表 + List childList = getLongChildList(list, o , getIdFn , getParentIdFn); + invokeChildrenList(o, childList , setChild); + + for (T oChild : childList) { + if (getLongChildList(list, oChild , getIdFn , getParentIdFn).size() > 0) { + recursionLong(list, oChild , getIdFn , getParentIdFn , setChild); + } + } + } + + + + /** + * 得到子节点列表,主键为 Long 时 + * @param list + * @param object + * @param getIdFn + * @param getParentIdFn + * @return + * @param + * @param + * @throws IllegalAccessException + */ + private static List getLongChildList(List list, T object , Function getIdFn , Function getParentIdFn) throws IllegalAccessException { + Long primaryId = (Long) getIdFn.apply(object); + + List objects = new ArrayList<>(); + for (T o : list) { + Long parentId = (Long) getParentIdFn.apply(o); + if (null != parentId && parentId.longValue() == primaryId.longValue()) { + objects.add(o); + } + } + return objects; + } + + + + /** + * 设置子集数据 + * @param o + * @param childList + * @param setChild + * @param + */ + private static void invokeChildrenList(T o, List childList , BiConsumer> setChild) { + setChild.accept(o , childList); + } + + + /** + * 将树转为List + * @param list + * @param getChildFn + * @return + * @param + */ + public static List tree2List(List list , Function> getChildFn) { + List res = Lists.newArrayList(); + for (T node : list) { + List childList = getChildFn.apply(node); + res.add(node); + if (CollUtil.isNotEmpty(childList)) { + res.addAll(tree2List(childList , getChildFn)); + } + } + return res; + } + + public static List buildStrTree(List tList , Function getIdFn , Function getParentIdFn , BiConsumer> setChild) { + try { + List returnList = new ArrayList<>(); + //主键id集合 + List tempList = new ArrayList<>(); + for (T t : tList) { + String primaryId = String.valueOf(getIdFn.apply(t)); + tempList.add(primaryId); + } + for (T t : tList) { + // 如果是顶级节点, 遍历该父节点的所有子节点 + String parentId = String.valueOf(getParentIdFn.apply(t)); + if (!tempList.contains(parentId)) { + recursionStr(tList, t , getIdFn , getParentIdFn, setChild); + returnList.add(t); + } + } + if (returnList.isEmpty()) { + returnList = tList; + } + return returnList; + } catch (Exception e) { + log.error(String.format("树结构转换失败:%s" , e.getMessage())); + return tList; + } + } + + private static void recursionStr(List list, T o , Function getIdFn , Function getParentIdFn , BiConsumer> setChild) throws IllegalAccessException { + // 得到子节点列表 + List childList = getStrChildList(list, o , getIdFn , getParentIdFn); + invokeChildrenList(o, childList , setChild); + + for (T oChild : childList) { + if (getStrChildList(list, oChild , getIdFn , getParentIdFn).size() > 0) { + recursionStr(list, oChild , getIdFn , getParentIdFn , setChild); + } + } + } + + private static List getStrChildList(List list, T object , Function getIdFn , Function getParentIdFn) throws IllegalAccessException { + String primaryId = String.valueOf(getIdFn.apply(object)); + + List objects = new ArrayList<>(); + for (T o : list) { + String parentId = String.valueOf(getParentIdFn.apply(o)); + if (null != parentId && StrUtil.equals(parentId , primaryId)) { + objects.add(o); + } + } + return objects; + } + +} + diff --git a/src/main/java/com/project/ding/config/DingConfig.java b/src/main/java/com/project/ding/config/DingConfig.java new file mode 100644 index 0000000..a5bd050 --- /dev/null +++ b/src/main/java/com/project/ding/config/DingConfig.java @@ -0,0 +1,23 @@ +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.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class DingConfig { + @Bean + public DtService dtService() { + DtDefaultConfigImpl config = new DtDefaultConfigImpl(); + config.setAppKey("ding4eootvq4jzas96lr"); + config.setAppSecret("UI6XcD8GFPE_W_yo2eQkMuoSsTEf1whpZYEXrsDA7CV7bkJp40B3VNcETWS1aGgg"); + // 如果是企业内部应用,通常还需要 AgentId , +// config.setAgentId(4145005949L); + + DtService dtService = new DtServiceImpl(); + dtService.setDtConfigStorage(config); + return dtService;// + } +} diff --git a/src/main/java/com/project/ding/controller/DepartmentController.java b/src/main/java/com/project/ding/controller/DepartmentController.java new file mode 100644 index 0000000..48fe01a --- /dev/null +++ b/src/main/java/com/project/ding/controller/DepartmentController.java @@ -0,0 +1,39 @@ +package com.project.ding.controller; + + +import com.github.tingyugetc520.ali.dingtalk.api.DtService; +import com.github.tingyugetc520.ali.dingtalk.bean.department.DtDepart; +import com.github.tingyugetc520.ali.dingtalk.bean.user.DtUser; +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/domain/dto/DepartmentDTO.java b/src/main/java/com/project/ding/domain/dto/DepartmentDTO.java new file mode 100644 index 0000000..d58f4ab --- /dev/null +++ b/src/main/java/com/project/ding/domain/dto/DepartmentDTO.java @@ -0,0 +1,13 @@ +package com.project.ding.domain.dto; + + +import com.project.base.domain.dto.BaseDTO; +import lombok.Data; + +@Data +public class DepartmentDTO extends BaseDTO { + private String name; + + private Long parentId; + +} diff --git a/src/main/java/com/project/ding/domain/dto/DingUserDTO.java b/src/main/java/com/project/ding/domain/dto/DingUserDTO.java new file mode 100644 index 0000000..b548454 --- /dev/null +++ b/src/main/java/com/project/ding/domain/dto/DingUserDTO.java @@ -0,0 +1,67 @@ +package com.project.ding.domain.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.Date; +import java.util.List; + + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class DingUserDTO { + /** + * 用户id + */ + @JsonProperty("userid") + private String userId; + /** + * 用户在当前开发者企业账号范围内的唯一标识 + */ + @JsonProperty("unionid") + private String unionId; + /** + * 用户姓名 + */ + @JsonProperty("name") + private String name; + + /** + * 头像地址 + */ + @JsonProperty("avatar") + private String avatar; + + /** + * 手机号码 + */ + @JsonProperty("mobile") + private String mobile; + + /** + * 员工工号 + */ + @JsonProperty("job_number") + private String jobNumber; + + /** + * 职位 + */ + @JsonProperty("title") + private String title; + + /** + * 所属部门id列表 + */ + @JsonProperty("dept_id_list") + private List deptIdList; + /** + * 入职时间 + */ + @JsonProperty("hired_date") + @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8") + private Date hiredDate; + +} diff --git a/src/main/java/com/project/ding/domain/dto/UserDTO.java b/src/main/java/com/project/ding/domain/dto/UserDTO.java new file mode 100644 index 0000000..455eae9 --- /dev/null +++ b/src/main/java/com/project/ding/domain/dto/UserDTO.java @@ -0,0 +1,9 @@ +package com.project.ding.domain.dto; + +import com.project.base.domain.dto.BaseDTO; +import lombok.Data; + +@Data +public class UserDTO extends BaseDTO { + +} diff --git a/src/main/java/com/project/ding/domain/entity/DepartmentEntity.java b/src/main/java/com/project/ding/domain/entity/DepartmentEntity.java new file mode 100644 index 0000000..02364e2 --- /dev/null +++ b/src/main/java/com/project/ding/domain/entity/DepartmentEntity.java @@ -0,0 +1,27 @@ +package com.project.ding.domain.entity; + +import com.baomidou.mybatisplus.annotation.TableField; +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 lombok.Data; +import lombok.EqualsAndHashCode; + + +@Data +@Table(name = "evaluator_department" , indexes = {@Index(name = "Idx_parentId", columnList = "parent_id")}) +@Entity +@TableName("evaluator_department") +@EqualsAndHashCode(callSuper = true) +public class DepartmentEntity extends BaseEntity { + + @Column(name = "name" , columnDefinition="varchar(255) comment '部门名称'") + private String name; + + @TableField("parent_id") + @Column(name = "parent_id" , columnDefinition="bigint(20) comment '父部门id'") + private Long parentId; +} diff --git a/src/main/java/com/project/ding/utils/DingUtil.java b/src/main/java/com/project/ding/utils/DingUtil.java new file mode 100644 index 0000000..485940f --- /dev/null +++ b/src/main/java/com/project/ding/utils/DingUtil.java @@ -0,0 +1,71 @@ +package com.project.ding.utils; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.tingyugetc520.ali.dingtalk.api.DtService; +import com.github.tingyugetc520.ali.dingtalk.bean.department.DtDepart; + +import com.google.gson.JsonObject; +import com.jayway.jsonpath.JsonPath; +import com.project.ding.domain.dto.DingUserDTO; +import io.vavr.control.Try; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; + +@Component +public class DingUtil { + @Autowired + private DtService dtService; + + public List getAllDepartment() throws Exception { + return dtService.getDepartmentService().list(null , true); + } + + public List getAllUser() throws Exception { + List list = getAllDepartment(); + + List userList = new ArrayList<>(); + for (int i = 0; i < list.size(); i++) { + List userInDepartment = getUserIdInDepartment(list.get(i).getId()); + userList.addAll(userInDepartment); + } + return userList; + } + + public List getUserIdInDepartment(Long id) throws Exception { + List res = new ArrayList<>(); + + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("dept_id" , id); + jsonObject.addProperty("cursor" , 0); + jsonObject.addProperty("size" , 100); + + String url = dtService.getDtConfigStorage().getApiUrl("/topapi/v2/user/list"); + String responseContent = dtService.post(url, jsonObject); + res.addAll(Try.of(() -> new ObjectMapper().convertValue( + JsonPath.read(responseContent, "$.result.list"), + new TypeReference>() {})).getOrElse(new ArrayList<>())); + Boolean hasMore = Try.of(() -> new ObjectMapper().convertValue( + JsonPath.read(responseContent, "$.result.has_more"), + new TypeReference() {})).getOrElse(Boolean.FALSE); + while (hasMore) { + jsonObject.addProperty("cursor" , new ObjectMapper().convertValue( + JsonPath.read(responseContent, "$.result.next_cursor"), + new TypeReference() {})); + String nextResponseContent = dtService.post(url, jsonObject); + res.addAll(Try.of(() -> new ObjectMapper().convertValue( + JsonPath.read(nextResponseContent, "$.result.list"), + new TypeReference>() {})).getOrElse(new ArrayList<>())); + hasMore = Try.of(() -> new ObjectMapper().convertValue( + JsonPath.read(nextResponseContent, "$.result.has_more"), + new TypeReference() {})).getOrElse(Boolean.FALSE); + } + return res; + } + + + +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..c0baedc --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,39 @@ +server: + port: 7088 +spring: + datasource: + dynamic: + primary: master + datasource: + master: + driverClassName: com.mysql.cj.jdbc.Driver + password: Itc@123456 + url: jdbc:mysql://172.16.204.50:3306/proposal_workshop?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true + username: root + jpa: + hibernate: + ddl-auto: update + naming: + physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl + properties: + hibernate: + dialect: org.hibernate.dialect.MySQL8Dialect + show-sql: true + # 上传下载限制 + servlet: + multipart: + max-file-size: 100MB + max-request-size: 100MB + jackson: + 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} +mybatis-plus: + configuration: + map-underscore-to-camel-case: true + mapper-locations: classpath*:mapper/**/*.xml + type-aliases-package: com.proposal.**.domain.entity \ No newline at end of file