diff --git a/.gitignore b/.gitignore index 4c305b49..d885a769 100644 --- a/.gitignore +++ b/.gitignore @@ -36,4 +36,6 @@ build/ ### Mac OS ### .DS_Store -/.idea/ \ No newline at end of file +/.idea/ +/docker/minio/data/.minio.sys/ +/docker/minio/data/achobeta-recruitment/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..d8264b39 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,14 @@ +# 基础镜像 +FROM openjdk:21 + +# 配置 +ENV PARAMS="" + +# 时区 +ENV TZ=PRC +RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone + +# 添加应用 +ADD target/AchoBeta-Recruitment-1.0.jar /AchoBeta-Recruitment-1.0.jar + +ENTRYPOINT ["sh","-c","java -jar $JAVA_OPTS /AchoBeta-Recruitment-1.0.jar $PARAMS"] diff --git a/build.sh b/build.sh index e273e82d..9bf5b932 100644 --- a/build.sh +++ b/build.sh @@ -1,5 +1,17 @@ -#!/bin/zsh -# shellcheck disable=SC2164 +#!/usr/bin/env bash +# Be sure your script exits whenever encounter errors + +echo "---------------------------------" +echo "::: Welcome to AB-Recruitment :::" + +set -e +# Be sure your charset is correct. eg: zh_CN.UTF-8 +export LC_ALL=en_US.UTF-8 +export LANG=en_US.UTF-8 +export LANGUAGE=en_US.UTF-8 + mvn clean install package -Dmaven.test.skip=true -docker-compose pull -docker-compose up -d --build \ No newline at end of file + +# 普通镜像构建,随系统版本构建 amd/arm + +docker-compose -f docker-compose.yml up -d diff --git a/docker-compose.yml b/docker-compose.yml index ff897245..d5b483d2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,12 +1,180 @@ -version: '3' +# 命令执行 docker-compose -f docker-compose.yml up -d +version: '3.9' services: - recruitment: - container_name: achobeta-recruitment #配置容器名 + mysql: + image: mysql:5.7 + container_name: mysql + command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci + restart: always + environment: + TZ: Asia/Shanghai + MYSQL_ROOT_PASSWORD: achobeta666 + MYSQL_USER: mms + MYSQL_PASSWORD: bitter-macaron + networks: + - my-network + depends_on: + - mysql-job-dbdata + ports: + - "13306:3306" + volumes: + - ./docker/mysql/db:/docker-entrypoint-initdb.d + healthcheck: + test: [ "CMD", "mysqladmin" ,"ping", "-h", "localhost" ] + interval: 5s + timeout: 10s + retries: 10 + start_period: 15s + + # 自动加载数据 + mysql-job-dbdata: + image: alpine:3.18.2 + container_name: mysql-job-dbdata + volumes: + - /var/lib/mysql + + # phpmyadmin https://hub.docker.com/_/phpmyadmin + phpmyadmin: + image: phpmyadmin:5.2.1 + container_name: phpmyadmin + hostname: phpmyadmin + ports: + - "8899:80" + environment: + - PMA_HOST=mysql + - PMA_PORT=3306 + - MYSQL_ROOT_PASSWORD=achobeta666 + depends_on: + mysql: + condition: service_healthy + networks: + - my-network + + # Redis + redis: + image: redis:6.2 + container_name: redis + restart: always + hostname: redis + privileged: true + ports: + - "6379:6379" + volumes: + - ./docker/redis/conf/redis.conf:/usr/local/etc/redis/redis.conf + command: redis-server /usr/local/etc/redis/redis.conf + networks: + - my-network + healthcheck: + test: [ "CMD", "redis-cli", "ping" ] + interval: 10s + timeout: 5s + retries: 3 + + # RedisAdmin https://github.com/joeferner/redis-commander + # 账密 admin/admin + redis-admin: + image: spryker/redis-commander:0.8.0 + container_name: redis-admin + hostname: redis-commander + restart: always + ports: + - "8081:8081" + environment: + - REDIS_HOSTS=local:redis:6379 + - HTTP_USER=admin + - HTTP_PASSWORD=bitter-macaron + - LANG=C.UTF-8 + - LANGUAGE=C.UTF-8 + - LC_ALL=C.UTF-8 + networks: + - my-network + depends_on: + redis: + condition: service_healthy + + # rabbitmq + # 账密 admin/admin + # rabbitmq-plugins enable rabbitmq_management + rabbitmq: + image: rabbitmq:3.12.9 + container_name: rabbitmq + restart: always + ports: + - "5672:5672" + - "15672:15672" + environment: + RABBITMQ_DEFAULT_USER: itcast + RABBITMQ_DEFAULT_PASS: 123321 + command: rabbitmq-server + networks: + - my-network + + # minio + minio: + image: minio/minio:RELEASE.2024-09-13T20-26-02Z + container_name: minio + hostname: minio + privileged: true + restart: always + environment: + # 账号 + MINIO_ROOT_USER: mms + # 密码 + MINIO_ROOT_PASSWORD: bitter-macaron + ports: + - "19005:19005" + - "9005:9005" + volumes: + - ./docker/minio/data:/data + command: server /data --console-address ":19005" + networks: + - my-network + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:9005/minio/health/live"] + interval: 30s + timeout: 20s + retries: 3 + + ab-recruitment-app: + container_name: ab-recruitment-app build: - context: . - dockerfile: ./dockerfile #指定dockerFile文件 - image: java/achobeta-recruitment:1.0.0 # 指定镜像名 + context: ./ + dockerfile: Dockerfile + restart: always ports: - - "9001:9001" # 暴露端口 + - "9001:19001" + environment: + - TZ=PRC + - SERVER_PORT=9001 + - APP_CONFIG_API_VERSION=v1 + - APP_CONFIG_CROSS_ORIGIN=* + - SPRING_DATASOURCE_USERNAME=mms + - SPRING_DATASOURCE_PASSWORD=bitter-macaron + - SPRING_DATASOURCE_URL=jdbc:mysql://mysql:3306/achobeta_recruitment?serverTimezone=UTC&characterEncoding=utf8&autoReconnect=true&serverTimezone=Asia/Shanghai + - SPRING_DATASOURCE_DRIVER_CLASS_NAME=com.mysql.jdbc.Driver + - SPRING_HIKARI_POOL_NAME=Retail_HikariCP + - REDIS_SDK_CONFIG_HOST=redis + - REDIS_SDK_CONFIG_PORT=6379 volumes: - - ./logs:/logs # 创建容器数据卷 \ No newline at end of file + - ./log:/data/log + depends_on: + - redis + - rabbitmq + - mysql + - minio + links: + - redis + - rabbitmq + - mysql + - minio + networks: + - my-network + logging: + driver: "json-file" + options: + max-size: "10m" + max-file: "3" + +networks: + my-network: + driver: bridge \ No newline at end of file diff --git a/docker/mysql/Dockerfile b/docker/mysql/Dockerfile new file mode 100644 index 00000000..37d7b973 --- /dev/null +++ b/docker/mysql/Dockerfile @@ -0,0 +1,9 @@ +# 基础镜像 +FROM mysql:5.7 + +# author +MAINTAINER BanTanger + +# 执行sql脚本 +# `/docker-entrypoint-initdb.d/`是MySQL官方镜像中的一个特殊目录,用于存放初始化数据库的脚本文件。 +ADD ./db/*.sql /docker-entrypoint-initdb.d/ diff --git a/docker/mysql/db/recruitment-core.sql b/docker/mysql/db/recruitment-core.sql new file mode 100644 index 00000000..f40e7965 --- /dev/null +++ b/docker/mysql/db/recruitment-core.sql @@ -0,0 +1,562 @@ +SET character_set_client = utf8; +SET character_set_results = utf8; +SET character_set_connection = utf8; + +drop database if exists achobeta_recruitment; +create database achobeta_recruitment character set utf8mb4 collate utf8mb4_bin; +use achobeta_recruitment; + +drop table if exists `user`; +create table `user` +( + `id` bigint primary key auto_increment comment '用户唯一 id', + `username` varchar(50) not null default '' comment '用户名', + `nickname` varchar(50) not null default '' comment '用户昵称', + `email` varchar(50) not null default '' comment '邮箱', + `phone_number` varchar(11) not null default '' comment '手机号码', + `password` varchar(100) not null default '' comment '密码', + `user_type` int not null default 1 comment '用户类型:1.普通用户 2. 管理员', + `avatar` bigint comment '头像地址', + -- common column + `version` int not null default 0 comment '乐观锁', + `is_deleted` bit not null default b'0' comment '伪删除标记', + `create_time` datetime not null default current_timestamp comment '创建时间', + `update_time` datetime not null default current_timestamp on update current_timestamp comment '更新时间', + -- index + index `idx_email`(`email` asc) using btree, + index `idx_phone`(`phone_number` asc) using btree, + unique `uni_username`(`username` asc) using btree +) auto_increment = 10000 comment = '用户基本信息表'; + +-- root 管理员,username: root; password: AchoBeta666 +insert into user(`username`, `nickname`, `password`, `user_type`) + values('root', 'root', '$2a$10$YPKp0kzLjnNrW5CgKuDdiuF4tZO0KXacmhy2KT7N9Zey49Cmi/rfu', 2); + +drop table if exists `member`; +create table `member` +( + `id` bigint primary key auto_increment comment '正式成员 id', + `resume_id` bigint not null comment '简历 id', + `manager_id` bigint unique not null comment '新开的管理员账号 id', + `parent_id` bigint not null comment '父管理员 id', + -- common column + `version` int not null default 0 comment '乐观锁', + `is_deleted` bit not null default b'0' comment '伪删除标记', + `create_time` datetime not null default current_timestamp comment '创建时间', + `update_time` datetime not null default current_timestamp on update current_timestamp comment '更新时间', + -- index + index `idx_resume_id`(`resume_id` asc) using btree, + unique index `uni_manager_id`(`manager_id` asc) using btree, + index `idx_parent_id`(`parent_id` asc) using btree +) comment '正式成员表'; + +drop table if exists `user_feedback`; +create table `user_feedback` +( + `id` bigint primary key auto_increment comment '反馈 id' , + `user_id` bigint not null comment '用户id', + `batch_id` bigint not null comment '招新批次 id', + `message_id` bigint default null comment '处理结果的消息 id', + `title` varchar(256) default '' not null comment '反馈标题', + `content` text not null comment '反馈内容', + `attachment` bigint default null comment '附件链接', + `feedback_time` datetime default CURRENT_TIMESTAMP not null comment '反馈时间', + `is_handle` bit default b'0' not null comment '是否处理标记', + -- common column + `version` int not null default 0 comment '乐观锁', + `is_deleted` bit not null default b'0' comment '伪删除标记', + `create_time` datetime not null default current_timestamp comment '创建时间', + `update_time` datetime not null default current_timestamp on update current_timestamp comment '更新时间', + -- index + index `idx_user_id`(`user_id` asc) using btree, + index `idx_batch_id`(`batch_id` asc) using btree, + index `idx_message_id`(`message_id` asc) using btree +) comment = '用户反馈表'; + +drop table if exists `interview`; +create table `interview` +( + `id` bigint primary key auto_increment comment '面试 id', + `schedule_id` bigint unsigned not null comment '面试预约 id', + `paper_id` bigint default null comment '面试试卷 id', + `title` varchar(100) not null default '' comment '面试主题', + `description` text not null comment '面试说明', + `status` tinyint not null default 0 comment '是否开始(0未开始、1进行中、2已结束)', + `address` varchar(500) not null default '' comment '线下面试地址/显示面试会议链接', + -- common column + `version` int not null default 0 comment '乐观锁', + `is_deleted` bit not null default b'0' comment '伪删除标记', + `create_time` datetime not null default current_timestamp comment '创建时间', + `update_time` datetime not null default current_timestamp on update current_timestamp comment '更新时间', + -- index + index `idx_schedule_id`(`schedule_id` asc) using btree, + index `idx_paper_id`(`paper_id` asc) using btree +) comment '面试表'; + +drop table if exists `interview_comment`; +create table `interview_comment` +( + `id` bigint primary key auto_increment comment '面评 id', + `interview_id` bigint not null comment '面试 id', + `manager_id` bigint not null comment '管理员 id', + `content` text not null comment '评论内容', + -- common column + `version` int not null default 0 comment '乐观锁', + `is_deleted` bit not null default b'0' comment '伪删除标记', + `create_time` datetime not null default current_timestamp comment '创建时间', + `update_time` datetime not null default current_timestamp on update current_timestamp comment '更新时间', + -- index + index `idx_interview_id`(`interview_id` asc) using btree, + index `idx_manager_id`(`manager_id` asc) using btree +) comment '面试评论表'; + +drop table if exists `interview_question_score`; +create table `interview_question_score` +( + `id` bigint primary key auto_increment comment '面试题评分 id', + `interview_id` bigint not null comment '面试 id', + `question_id` bigint not null comment '问题 id', + `score` int not null comment '评分(0-10,-1为超纲)', + -- common column + `version` int not null default 0 comment '乐观锁', + `is_deleted` bit not null default b'0' comment '伪删除标记', + `create_time` datetime not null default current_timestamp comment '创建时间', + `update_time` datetime not null default current_timestamp on update current_timestamp comment '更新时间', + -- index + index `idx_interview_id`(`interview_id` asc) using btree, + index `idx_question_id`(`question_id` asc) using btree +) comment '面试题评分关联表'; + +drop table if exists `interview_schedule`; +create table `interview_schedule` +( + `id` bigint primary key auto_increment comment '面试预约 id', + `participation_id` bigint not null comment '用户的“活动参与” id', + `start_time` datetime not null comment '预约开始时间', + `end_time` datetime not null comment '预约结束时间', + -- common column + `version` int not null default 0 comment '乐观锁', + `is_deleted` bit not null default b'0' comment '伪删除标记', + `create_time` datetime not null default current_timestamp comment '创建时间', + `update_time` datetime not null default current_timestamp on update current_timestamp comment '更新时间', + -- index + index `idx_participation_id`(`participation_id` asc) using btree +) comment '面试预约表'; + +drop table if exists `interview_summary`; +create table `interview_summary` +( + `id` bigint primary key auto_increment comment '面试总结 id', + `interview_id` bigint not null comment '面试 id', + `basis` tinyint not null default 0 comment '基础理论知识掌握(0-5)', + `coding` tinyint not null default 0 comment '代码能力(0-5)', + `thinking` tinyint not null default 0 comment '思维能力(0-5)', + `express` tinyint not null default 0 comment '表达能力(0-5)', + `evaluate` varchar(500) not null default '' comment '面试总评', + `suggest` varchar(500) not null default '' comment '复习建议', + `playback` varchar(256) not null default '' comment '面试回放链接', + -- common column + `version` int not null default 0 comment '乐观锁', + `is_deleted` bit not null default b'0' comment '伪删除标记', + `create_time` datetime not null default current_timestamp comment '创建时间', + `update_time` datetime not null default current_timestamp on update current_timestamp comment '更新时间', + -- index + index `idx_interview_id`(`interview_id` asc) using btree +) comment '面试总结表'; + +drop table if exists `interviewer`; +create table `interviewer` +( + `id` bigint primary key auto_increment comment '面试官 id', + `manager_id` bigint not null comment '管理员 id', + `schedule_id` bigint not null comment '面试预约 id', + -- common column + `version` int not null default 0 comment '乐观锁', + `is_deleted` bit not null default b'0' comment '伪删除标记', + `create_time` datetime not null default current_timestamp comment '创建时间', + `update_time` datetime not null default current_timestamp on update current_timestamp comment '更新时间', + -- index + index `idx_manager_id`(`manager_id` asc) using btree, + index `idx_schedule_id`(`schedule_id` asc) using btree +) comment '面试官表'; + +drop table if exists `message`; +create table `message` +( + `id` bigint primary key auto_increment comment '消息 id', + `manager_id` bigint not null comment '发送消息的管理员 id', + `user_id` bigint not null comment '用户 id', + `tittle` varchar(256) not null default '' comment '消息标题', + `content` text not null comment '消息内容', + `send_time` datetime not null default current_timestamp comment '发送时间', + `attachment` bigint null default null comment '附件 url', + -- common column + `version` int not null default 0 comment '乐观锁', + `is_deleted` bit not null default b'0' comment '伪删除标记', + `create_time` datetime not null default current_timestamp comment '创建时间', + `update_time` datetime not null default current_timestamp on update current_timestamp comment '更新时间', + -- index + index `idx_manager_id`(`manager_id` asc) using btree, + index `idx_user_id`(`user_id` asc) using btree +) comment = '消息表'; + +drop table if exists `message_template`; +create table `message_template` +( + `id` bigint primary key auto_increment comment '模板消息 id', + `template_title` varchar(100) not null default '' comment '模板消息标题', + `template_content` text not null comment '模板消息内容', + -- common column + `version` int not null default 0 comment '乐观锁', + `is_deleted` bit not null default b'0' comment '伪删除标记', + `create_time` datetime not null default current_timestamp comment '创建时间', + `update_time` datetime not null default current_timestamp on update current_timestamp comment '更新时间' +) comment = '模板消息表'; + +drop table if exists `library_paper_link`; +create table `library_paper_link` +( + `id` bigint primary key auto_increment comment 'id', + `lib_id` bigint not null comment '试卷库 id', + `paper_id` bigint not null comment '试卷 id', + -- common column + `version` int not null default 0 comment '乐观锁', + `is_deleted` bit not null default b'0' comment '伪删除标记', + `create_time` datetime not null default current_timestamp comment '创建时间', + `update_time` datetime not null default current_timestamp on update current_timestamp comment '更新时间', + -- index + index `idx_lp_id`(`lib_id` asc, `paper_id`) using btree +) comment '试卷库-试卷关联表'; + +drop table if exists `paper_question_link`; +create table `paper_question_link` +( + `id` bigint primary key auto_increment comment 'id', + `paper_id` bigint not null comment '试卷 id', + `question_id` bigint not null comment '问题 id', + -- common column + `version` int not null default 0 comment '乐观锁', + `is_deleted` bit not null default b'0' comment '伪删除标记', + `create_time` datetime not null default current_timestamp comment '创建时间', + `update_time` datetime not null default current_timestamp on update current_timestamp comment '更新时间', + -- index + index idx_pq_id(`paper_id` asc, `question_id`) using btree +) comment '试卷-问题关联表'; + +drop table if exists `question_paper`; +create table `question_paper` +( + `id` bigint primary key auto_increment comment '试卷 id', + `title` varchar(100) not null default '' comment '试卷标题', + `description` text not null comment '试卷说明', + -- common column + `version` int not null default 0 comment '乐观锁', + `is_deleted` bit not null default b'0' comment '伪删除标记', + `create_time` datetime not null default current_timestamp comment '创建时间', + `update_time` datetime not null default current_timestamp on update current_timestamp comment '更新时间' +) comment '试卷表'; + +drop table if exists `question_paper_library`; +create table `question_paper_library` +( + `id` bigint primary key auto_increment comment '试卷库 id', + `lib_type` varchar(100) not null default '' comment '试卷库的类别,例如技术类的后端试卷,前端试卷;非技术的信息收集试卷;筛选的笔试试卷...', + -- common column + `version` int not null default 0 comment '乐观锁', + `is_deleted` bit not null default b'0' comment '伪删除标记', + `create_time` datetime not null default current_timestamp comment '创建时间', + `update_time` datetime not null default current_timestamp on update current_timestamp comment '更新时间', + -- index + index `idx_lib_type`(`lib_type` asc) using btree +) comment '试卷库表'; + +drop table if exists `library_question_link`; +create table `library_question_link` +( + `id` bigint primary key auto_increment comment 'id', + `lib_id` bigint not null comment '题库 id', + `question_id` bigint not null comment '问题 id', + -- common column + `version` int not null default 0 comment '乐观锁', + `is_deleted` bit not null default b'0' comment '伪删除标记', + `create_time` datetime not null default current_timestamp comment '创建时间', + `update_time` datetime not null default current_timestamp on update current_timestamp comment '更新时间', + -- index + index `idx_lq_id`(`lib_id` asc, `question_id`) using btree +) comment '题库-问题关联表'; + +drop table if exists `question`; +create table `question` +( + `id` bigint primary key auto_increment comment '问题 id', + `title` varchar(2048) not null default '' comment '问题标题', + `standard` text not null comment '问题标答', + -- common column + `version` int not null default 0 comment '乐观锁', + `is_deleted` bit not null default b'0' comment '伪删除标记', + `create_time` datetime not null default current_timestamp comment '创建时间', + `update_time` datetime not null default current_timestamp on update current_timestamp comment '更新时间' +) comment '问题表'; + +drop table if exists `question_library`; +create table `question_library` +( + `id` bigint primary key auto_increment comment '题库 id', + `lib_type` varchar(100) not null default '' comment '题库的类别', + -- common column + `version` int not null default 0 comment '乐观锁', + `is_deleted` bit not null default b'0' comment '伪删除标记', + `create_time` datetime not null default current_timestamp comment '创建时间', + `update_time` datetime not null default current_timestamp on update current_timestamp comment '更新时间', + -- index + index `idx_lib_type`(`lib_type` asc) using btree +) comment '题库表'; + +drop table if exists `activity_participation`; +create table `activity_participation` +( + `id` bigint primary key auto_increment comment '“活动参与” id', + `stu_id` bigint not null comment '学生 id', + `act_id` bigint not null comment '活动 id', + -- common column + `version` int not null default 0 comment '乐观锁', + `is_deleted` bit not null default b'0' comment '伪删除标记', + `create_time` datetime not null default current_timestamp comment '创建时间', + `update_time` datetime not null default current_timestamp on update current_timestamp comment '更新时间', + -- index + index `idx_stu_id`(`stu_id` asc) using btree, + index `idx_act_id`(`act_id` asc) using btree +) comment '“活动参与”表'; + +drop table if exists `participation_period_link`; +create table `participation_period_link` +( + `id` bigint primary key auto_increment comment 'id', + `participation_id` bigint not null comment '“活动参与” id', + `period_id` bigint not null comment '时间段 id', + -- common column + `version` int not null default 0 comment '乐观锁', + `is_deleted` bit not null default b'0' comment '伪删除标记', + `create_time` datetime not null default current_timestamp comment '创建时间', + `update_time` datetime not null default current_timestamp on update current_timestamp comment '更新时间', + -- index + index `idx_pp_id`(`participation_id` asc, `period_id` asc) using btree +) comment '“活动参与”-时间段关联表'; + +drop table if exists `participation_question_link`; +create table `participation_question_link` +( + `id` bigint primary key auto_increment comment 'id', + `participation_id` bigint not null comment '“活动参与” id', + `question_id` bigint not null comment '问题 id', + `answer` text not null comment '回答', + -- common column + `version` int not null default 0 comment '乐观锁', + `is_deleted` bit not null default b'0' comment '伪删除标记', + `create_time` datetime not null default current_timestamp comment '创建时间', + `update_time` datetime not null default current_timestamp on update current_timestamp comment '更新时间', + -- index + index `idx_pq_id`(`participation_id` asc, `question_id` asc) using btree +) comment '“活动参与”-问题关联表'; + +drop table if exists `recruitment_activity`; +create table `recruitment_activity` +( + `id` bigint primary key auto_increment comment '招新活动 id', + `batch_id` bigint not null comment '招新批次 id', + `paper_id` bigint default null comment '试卷 id', + `title` varchar(100) not null default '' comment '活动标题', + `target` json not null comment '面向的人群', + `description` text not null comment '活动说明', + `deadline` datetime not null comment '截止时间', + `is_run` bit not null default b'0' comment '是否启动', + -- common column + `version` int not null default 0 comment '乐观锁', + `is_deleted` bit not null default b'0' comment '伪删除标记', + `create_time` datetime not null default current_timestamp comment '创建时间', + `update_time` datetime not null default current_timestamp on update current_timestamp comment '更新时间', + -- index + index `idx_batch_id`(`batch_id` asc) using btree, + index `idx_paper_id`(`paper_id` asc) using btree +) comment '招新活动表'; + +drop table if exists `recruitment_batch`; +create table `recruitment_batch` +( + `id` bigint primary key auto_increment comment '招新批次 id', + `batch` int not null comment 'AchoBeta 届数', + `title` varchar(100) not null default '' comment '招新标题', + `deadline` datetime not null comment '截止时间', + `is_run` bit not null default b'0' comment '是否启动', + -- common column + `version` int not null default 0 comment '乐观锁', + `is_deleted` bit not null default b'0' comment '伪删除标记', + `create_time` datetime not null default current_timestamp comment '创建时间', + `update_time` datetime not null default current_timestamp on update current_timestamp comment '更新时间', + -- index + index `idx_batch`(`batch` asc) using btree +) comment '招新批次表'; + +drop table if exists `time_period`; +create table `time_period` +( + `id` bigint primary key auto_increment comment '时间段 id', + `act_id` bigint not null comment '招新活动 id', + `start_time` datetime not null comment '开始时间', + `end_time` datetime not null comment '结束时间', + -- common column + `version` int not null default 0 comment '乐观锁', + `is_deleted` bit not null default b'0' comment '伪删除标记', + `create_time` datetime not null default current_timestamp comment '创建时间', + `update_time` datetime not null default current_timestamp on update current_timestamp comment '更新时间', + -- index + index `idx_act_id`(`act_id` asc) using btree +) comment '时间段表'; + +drop table if exists `digital_resource`; +create table `digital_resource` +( + `id` bigint primary key auto_increment comment '资源 id', + `code` bigint unique not null comment '资源码', + `user_id` bigint not null comment '上传文件的用户 id', + `access_level` int not null default 2 comment '访问权限', + `original_name` varchar(100) not null comment '上传时的文件名', + `file_name` varchar(256) not null comment '在对象存储服务中存储的对象名', + -- common column + `version` int not null default 0 comment '乐观锁', + `is_deleted` bit not null default b'0' comment '伪删除标记', + `create_time` datetime not null default current_timestamp comment '创建时间', + `update_time` datetime not null default current_timestamp on update current_timestamp comment '更新时间', + -- index + unique index `uni_code`(`code` asc) using btree, + index `idx_user_id`(`user_id` asc) using btree +) comment '资源表'; + +drop table if exists `feishu_resource`; +create table `feishu_resource` +( + `id` bigint primary key auto_increment comment '飞书资源 id', + `ticket` varchar(50) unique not null comment '任务 ID', + `original_name` varchar(100) not null comment '上传时的文件名', + `token` varchar(50) not null default '' comment '导入云文档的 token', + `type` varchar(20) not null default '' comment '导入的在线云文档类型', + `url` varchar(200) not null default '' comment '导入云文档的 URL', + -- common column + `version` int not null default 0 comment '乐观锁', + `is_deleted` bit not null default b'0' comment '伪删除标记', + `create_time` datetime not null default current_timestamp comment '创建时间', + `update_time` datetime not null default current_timestamp on update current_timestamp comment '更新时间', + -- index + unique index `uni_ticket`(`ticket`) using btree +) comment '飞书资源表'; + +drop table if exists `short_link`; +create table `short_link` +( + `id` bigint primary key auto_increment comment '短链接编号', + `origin_url` varchar(512) default '' comment '原链接', + `short_code` char(6) unique default '' comment '短链code', + `is_used` bit default b'0' comment '是否使用过', + -- common column + `version` int not null default 0 comment '乐观锁', + `is_deleted` bit not null default b'0' comment '伪删除标记', + `create_time` datetime not null default current_timestamp comment '创建时间', + `update_time` datetime not null default current_timestamp on update current_timestamp comment '更新时间', + -- 索引 + index `idx_short_code`(`short_code` asc) using btree +) comment '长短链关系表'; + +drop table if exists `resume_status_process`; +create table `resume_status_process` +( + `id` bigint primary key auto_increment comment 'id', + `resume_id` bigint not null comment '简历 id', + `resume_status` int not null comment '简历状态', + `resume_event` int not null comment '简历事件', + -- common column + `version` int not null default 0 comment '乐观锁', + `is_deleted` bit not null default b'0' comment '伪删除标记', + `create_time` datetime not null default current_timestamp comment '创建时间', + `update_time` datetime not null default current_timestamp on update current_timestamp comment '更新时间', + -- 索引 + index `idx_resume_id`(`resume_id` asc) using btree +) comment '招新简历状态过程表'; + +drop table if exists `stu_attachment`; +create table `stu_attachment` +( + `id` bigint primary key auto_increment comment 'id', + `resume_id` bigint not null comment '学生表主键 id', + `filename` varchar(256) not null default '' comment '附件名', + `attachment` bigint not null comment '附件资源码', + -- common column + `version` int not null default 0 comment '乐观锁', + `is_deleted` bit not null default b'0' comment '伪删除标记', + `create_time` datetime not null default current_timestamp comment '创建时间', + `update_time` datetime not null default current_timestamp on update current_timestamp comment '更新时间', + -- index + index `idx_resume_id`(`resume_id` asc) using btree +) comment = '学生附件表'; + +-- 简历状态 comment 说明 +-- 范围:0~16,简历状态{ +-- 0-草稿 +-- 1-待筛选 +-- 2-筛选不通过 +-- +-- 3-待安排初试 +-- 4-待初试 +-- 5-初试通过(仅当初试为最后一个流程时显示) +-- 6-初试不通过(仅当初试为最后一个流程时显示) +-- +-- 7-待安排复试 +-- 8-待复试 +-- 9-复试通过(仅当复试为最后一个流程时显示) +-- 10-复试不通过(仅当复试为最后一个流程时显示) +-- +-- 11-待安排终试 +-- 12-待终试 +-- 13-终试通过(仅当复试为最后一个流程时显示) +-- 14-终试不通过(仅当复试为最后一个流程时显示) +-- +-- 15-待处理(反馈异常/或管理员主动设置为该状态) +-- 16-挂起(管理员可以主动设置该状态) +-- } +-- ---------------------------- +-- 创建学生简历表 +drop table if exists `stu_resume`; +create table `stu_resume` +( + `id` bigint primary key auto_increment comment 'id', + `user_id` bigint not null comment '用户 id', + `batch_id` bigint not null comment '招新批次 id', + `student_id` varchar(13) not null default '' comment '学号', + `name` varchar(10) not null default '' comment '姓名', + `gender` tinyint not null default 0 comment '性别', + `grade` int not null comment '年级', + `major` varchar(20) not null default '' comment '专业', + `class` varchar(30) not null default '' comment '班级', + `email` varchar(50) not null default '' comment '邮箱', + `phone_number` varchar(11) not null default '' comment '手机号码', + `reason` text not null comment '加入 achobeta 的理由', + `introduce` text not null comment '个人介绍(自我认知)', + `experience` text not null comment '个人经历(项目经历、职业规划等)', + `awards` text not null comment '获奖经历', + `image` bigint not null comment '照片', + `remark` varchar(500) not null default '' comment '备注', + `status` int not null default 1 comment '简历状态,范围:0~16', + `submit_count` int not null default 0 comment '提交次数', + -- common column + `version` int not null default 0 comment '乐观锁', + `is_deleted` bit not null default b'0' comment '伪删除标记', + `create_time` datetime not null default current_timestamp comment '创建时间', + `update_time` datetime not null default current_timestamp on update current_timestamp comment '更新时间', + -- index + index `idx_student_id` (`student_id` asc) using btree, + index `idx_email` (`email` asc) using btree, + index `idx_user_id` (`user_id` asc) using btree, + index `idx_batch_id` (`batch_id` asc) using btree, + index `idx_class` (`class` asc) using btree, + index `idx_major` (`major` asc) using btree, + index `idx_name` (`name` asc) using btree +) comment = '学生简历表'; diff --git a/docker/redis/Dockerfile b/docker/redis/Dockerfile new file mode 100644 index 00000000..845d6315 --- /dev/null +++ b/docker/redis/Dockerfile @@ -0,0 +1,14 @@ +# 基础镜像 +FROM redis:6.0.8 +# author +MAINTAINER BanTanger + +EXPOSE 6379 +# 挂载目录 +VOLUME /home/order/redis +# 创建目录 +RUN mkdir -p /home/order/redis +# 指定路径 +WORKDIR /home/order/redis +# 复制conf文件到路径 +COPY ./conf/redis.conf /home/order/redis/redis.conf diff --git a/docker/redis/conf/redis.conf b/docker/redis/conf/redis.conf new file mode 100644 index 00000000..8fe64b4e --- /dev/null +++ b/docker/redis/conf/redis.conf @@ -0,0 +1,5 @@ +bind 0.0.0.0 +requirepass bitter-macaron +protected-mode no +appendonly yes +daemonize no diff --git a/dockerfile b/dockerfile deleted file mode 100644 index 433514a0..00000000 --- a/dockerfile +++ /dev/null @@ -1,10 +0,0 @@ -# 以jdk8为基础镜像 -FROM openjdk:21 -# 描述 -LABEL description="AchoBeta Recruitment" -# 暴露接口 -EXPOSE 9001 -# 将主机中的jar包添加到镜像中 -ADD target/AchoBeta-Recruitment-1.0.jar AchoBeta-Recruitment-1.0.jar -# 运行jar包 -ENTRYPOINT ["java", "-jar","AchoBeta-Recruitment-1.0.jar"] \ No newline at end of file diff --git a/src/main/java/com/achobeta/common/annotation/IsAccessible.java b/src/main/java/com/achobeta/common/annotation/Accessible.java similarity index 80% rename from src/main/java/com/achobeta/common/annotation/IsAccessible.java rename to src/main/java/com/achobeta/common/annotation/Accessible.java index c8321048..cd3a03a7 100644 --- a/src/main/java/com/achobeta/common/annotation/IsAccessible.java +++ b/src/main/java/com/achobeta/common/annotation/Accessible.java @@ -1,6 +1,6 @@ package com.achobeta.common.annotation; -import com.achobeta.common.annotation.handler.IsAccessibleValidator; +import com.achobeta.common.annotation.handler.AccessibleValidator; import jakarta.validation.Constraint; import jakarta.validation.Payload; @@ -14,10 +14,10 @@ * Time: 23:50 */ @Documented -@Constraint(validatedBy = {IsAccessibleValidator.class}) +@Constraint(validatedBy = {AccessibleValidator.class}) @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE}) @Retention(RetentionPolicy.RUNTIME) -public @interface IsAccessible { +public @interface Accessible { String message() default "url 无法访问"; // 默认消息 diff --git a/src/main/java/com/achobeta/common/annotation/HttpUrl.java b/src/main/java/com/achobeta/common/annotation/HttpUrl.java new file mode 100644 index 00000000..036e06a2 --- /dev/null +++ b/src/main/java/com/achobeta/common/annotation/HttpUrl.java @@ -0,0 +1,27 @@ +package com.achobeta.common.annotation; + +import com.achobeta.common.annotation.handler.HttpUrlValidator; +import jakarta.validation.Constraint; +import jakarta.validation.Payload; + +import java.lang.annotation.*; + +/** + * Created With Intellij IDEA + * Description: + * User: 马拉圈 + * Date: 2024-10-19 + * Time: 11:35 + */ +@Documented +@Constraint(validatedBy = {HttpUrlValidator.class}) +@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface HttpUrl { + + String message() default "链接非法"; // 默认消息 + + Class[] groups() default {}; // 分组校验 + + Class[] payload() default {}; // 负载信息 +} diff --git a/src/main/java/com/achobeta/common/annotation/Intercept.java b/src/main/java/com/achobeta/common/annotation/Intercept.java index 0b666922..5b367db9 100644 --- a/src/main/java/com/achobeta/common/annotation/Intercept.java +++ b/src/main/java/com/achobeta/common/annotation/Intercept.java @@ -27,6 +27,4 @@ boolean ignore() default false; - boolean log() default false; - } diff --git a/src/main/java/com/achobeta/common/annotation/MobilePhone.java b/src/main/java/com/achobeta/common/annotation/MobilePhone.java new file mode 100644 index 00000000..5cf8ac13 --- /dev/null +++ b/src/main/java/com/achobeta/common/annotation/MobilePhone.java @@ -0,0 +1,27 @@ +package com.achobeta.common.annotation; + +import com.achobeta.common.annotation.handler.MobilePhoneValidator; +import jakarta.validation.Constraint; +import jakarta.validation.Payload; + +import java.lang.annotation.*; + +/** + * Created With Intellij IDEA + * Description: + * User: 马拉圈 + * Date: 2024-10-17 + * Time: 21:59 + */ +@Documented +@Constraint(validatedBy = {MobilePhoneValidator.class}) +@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface MobilePhone { + + String message() default "手机号非法"; // 默认消息 + + Class[] groups() default {}; // 分组校验 + + Class[] payload() default {}; // 负载信息 +} diff --git a/src/main/java/com/achobeta/common/annotation/handler/IsAccessibleValidator.java b/src/main/java/com/achobeta/common/annotation/handler/AccessibleValidator.java similarity index 72% rename from src/main/java/com/achobeta/common/annotation/handler/IsAccessibleValidator.java rename to src/main/java/com/achobeta/common/annotation/handler/AccessibleValidator.java index 485a4f68..7575e732 100644 --- a/src/main/java/com/achobeta/common/annotation/handler/IsAccessibleValidator.java +++ b/src/main/java/com/achobeta/common/annotation/handler/AccessibleValidator.java @@ -1,16 +1,15 @@ package com.achobeta.common.annotation.handler; -import com.achobeta.common.annotation.IsAccessible; +import com.achobeta.common.annotation.Accessible; import com.achobeta.util.HttpRequestUtil; import jakarta.validation.ConstraintValidator; import jakarta.validation.ConstraintValidatorContext; import lombok.extern.slf4j.Slf4j; -import java.io.IOException; import java.util.Optional; @Slf4j -public class IsAccessibleValidator implements ConstraintValidator { +public class AccessibleValidator implements ConstraintValidator { @Override public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) { @@ -19,8 +18,7 @@ public boolean isValid(String s, ConstraintValidatorContext constraintValidatorC .map(url -> { try { return HttpRequestUtil.isAccessible(url); - } catch (IOException e) { - // 重定向次数过多也判定为无法访问 + } catch (Exception e) { log.warn(e.getMessage()); return Boolean.FALSE; } diff --git a/src/main/java/com/achobeta/common/annotation/handler/HttpUrlValidator.java b/src/main/java/com/achobeta/common/annotation/handler/HttpUrlValidator.java new file mode 100644 index 00000000..e5f10de3 --- /dev/null +++ b/src/main/java/com/achobeta/common/annotation/handler/HttpUrlValidator.java @@ -0,0 +1,25 @@ +package com.achobeta.common.annotation.handler; + +import com.achobeta.common.annotation.HttpUrl; +import com.achobeta.util.HttpRequestUtil; +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; + +import java.util.Optional; + +/** + * Created With Intellij IDEA + * Description: + * User: 马拉圈 + * Date: 2024-10-19 + * Time: 11:35 + */ +public class HttpUrlValidator implements ConstraintValidator { + + @Override + public boolean isValid(String url, ConstraintValidatorContext constraintValidatorContext) { + return Optional.ofNullable(url) + .map(s -> HttpRequestUtil.HTTP_URL_PATTERN.matcher(s).matches()) + .orElse(Boolean.TRUE); + } +} diff --git a/src/main/java/com/achobeta/common/annotation/handler/InterceptHelper.java b/src/main/java/com/achobeta/common/annotation/handler/InterceptHelper.java index a2288b90..f2a47cb5 100644 --- a/src/main/java/com/achobeta/common/annotation/handler/InterceptHelper.java +++ b/src/main/java/com/achobeta/common/annotation/handler/InterceptHelper.java @@ -39,14 +39,6 @@ public static boolean isIgnore(Method targetMethod) { return isIgnore(getIntercept(targetMethod)); } - public static boolean shouldPrintLog(Intercept intercept) { - return Objects.isNull(intercept) || intercept.log(); - } - - public static boolean shouldPrintLog(Method targetMethod) { - return shouldPrintLog(getIntercept(targetMethod)); - } - public static boolean isValid(Intercept intercept, UserTypeEnum role) { // permit 中没有 role 就会抛异常 return Arrays.stream(intercept.permit()) diff --git a/src/main/java/com/achobeta/common/annotation/handler/MobilePhoneValidator.java b/src/main/java/com/achobeta/common/annotation/handler/MobilePhoneValidator.java new file mode 100644 index 00000000..94bd15f5 --- /dev/null +++ b/src/main/java/com/achobeta/common/annotation/handler/MobilePhoneValidator.java @@ -0,0 +1,28 @@ +package com.achobeta.common.annotation.handler; + +import com.achobeta.common.annotation.MobilePhone; +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; + +import java.util.Optional; +import java.util.regex.Pattern; + +/** + * Created With Intellij IDEA + * Description: + * User: 马拉圈 + * Date: 2024-10-17 + * Time: 22:00 + */ +public class MobilePhoneValidator implements ConstraintValidator { + + private final static Pattern MOBILE_PHONE_PATTERN = Pattern.compile("^1[3-9]\\d{9}$"); + + @Override + public boolean isValid(String phone, ConstraintValidatorContext constraintValidatorContext) { + return Optional.ofNullable(phone) + .map(s -> MOBILE_PHONE_PATTERN.matcher(s).matches()) + .orElse(Boolean.TRUE); + } + +} diff --git a/src/main/java/com/achobeta/domain/evaluate/handler/ext/RemovePaperIQScoreHandler.java b/src/main/java/com/achobeta/domain/evaluate/handler/ext/RemovePaperIQScoreHandler.java index 2255eccc..94360315 100644 --- a/src/main/java/com/achobeta/domain/evaluate/handler/ext/RemovePaperIQScoreHandler.java +++ b/src/main/java/com/achobeta/domain/evaluate/handler/ext/RemovePaperIQScoreHandler.java @@ -34,18 +34,17 @@ public void handle(Long paperId) { .stream() .map(Interview::getId) .toList(); - if(CollectionUtils.isEmpty(interviewIds)) { - return; + if(!CollectionUtils.isEmpty(interviewIds)) { + // 将面试试卷设置为空 + interviewService.lambdaUpdate() + .in(Interview::getId, interviewIds) + .set(Interview::getPaperId, null) + .update(); + // 删除对应的评分 + interviewQuestionScoreService.lambdaUpdate() + .in(InterviewQuestionScore::getInterviewId, interviewIds) + .remove(); } - // 将面试试卷设置为空 - interviewService.lambdaUpdate() - .in(Interview::getId, interviewIds) - .set(Interview::getPaperId, null) - .update(); - // 删除对应的评分 - interviewQuestionScoreService.lambdaUpdate() - .in(InterviewQuestionScore::getInterviewId, interviewIds) - .remove(); // 执行下一个 super.doNextHandler(paperId); } diff --git a/src/main/java/com/achobeta/domain/evaluate/handler/ext/RemoveQuestionFromPaperIQSHandler.java b/src/main/java/com/achobeta/domain/evaluate/handler/ext/RemoveQuestionFromPaperIQSHandler.java index 85502700..7e0590e4 100644 --- a/src/main/java/com/achobeta/domain/evaluate/handler/ext/RemoveQuestionFromPaperIQSHandler.java +++ b/src/main/java/com/achobeta/domain/evaluate/handler/ext/RemoveQuestionFromPaperIQSHandler.java @@ -7,6 +7,7 @@ import com.achobeta.domain.paper.handler.RemoveQuestionFromPaperHandler; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; import java.util.List; @@ -34,10 +35,12 @@ public void handle(Long paperId, List questionIds) { .map(Interview::getId) .toList(); // 删除对应的评分 - interviewQuestionScoreService.lambdaUpdate() - .in(InterviewQuestionScore::getInterviewId, interviewIds) - .in(InterviewQuestionScore::getQuestionId, questionIds) - .remove(); + if(!CollectionUtils.isEmpty(interviewIds) && !CollectionUtils.isEmpty(questionIds)) { + interviewQuestionScoreService.lambdaUpdate() + .in(InterviewQuestionScore::getInterviewId, interviewIds) + .in(InterviewQuestionScore::getQuestionId, questionIds) + .remove(); + } // 执行下一个 super.doNextHandler(paperId, questionIds); } diff --git a/src/main/java/com/achobeta/domain/evaluate/machine/events/internal/InterviewExperienceHelper.java b/src/main/java/com/achobeta/domain/evaluate/machine/events/internal/InterviewExperienceHelper.java index 0812f0ba..bf9beb24 100644 --- a/src/main/java/com/achobeta/domain/evaluate/machine/events/internal/InterviewExperienceHelper.java +++ b/src/main/java/com/achobeta/domain/evaluate/machine/events/internal/InterviewExperienceHelper.java @@ -91,7 +91,6 @@ public Action getPerformActio InterviewExperienceTemplateInner inner = InterviewExperienceTemplateInner.builder() .title(question.getTitle()) .score(question.getScore()) - .average(question.getAverage()) .standard(target) .build(); replaceResourceList.add(new ReplaceResource(target, question.getStandard())); diff --git a/src/main/java/com/achobeta/domain/evaluate/model/dto/InterviewSummaryDTO.java b/src/main/java/com/achobeta/domain/evaluate/model/dto/InterviewSummaryDTO.java index c2793e2f..083f6dd8 100644 --- a/src/main/java/com/achobeta/domain/evaluate/model/dto/InterviewSummaryDTO.java +++ b/src/main/java/com/achobeta/domain/evaluate/model/dto/InterviewSummaryDTO.java @@ -1,7 +1,7 @@ package com.achobeta.domain.evaluate.model.dto; +import com.achobeta.common.annotation.Accessible; import com.achobeta.common.annotation.IntRange; -import com.achobeta.common.annotation.IsAccessible; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import lombok.Data; @@ -44,7 +44,7 @@ public class InterviewSummaryDTO { private String suggest; // @NotBlank(message = "回放不能为空") - @IsAccessible(message = "回放链接不可访问") + @Accessible private String playback; } diff --git a/src/main/java/com/achobeta/domain/evaluate/model/vo/InterviewExperienceTemplateInner.java b/src/main/java/com/achobeta/domain/evaluate/model/vo/InterviewExperienceTemplateInner.java index 08903b4f..19945649 100644 --- a/src/main/java/com/achobeta/domain/evaluate/model/vo/InterviewExperienceTemplateInner.java +++ b/src/main/java/com/achobeta/domain/evaluate/model/vo/InterviewExperienceTemplateInner.java @@ -22,8 +22,6 @@ public class InterviewExperienceTemplateInner { private Integer score; - private Double average; - private String standard; } diff --git a/src/main/java/com/achobeta/domain/evaluate/model/vo/InterviewRankVO.java b/src/main/java/com/achobeta/domain/evaluate/model/vo/InterviewRankVO.java index f7a9f8bc..6975f419 100644 --- a/src/main/java/com/achobeta/domain/evaluate/model/vo/InterviewRankVO.java +++ b/src/main/java/com/achobeta/domain/evaluate/model/vo/InterviewRankVO.java @@ -14,16 +14,16 @@ @Data public class InterviewRankVO { - private Long summaryId; - - private Integer average; - private Long interviewId; private String title; private InterviewStatus status; + private Long summaryId; + + private Integer sum; + private SimpleStudentVO simpleStudentVO; } diff --git a/src/main/java/com/achobeta/domain/evaluate/service/impl/InterviewQuestionScoreServiceImpl.java b/src/main/java/com/achobeta/domain/evaluate/service/impl/InterviewQuestionScoreServiceImpl.java index 58214554..4e314f25 100644 --- a/src/main/java/com/achobeta/domain/evaluate/service/impl/InterviewQuestionScoreServiceImpl.java +++ b/src/main/java/com/achobeta/domain/evaluate/service/impl/InterviewQuestionScoreServiceImpl.java @@ -14,9 +14,11 @@ import com.achobeta.domain.question.model.vo.QuestionVO; import com.achobeta.redis.lock.RedisLock; import com.achobeta.redis.lock.strategy.SimpleLockStrategy; +import com.achobeta.util.ObjectUtil; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; import java.util.ArrayList; import java.util.List; @@ -67,9 +69,11 @@ public List getQuestionScoresByInterviewId(Long intervie @Override public List getAverageQuestions(List questionIds) { - return Optional.ofNullable(questionIds) - .map(ids -> interviewQuestionScoreMapper.getAverageQuestions(questionIds)) - .orElseGet(ArrayList::new); + questionIds = ObjectUtil.distinctNonNullStream(questionIds).toList(); + if(CollectionUtils.isEmpty(questionIds)) { + return new ArrayList<>(); + } + return interviewQuestionScoreMapper.getAverageQuestions(questionIds); } @Override diff --git a/src/main/java/com/achobeta/domain/evaluate/service/impl/InterviewSummaryServiceImpl.java b/src/main/java/com/achobeta/domain/evaluate/service/impl/InterviewSummaryServiceImpl.java index fd4a3656..ffe7bd09 100644 --- a/src/main/java/com/achobeta/domain/evaluate/service/impl/InterviewSummaryServiceImpl.java +++ b/src/main/java/com/achobeta/domain/evaluate/service/impl/InterviewSummaryServiceImpl.java @@ -65,6 +65,8 @@ public void summaryInterview(InterviewSummaryDTO interviewSummaryDTO) { getInterviewSummaryByInterviewId(interviewId).map(InterviewSummary::getId).ifPresentOrElse(summaryId -> { this.lambdaUpdate() .eq(InterviewSummary::getId, summaryId) + .set(InterviewSummary::getPlayback, Optional.ofNullable(interviewSummary.getPlayback()).orElse("")) + .set(InterviewSummary::getSuggest, Optional.ofNullable(interviewSummary.getSuggest()).orElse("")) .update(interviewSummary); }, () -> { this.save(interviewSummary); diff --git a/src/main/java/com/achobeta/domain/feishu/service/impl/FeishuServiceImpl.java b/src/main/java/com/achobeta/domain/feishu/service/impl/FeishuServiceImpl.java index b70fcaca..0bbd10e8 100644 --- a/src/main/java/com/achobeta/domain/feishu/service/impl/FeishuServiceImpl.java +++ b/src/main/java/com/achobeta/domain/feishu/service/impl/FeishuServiceImpl.java @@ -7,7 +7,7 @@ import com.achobeta.feishu.config.ResourceProperties; import com.achobeta.feishu.constants.ObjectType; import com.achobeta.feishu.request.FeishuRequestEngine; -import com.achobeta.feishu.token.FeishuTenantAccessToken; +import com.achobeta.feishu.token.FeishuTenantSession; import com.achobeta.util.GsonUtil; import com.achobeta.util.MediaUtil; import com.achobeta.util.TimeUtil; @@ -57,7 +57,7 @@ public class FeishuServiceImpl implements FeishuService, InitializingBean { private final FeishuAppConfig feishuAppConfig; - private final FeishuTenantAccessToken feishuTenantAccessToken; + private final FeishuTenantSession feishuTenantSession; private final FeishuRequestEngine feishuRequestEngine; @@ -86,7 +86,7 @@ public BatchGetIdUserRespBody batchGetUserId(BatchGetIdUserReqBody batchGetIdUse // } catch (Exception e) { // throw new GlobalServiceException(e.getMessage()); // } - String token = feishuTenantAccessToken.getToken(); + String token = feishuTenantSession.getToken(); return feishuRequestEngine.jsonRequest( GET_USER_ID, batchGetIdUserReqBody, @@ -133,7 +133,7 @@ public ApplyReserveRespBody reserveApply(ApplyReserveReqBody applyReserveReqBody // } catch (Exception e) { // throw new GlobalServiceException(e.getMessage()); // } - String token = feishuTenantAccessToken.getToken(); + String token = feishuTenantSession.getToken(); return feishuRequestEngine.jsonRequest( RESERVE_APPLY, applyReserveReqBody, @@ -145,10 +145,15 @@ public ApplyReserveRespBody reserveApply(ApplyReserveReqBody applyReserveReqBody @Override public ApplyReserveRespBody reserveApplyBriefly(String ownerId, Long endTime, String topic) { + // 这里预约的会议不会出现在日历里 + // 其中,是否自动录制(云录制,无法自动本地录制),不会根据飞书管理后台的全局配置中的是否自动录制,在飞书网页/软件申请的会议根据的才会用到这个全局配置 + // 而我们这次的请求参数若不设置,默认为 false(局部优先),设置为 true 后,若允许云录制会议有若的时候就会自动录制 ApplyReserveReqBody reserveReqBody = ApplyReserveReqBody.newBuilder() .endTime(String.valueOf(TimeUtil.millisToSecond(endTime))) .ownerId(ownerId) - .meetingSettings(ReserveMeetingSetting.newBuilder().topic(topic).meetingInitialType(GROUP_MEETING).build()) + .meetingSettings(ReserveMeetingSetting.newBuilder().topic(topic) +// .autoRecord(Boolean.TRUE) + .meetingInitialType(GROUP_MEETING).build()) .build(); return reserveApply(reserveReqBody); } @@ -228,7 +233,7 @@ public CreateImportTaskRespBody importTask(ImportTask importTask) { // } catch (Exception e) { // throw new GlobalServiceException(e.getMessage()); // } - String token = feishuTenantAccessToken.getToken(); + String token = feishuTenantSession.getToken(); return feishuRequestEngine.jsonRequest( IMPORT_TASK, importTask, @@ -264,7 +269,7 @@ public GetImportTaskRespBody getImportTask(String ticket) { // } catch (Exception e) { // throw new GlobalServiceException(e.getMessage()); // } - String token = feishuTenantAccessToken.getToken(); + String token = feishuTenantSession.getToken(); return feishuRequestEngine.jsonRequest( GET_IMPORT_TASK, null, diff --git a/src/main/java/com/achobeta/domain/interview/controller/InterviewController.java b/src/main/java/com/achobeta/domain/interview/controller/InterviewController.java index bced860e..4e0a4337 100644 --- a/src/main/java/com/achobeta/domain/interview/controller/InterviewController.java +++ b/src/main/java/com/achobeta/domain/interview/controller/InterviewController.java @@ -34,9 +34,6 @@ import java.util.Objects; import java.util.Optional; -import static com.achobeta.domain.interview.enums.InterviewStatus.ENDED; -import static com.achobeta.domain.interview.enums.InterviewStatus.NOT_STARTED; - /** * Created With Intellij IDEA * Description: @@ -70,8 +67,6 @@ public SystemJsonResponse createInterview(@Valid @RequestBody InterviewCreateDTO @PostMapping("/update") public SystemJsonResponse updateInterview(@Valid @RequestBody InterviewUpdateDTO interviewUpdateDTO) { - // 未开始才能修改 - interviewService.checkInterviewStatus(interviewUpdateDTO.getInterviewId(), List.of(NOT_STARTED, ENDED)); // 更新 interviewService.updateInterview(interviewUpdateDTO); return SystemJsonResponse.SYSTEM_SUCCESS(); @@ -117,14 +112,12 @@ public SystemJsonResponse setPaperForInterview(@Valid @RequestBody InterviewPape // 检查 Long interviewId = interviewPaperDTO.getInterviewId(); Interview interview = interviewService.checkAndGetInterviewExists(interviewId); - // 检查面试是否未开始 - interview.getStatus().check(List.of(NOT_STARTED, ENDED)); Long paperId = interviewPaperDTO.getPaperId(); if(!Objects.equals(interview.getPaperId(), paperId)) { // 检查试卷是否存在 questionPaperService.checkPaperExists(paperId); // 设置试卷 - interviewService.setPaperForInterview(interviewId, paperId); + interviewService.setPaperForInterview(interview, paperId); } return SystemJsonResponse.SYSTEM_SUCCESS(); } @@ -132,7 +125,7 @@ public SystemJsonResponse setPaperForInterview(@Valid @RequestBody InterviewPape @PostMapping("/list/manager/all") public SystemJsonResponse managerGetAllInterviewList(@Valid @RequestBody(required = false) InterviewConditionDTO interviewConditionDTO) { // 查询 - List interviewVOList = interviewService.managerGetInterviewList(null, InterviewConditionDTO.of(interviewConditionDTO)); + List interviewVOList = interviewService.managerGetInterviewList(null, InterviewConditionDTO.of(interviewConditionDTO)); return SystemJsonResponse.SYSTEM_SUCCESS(interviewVOList); } @@ -154,7 +147,7 @@ public SystemJsonResponse managerGetOwnInterviewList(@Valid @RequestBody(require // 获取当前管理员 id Long managerId = BaseContext.getCurrentUser().getUserId(); // 查询 - List interviewVOList = interviewService.managerGetInterviewList(managerId, InterviewConditionDTO.of(interviewConditionDTO)); + List interviewVOList = interviewService.managerGetInterviewList(managerId, InterviewConditionDTO.of(interviewConditionDTO)); return SystemJsonResponse.SYSTEM_SUCCESS(interviewVOList); } diff --git a/src/main/java/com/achobeta/domain/interview/model/converter/InterviewConverter.java b/src/main/java/com/achobeta/domain/interview/model/converter/InterviewConverter.java index d16ab1bb..4ba5d93d 100644 --- a/src/main/java/com/achobeta/domain/interview/model/converter/InterviewConverter.java +++ b/src/main/java/com/achobeta/domain/interview/model/converter/InterviewConverter.java @@ -39,6 +39,6 @@ public interface InterviewConverter { InterviewReserveVO feishuReserveToInterviewReserveVO(Reserve reserve); - List interviewVOListToInterviewExcelTemplateList(List interviewVOList); + List interviewVOListToInterviewExcelTemplateList(List interviewVOList); } diff --git a/src/main/java/com/achobeta/domain/interview/model/dao/mapper/InterviewMapper.java b/src/main/java/com/achobeta/domain/interview/model/dao/mapper/InterviewMapper.java index caa12348..0fe15a8e 100644 --- a/src/main/java/com/achobeta/domain/interview/model/dao/mapper/InterviewMapper.java +++ b/src/main/java/com/achobeta/domain/interview/model/dao/mapper/InterviewMapper.java @@ -17,7 +17,7 @@ */ public interface InterviewMapper extends BaseMapper { - List managerGetInterviewList(@Param("managerId") Long managerId, @Param("condition") InterviewConditionDTO interviewConditionDTO); + List managerGetInterviewList(@Param("managerId") Long managerId, @Param("condition") InterviewConditionDTO interviewConditionDTO); List userGetInterviewList(@Param("userId") Long userId, @Param("condition") InterviewConditionDTO interviewConditionDTO); diff --git a/src/main/java/com/achobeta/domain/interview/model/vo/InterviewExcelTemplate.java b/src/main/java/com/achobeta/domain/interview/model/vo/InterviewExcelTemplate.java index f098ac90..18443734 100644 --- a/src/main/java/com/achobeta/domain/interview/model/vo/InterviewExcelTemplate.java +++ b/src/main/java/com/achobeta/domain/interview/model/vo/InterviewExcelTemplate.java @@ -22,6 +22,12 @@ public class InterviewExcelTemplate { @Excel(name = "状态", width = 20) private InterviewStatus status; + @Excel(name = "说明", width = 50) + private String description; + + @Excel(name = "地址", width = 50) + private String address; + @ExcelEntity(name = "面试预约") private ScheduleVO scheduleVO; diff --git a/src/main/java/com/achobeta/domain/interview/service/InterviewService.java b/src/main/java/com/achobeta/domain/interview/service/InterviewService.java index 1837643c..17374b1e 100644 --- a/src/main/java/com/achobeta/domain/interview/service/InterviewService.java +++ b/src/main/java/com/achobeta/domain/interview/service/InterviewService.java @@ -29,7 +29,7 @@ public interface InterviewService extends IService { List getInterviewListByScheduleId(Long scheduleId); - List managerGetInterviewList(Long managerId, InterviewConditionDTO condition); + List managerGetInterviewList(Long managerId, InterviewConditionDTO condition); OnlineResourceVO printAllInterviewList(Long managerId, InterviewConditionDTO condition, ResourceAccessLevel level, Boolean synchronous); @@ -49,7 +49,7 @@ public interface InterviewService extends IService { InterviewStatus executeInterviewStateEvent(InterviewEvent interviewEvent, InterviewContext interviewContext); - void setPaperForInterview(Long interviewId, Long paperId); + void setPaperForInterview(Interview interview, Long paperId); // 检测 ------------------------------------------ @@ -57,6 +57,4 @@ public interface InterviewService extends IService { Interview checkAndGetInterviewExists(Long interviewId); - void checkInterviewStatus(Long interviewId, List interviewStatus); - } diff --git a/src/main/java/com/achobeta/domain/interview/service/impl/InterviewServiceImpl.java b/src/main/java/com/achobeta/domain/interview/service/impl/InterviewServiceImpl.java index 716cc919..38104fce 100644 --- a/src/main/java/com/achobeta/domain/interview/service/impl/InterviewServiceImpl.java +++ b/src/main/java/com/achobeta/domain/interview/service/impl/InterviewServiceImpl.java @@ -74,13 +74,13 @@ public List getInterviewListByScheduleId(Long scheduleId) { } @Override - public List managerGetInterviewList(Long managerId, InterviewConditionDTO condition) { + public List managerGetInterviewList(Long managerId, InterviewConditionDTO condition) { return interviewMapper.managerGetInterviewList(managerId, condition); } @Override public OnlineResourceVO printAllInterviewList(Long managerId, InterviewConditionDTO condition, ResourceAccessLevel level, Boolean synchronous) { - List interviewVOList = managerGetInterviewList(null, condition); + List interviewVOList = managerGetInterviewList(null, condition); // 上传表格到对象存储服务器 ExcelTemplateEnum achobetaInterviewAll = ExcelTemplateEnum.ACHOBETA_INTERVIEW_ALL; return resourceService.uploadExcel( @@ -161,13 +161,14 @@ public InterviewStatus executeInterviewStateEvent(InterviewEvent interviewEvent, @Override @Transactional - public void setPaperForInterview(Long interviewId, Long paperId) { + public void setPaperForInterview(Interview interview, Long paperId) { // 删除面试相关的打分 + Long interviewId = interview.getId(); Db.lambdaUpdate(InterviewQuestionScore.class) .eq(InterviewQuestionScore::getInterviewId, interviewId) .remove(); // 拷贝一份试卷 - Long newPaperId = paperQuestionLinkService.cloneQuestionPaper(paperId); + Long newPaperId = paperQuestionLinkService.cloneQuestionPaper(paperId, interview.getTitle()); // 设置试卷 this.lambdaUpdate() .eq(Interview::getId, interviewId) @@ -187,10 +188,4 @@ public Interview checkAndGetInterviewExists(Long interviewId) { new GlobalServiceException(GlobalServiceStatusCode.INTERVIEW_NOT_EXISTS)); } - @Override - public void checkInterviewStatus(Long interviewId, List interviewStatus) { - checkAndGetInterviewExists(interviewId) - .getStatus() - .check(interviewStatus); - } } diff --git a/src/main/java/com/achobeta/domain/login/model/dto/SmsLoginDTO.java b/src/main/java/com/achobeta/domain/login/model/dto/SmsLoginDTO.java index 99dc0eb2..492e7f93 100644 --- a/src/main/java/com/achobeta/domain/login/model/dto/SmsLoginDTO.java +++ b/src/main/java/com/achobeta/domain/login/model/dto/SmsLoginDTO.java @@ -1,7 +1,7 @@ package com.achobeta.domain.login.model.dto; +import com.achobeta.common.annotation.MobilePhone; import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.Pattern; import lombok.Getter; import lombok.Setter; import lombok.ToString; @@ -25,7 +25,7 @@ public class SmsLoginDTO implements Serializable { * 手机号 */ @NotBlank(message = "手机号不能为空") - @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号非法") + @MobilePhone private String phoneNumber; /** diff --git a/src/main/java/com/achobeta/domain/message/handler/chain/MessageSendDelayChain.java b/src/main/java/com/achobeta/domain/message/handler/chain/MessageSendDelayChain.java index 5912c613..cc8429db 100644 --- a/src/main/java/com/achobeta/domain/message/handler/chain/MessageSendDelayChain.java +++ b/src/main/java/com/achobeta/domain/message/handler/chain/MessageSendDelayChain.java @@ -9,6 +9,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; import java.time.Duration; import java.time.LocalDateTime; @@ -37,6 +38,7 @@ private MessageSendHandler initHandlerChain() { } @Override + @Transactional public void handleChain(MessageSendDTO messageSendBody, CopyOnWriteArraySet webSocketSet) { //初始化责任链 MessageSendHandler messageSendHandler = initHandlerChain(); diff --git a/src/main/java/com/achobeta/domain/message/handler/chain/MessageSendNowChain.java b/src/main/java/com/achobeta/domain/message/handler/chain/MessageSendNowChain.java index 72ca72ed..74e8e3b1 100644 --- a/src/main/java/com/achobeta/domain/message/handler/chain/MessageSendNowChain.java +++ b/src/main/java/com/achobeta/domain/message/handler/chain/MessageSendNowChain.java @@ -6,6 +6,7 @@ import com.achobeta.domain.message.model.dto.MessageSendDTO; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; import java.util.Optional; import java.util.concurrent.CopyOnWriteArraySet; @@ -27,6 +28,7 @@ private MessageSendHandler initHandlerChain() { } @Override + @Transactional public void handleChain(MessageSendDTO messageSendBody, CopyOnWriteArraySet webSocketSet) { //初始化责任链 MessageSendHandler messageSendHandler = initHandlerChain(); diff --git a/src/main/java/com/achobeta/domain/message/service/impl/MessageServiceImpl.java b/src/main/java/com/achobeta/domain/message/service/impl/MessageServiceImpl.java index f665fa43..3a289c2b 100644 --- a/src/main/java/com/achobeta/domain/message/service/impl/MessageServiceImpl.java +++ b/src/main/java/com/achobeta/domain/message/service/impl/MessageServiceImpl.java @@ -64,7 +64,7 @@ public QueryStuListVO queryStuListByCondition(QueryStuListDTO queryStuDTO) { @Override public List queryStuList(Long batchId, List userIds) { - List stuResumeList = stuResumeService.queryStuList(batchId, userIds.stream().distinct().toList()); + List stuResumeList = stuResumeService.queryStuList(batchId, userIds); return messageConverter.stuResumeListToStuBaseInfoDTOList(stuResumeList); } diff --git a/src/main/java/com/achobeta/domain/paper/constants/PaperLibraryConstants.java b/src/main/java/com/achobeta/domain/paper/constants/PaperLibraryConstants.java new file mode 100644 index 00000000..17446db6 --- /dev/null +++ b/src/main/java/com/achobeta/domain/paper/constants/PaperLibraryConstants.java @@ -0,0 +1,14 @@ +package com.achobeta.domain.paper.constants; + +/** + * Created With Intellij IDEA + * Description: + * User: 马拉圈 + * Date: 2024-10-16 + * Time: 21:24 + */ +public interface PaperLibraryConstants { + + String PAPER_LIBRARY_REFERENCE_PAPERS_LOCK = "paperLibraryReferencePapersLock:"; + +} diff --git a/src/main/java/com/achobeta/domain/paper/constants/QuestionPaperConstants.java b/src/main/java/com/achobeta/domain/paper/constants/QuestionPaperConstants.java new file mode 100644 index 00000000..23176cbd --- /dev/null +++ b/src/main/java/com/achobeta/domain/paper/constants/QuestionPaperConstants.java @@ -0,0 +1,14 @@ +package com.achobeta.domain.paper.constants; + +/** + * Created With Intellij IDEA + * Description: + * User: 马拉圈 + * Date: 2024-10-16 + * Time: 21:36 + */ +public interface QuestionPaperConstants { + + String PAPER_ADD_QUESTIONS_LOCK = "paperAddQuestionsLock:"; + +} diff --git a/src/main/java/com/achobeta/domain/paper/controller/PaperLibraryController.java b/src/main/java/com/achobeta/domain/paper/controller/PaperLibraryController.java index 6442b9e8..682bb666 100644 --- a/src/main/java/com/achobeta/domain/paper/controller/PaperLibraryController.java +++ b/src/main/java/com/achobeta/domain/paper/controller/PaperLibraryController.java @@ -4,9 +4,11 @@ import com.achobeta.common.annotation.Intercept; import com.achobeta.common.enums.UserTypeEnum; import com.achobeta.domain.paper.model.converter.LibraryConverter; +import com.achobeta.domain.paper.model.dto.LibraryReferencePaperDTO; import com.achobeta.domain.paper.model.dto.PaperLibraryDTO; import com.achobeta.domain.paper.model.entity.QuestionPaperLibrary; import com.achobeta.domain.paper.service.QuestionPaperLibraryService; +import com.achobeta.domain.paper.service.QuestionPaperService; import jakarta.validation.Valid; import jakarta.validation.constraints.NotBlank; import lombok.RequiredArgsConstructor; @@ -33,12 +35,23 @@ public class PaperLibraryController { private final QuestionPaperLibraryService questionPaperLibraryService; + private final QuestionPaperService questionPaperService; + @PostMapping("/create") public SystemJsonResponse createPaperLibrary(@RequestParam("libType") @NotBlank String libType) { Long paperLibraryId = questionPaperLibraryService.createPaperLibrary(libType); return SystemJsonResponse.SYSTEM_SUCCESS(paperLibraryId); } + @PostMapping("/reference") + public SystemJsonResponse referencePapers(@Valid @RequestBody LibraryReferencePaperDTO libraryReferencePaperDTO) { + Long libId = libraryReferencePaperDTO.getLibId(); + questionPaperLibraryService.checkPaperLibraryExists(libId); + // 引用 + questionPaperService.referencePapers(libraryReferencePaperDTO.getLibId(), libraryReferencePaperDTO.getPaperIds()); + return SystemJsonResponse.SYSTEM_SUCCESS(); + } + @PostMapping("/rename") public SystemJsonResponse renamePaperLibrary(@Valid @RequestBody PaperLibraryDTO paperLibraryDTO) { // 检查 diff --git a/src/main/java/com/achobeta/domain/paper/handler/chain/RemovePaperHandlerChain.java b/src/main/java/com/achobeta/domain/paper/handler/chain/RemovePaperHandlerChain.java index c03799d8..c2bc5438 100644 --- a/src/main/java/com/achobeta/domain/paper/handler/chain/RemovePaperHandlerChain.java +++ b/src/main/java/com/achobeta/domain/paper/handler/chain/RemovePaperHandlerChain.java @@ -5,6 +5,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; import java.util.List; @@ -39,6 +40,7 @@ public void doPostConstruct() { } @Override + @Transactional public void handle(Long paperId) { log.info("责任链开始处理 [paperId 为 {}] 的“删除试卷”事件", paperId); super.doNextHandler(paperId); diff --git a/src/main/java/com/achobeta/domain/paper/handler/chain/RemoveQuestionFromPaperHandlerChain.java b/src/main/java/com/achobeta/domain/paper/handler/chain/RemoveQuestionFromPaperHandlerChain.java index 3fe882ee..cca60772 100644 --- a/src/main/java/com/achobeta/domain/paper/handler/chain/RemoveQuestionFromPaperHandlerChain.java +++ b/src/main/java/com/achobeta/domain/paper/handler/chain/RemoveQuestionFromPaperHandlerChain.java @@ -5,6 +5,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; import java.util.List; @@ -39,6 +40,7 @@ public void doPostConstruct() { } @Override + @Transactional public void handle(Long paperId, List questionIds) { log.info("责任链开始处理 [paperId 为 {},questionIds 为 {}] 的“从试卷中移除若干题”事件", paperId, questionIds); super.doNextHandler(paperId, questionIds); diff --git a/src/main/java/com/achobeta/domain/paper/handler/ext/RemovePaperLPLinkHandler.java b/src/main/java/com/achobeta/domain/paper/handler/ext/RemovePaperLPLinkHandler.java index 642e2b55..65a4c1ef 100644 --- a/src/main/java/com/achobeta/domain/paper/handler/ext/RemovePaperLPLinkHandler.java +++ b/src/main/java/com/achobeta/domain/paper/handler/ext/RemovePaperLPLinkHandler.java @@ -2,7 +2,9 @@ import com.achobeta.domain.paper.handler.RemovePaperHandler; import com.achobeta.domain.paper.model.entity.LibraryPaperLink; +import com.achobeta.domain.paper.model.entity.PaperQuestionLink; import com.achobeta.domain.paper.service.LibraryPaperLinkService; +import com.achobeta.domain.paper.service.PaperQuestionLinkService; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; @@ -19,12 +21,17 @@ public class RemovePaperLPLinkHandler extends RemovePaperHandler { private final LibraryPaperLinkService libraryPaperLinkService; + private final PaperQuestionLinkService paperQuestionLinkService; + @Override public void handle(Long paperId) { // 删除关联表的一些行 libraryPaperLinkService.lambdaUpdate() .eq(LibraryPaperLink::getPaperId, paperId) .remove(); + paperQuestionLinkService.lambdaUpdate() + .eq(PaperQuestionLink::getPaperId, paperId) + .remove(); super.doNextHandler(paperId); } diff --git a/src/main/java/com/achobeta/domain/paper/model/dao/mapper/QuestionPaperMapper.java b/src/main/java/com/achobeta/domain/paper/model/dao/mapper/QuestionPaperMapper.java index d8270db0..2b0993f1 100644 --- a/src/main/java/com/achobeta/domain/paper/model/dao/mapper/QuestionPaperMapper.java +++ b/src/main/java/com/achobeta/domain/paper/model/dao/mapper/QuestionPaperMapper.java @@ -17,6 +17,8 @@ public interface QuestionPaperMapper extends BaseMapper { // 并不会将结果集加入 page,而是返回值 IPage 里 IPage queryPapers(IPage page, @Param("libIds") List libIds); + + List getPapers(@Param("libIds") List libIds); } diff --git a/src/main/java/com/achobeta/domain/paper/model/dto/LibraryReferencePaperDTO.java b/src/main/java/com/achobeta/domain/paper/model/dto/LibraryReferencePaperDTO.java new file mode 100644 index 00000000..fc3fc0f9 --- /dev/null +++ b/src/main/java/com/achobeta/domain/paper/model/dto/LibraryReferencePaperDTO.java @@ -0,0 +1,25 @@ +package com.achobeta.domain.paper.model.dto; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.List; + +/** + * Created With Intellij IDEA + * Description: + * User: 马拉圈 + * Date: 2024-10-16 + * Time: 20:43 + */ +@Data +public class LibraryReferencePaperDTO { + + @NotNull(message = "试卷库 id 不能为空") + private Long libId; + + @NotEmpty(message = "试卷 id 列表不能为空") + private List paperIds; + +} diff --git a/src/main/java/com/achobeta/domain/paper/model/dto/PaperLibraryDTO.java b/src/main/java/com/achobeta/domain/paper/model/dto/PaperLibraryDTO.java index e6de6bc9..7d9dfbde 100644 --- a/src/main/java/com/achobeta/domain/paper/model/dto/PaperLibraryDTO.java +++ b/src/main/java/com/achobeta/domain/paper/model/dto/PaperLibraryDTO.java @@ -14,7 +14,7 @@ @Data public class PaperLibraryDTO { - @NotNull(message = "库的 id 不能为空") + @NotNull(message = "试卷库 id 不能为空") private Long libId; @NotBlank(message = "库的类型不能为空") diff --git a/src/main/java/com/achobeta/domain/paper/model/dto/PaperQuestionLinkDTO.java b/src/main/java/com/achobeta/domain/paper/model/dto/PaperQuestionLinkDTO.java index 34f39141..8a149324 100644 --- a/src/main/java/com/achobeta/domain/paper/model/dto/PaperQuestionLinkDTO.java +++ b/src/main/java/com/achobeta/domain/paper/model/dto/PaperQuestionLinkDTO.java @@ -1,5 +1,6 @@ package com.achobeta.domain.paper.model.dto; +import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import lombok.Data; @@ -18,7 +19,7 @@ public class PaperQuestionLinkDTO { @NotNull(message = "试卷 id 不能为空") private Long paperId; - @NotNull(message = "问题 ids 不能为空") + @NotEmpty(message = "问题 ids 不能为空") private List questionIds; } diff --git a/src/main/java/com/achobeta/domain/paper/model/dto/QuestionPaperDTO.java b/src/main/java/com/achobeta/domain/paper/model/dto/QuestionPaperDTO.java index e15294e2..24361403 100644 --- a/src/main/java/com/achobeta/domain/paper/model/dto/QuestionPaperDTO.java +++ b/src/main/java/com/achobeta/domain/paper/model/dto/QuestionPaperDTO.java @@ -1,7 +1,7 @@ package com.achobeta.domain.paper.model.dto; import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.NotEmpty; import lombok.Data; import java.util.List; @@ -16,7 +16,7 @@ @Data public class QuestionPaperDTO { - @NotNull(message = "试卷库 ids 不能为空") + @NotEmpty(message = "试卷库 ids 不能为空") private List libIds; @NotBlank(message = "题目不能为空") @@ -25,5 +25,4 @@ public class QuestionPaperDTO { @NotBlank(message = "试卷说明不能为空") private String description; - } diff --git a/src/main/java/com/achobeta/domain/paper/service/PaperQuestionLinkService.java b/src/main/java/com/achobeta/domain/paper/service/PaperQuestionLinkService.java index 0cd292ad..79edab6a 100644 --- a/src/main/java/com/achobeta/domain/paper/service/PaperQuestionLinkService.java +++ b/src/main/java/com/achobeta/domain/paper/service/PaperQuestionLinkService.java @@ -25,7 +25,7 @@ public interface PaperQuestionLinkService extends IService { QuestionPaperDetailVO getPaperDetail(Long paperId); - Long cloneQuestionPaper(Long paperId); + Long cloneQuestionPaper(Long paperId, String title); void checkQuestionExistInPaper(Long paperId, Long questionId); diff --git a/src/main/java/com/achobeta/domain/paper/service/QuestionPaperService.java b/src/main/java/com/achobeta/domain/paper/service/QuestionPaperService.java index 5565f0c9..2363f269 100644 --- a/src/main/java/com/achobeta/domain/paper/service/QuestionPaperService.java +++ b/src/main/java/com/achobeta/domain/paper/service/QuestionPaperService.java @@ -21,6 +21,8 @@ public interface QuestionPaperService extends IService { Long addQuestionPaper(List libIds, String title, String description); + void referencePapers(Long libId, List paperIds); + void updateQuestionPaper(Long paperId, List libIds, String title, String description); /** diff --git a/src/main/java/com/achobeta/domain/paper/service/impl/PaperQuestionLinkServiceImpl.java b/src/main/java/com/achobeta/domain/paper/service/impl/PaperQuestionLinkServiceImpl.java index 0b15347c..85cc55e3 100644 --- a/src/main/java/com/achobeta/domain/paper/service/impl/PaperQuestionLinkServiceImpl.java +++ b/src/main/java/com/achobeta/domain/paper/service/impl/PaperQuestionLinkServiceImpl.java @@ -1,6 +1,7 @@ package com.achobeta.domain.paper.service.impl; import com.achobeta.common.enums.GlobalServiceStatusCode; +import com.achobeta.domain.paper.constants.QuestionPaperConstants; import com.achobeta.domain.paper.model.dao.mapper.PaperQuestionLinkMapper; import com.achobeta.domain.paper.model.dao.mapper.QuestionPaperLibraryMapper; import com.achobeta.domain.paper.model.entity.PaperQuestionLink; @@ -10,15 +11,21 @@ import com.achobeta.domain.paper.service.QuestionPaperService; import com.achobeta.domain.question.model.vo.QuestionVO; import com.achobeta.exception.GlobalServiceException; +import com.achobeta.redis.lock.RedisLock; +import com.achobeta.redis.lock.strategy.SimpleLockStrategy; +import com.achobeta.util.ObjectUtil; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; -import java.util.HashSet; import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.Set; +import java.util.stream.Collectors; /** * @author 马拉圈 @@ -36,6 +43,10 @@ public class PaperQuestionLinkServiceImpl extends ServiceImpl getQuestionsOnPaper(Long paperId) { return paperQuestionLinkMapper.getQuestionsOnPaper(paperId); @@ -52,27 +63,32 @@ public Optional getPaperQuestionLink(Long paperId, Long quest @Override @Transactional public void addQuestionsForPaper(Long paperId, List questionIds) { - Set hash = new HashSet<>(); - // 获取试卷的所有题 - getQuestionsOnPaper(paperId).forEach(questionVO -> hash.add(questionVO.getId())); - // 将不存在于原试卷的题滤出来 - List paperQuestionLinks = questionIds.stream() - .filter(questionId -> !hash.contains(questionId)) - .map(questionId -> { - PaperQuestionLink paperQuestionLink = new PaperQuestionLink(); - paperQuestionLink.setPaperId(paperId); - paperQuestionLink.setQuestionId(questionId); - return paperQuestionLink; - }).toList(); - this.saveBatch(paperQuestionLinks); + redisLock.tryLockDoSomething(QuestionPaperConstants.PAPER_ADD_QUESTIONS_LOCK + paperId, () -> { + // 获取试卷的所有题 + Set hash = getQuestionsOnPaper(paperId).stream().map(QuestionVO::getId).collect(Collectors.toSet()); + // 将不存在于原试卷的题滤出来 + List paperQuestionLinks = questionIds.stream() + .distinct() + .filter(questionId -> Objects.nonNull(questionId) && !hash.contains(questionId)) + .map(questionId -> { + PaperQuestionLink paperQuestionLink = new PaperQuestionLink(); + paperQuestionLink.setPaperId(paperId); + paperQuestionLink.setQuestionId(questionId); + return paperQuestionLink; + }).toList(); + this.saveBatch(paperQuestionLinks); + }, () -> {}, simpleLockStrategy); } @Override public void removeQuestionsFromPaper(Long paperId, List questionIds) { - this.lambdaUpdate() - .eq(PaperQuestionLink::getPaperId, paperId) - .in(PaperQuestionLink::getQuestionId, questionIds) - .remove(); + questionIds = ObjectUtil.distinctNonNullStream(questionIds).toList(); + if(!CollectionUtils.isEmpty(questionIds)) { + this.lambdaUpdate() + .eq(PaperQuestionLink::getPaperId, paperId) + .in(PaperQuestionLink::getQuestionId, questionIds) + .remove(); + } } @Override @@ -85,15 +101,18 @@ public QuestionPaperDetailVO getPaperDetail(Long paperId) { @Override @Transactional - public Long cloneQuestionPaper(Long paperId) { + public Long cloneQuestionPaper(Long paperId, String title) { // 拷贝试卷的定义 QuestionPaperDetailVO paperDetail = getPaperDetail(paperId); List libIds = paperDetail.getTypes() .stream() .map(PaperLibraryVO::getId) .toList(); - Long newPaperId = questionPaperService.addQuestionPaper(libIds, - paperDetail.getTitle(), paperDetail.getDescription()); + Long newPaperId = questionPaperService.addQuestionPaper( + libIds, + Optional.ofNullable(title).filter(StringUtils::hasText).orElseGet(paperDetail::getTitle), + paperDetail.getDescription() + ); // 拷贝试卷的题目 List questionIds = paperDetail.getQuestions() .stream() diff --git a/src/main/java/com/achobeta/domain/paper/service/impl/QuestionPaperServiceImpl.java b/src/main/java/com/achobeta/domain/paper/service/impl/QuestionPaperServiceImpl.java index eff1936b..213c9178 100644 --- a/src/main/java/com/achobeta/domain/paper/service/impl/QuestionPaperServiceImpl.java +++ b/src/main/java/com/achobeta/domain/paper/service/impl/QuestionPaperServiceImpl.java @@ -3,6 +3,7 @@ import com.achobeta.common.base.BasePageQuery; import com.achobeta.common.base.BasePageResult; import com.achobeta.common.enums.GlobalServiceStatusCode; +import com.achobeta.domain.paper.constants.PaperLibraryConstants; import com.achobeta.domain.paper.model.converter.QuestionPaperConverter; import com.achobeta.domain.paper.model.dao.mapper.QuestionPaperMapper; import com.achobeta.domain.paper.model.dto.PaperQueryDTO; @@ -12,6 +13,8 @@ import com.achobeta.domain.paper.service.LibraryPaperLinkService; import com.achobeta.domain.paper.service.QuestionPaperService; import com.achobeta.exception.GlobalServiceException; +import com.achobeta.redis.lock.RedisLock; +import com.achobeta.redis.lock.strategy.SimpleLockStrategy; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import lombok.RequiredArgsConstructor; @@ -19,6 +22,7 @@ import org.springframework.transaction.annotation.Transactional; import java.util.*; +import java.util.stream.Collectors; /** * @author 马拉圈 @@ -34,6 +38,10 @@ public class QuestionPaperServiceImpl extends ServiceImpl BasePageQuery -> page -> BasePageResult -> vo * @param paperQueryDTO 分页参数 @@ -77,6 +85,23 @@ public Long addQuestionPaper(List libIds, String title, String description return paperId; } + @Override + public void referencePapers(Long libId, List paperIds) { + redisLock.tryLockDoSomething(PaperLibraryConstants.PAPER_LIBRARY_REFERENCE_PAPERS_LOCK + libId, () -> { + Set hash = questionPaperMapper.getPapers(List.of(libId)).stream().map(QuestionPaper::getId).collect(Collectors.toSet()); + List libraryPaperLinkList = paperIds.stream() + .distinct() + .filter(paperId -> Objects.nonNull(paperId) && !hash.contains(paperId)) + .map(paperId -> { + LibraryPaperLink libraryPaperLink = new LibraryPaperLink(); + libraryPaperLink.setPaperId(paperId); + libraryPaperLink.setLibId(libId); + return libraryPaperLink; + }).toList(); + libraryPaperLinkService.saveBatch(libraryPaperLinkList); + }, () -> {}, simpleLockStrategy); + } + @Override @Transactional public void updateQuestionPaper(Long paperId, List libIds, String title, String description) { diff --git a/src/main/java/com/achobeta/domain/question/constants/QuestionLibraryConstants.java b/src/main/java/com/achobeta/domain/question/constants/QuestionLibraryConstants.java new file mode 100644 index 00000000..ebdb40fa --- /dev/null +++ b/src/main/java/com/achobeta/domain/question/constants/QuestionLibraryConstants.java @@ -0,0 +1,14 @@ +package com.achobeta.domain.question.constants; + +/** + * Created With Intellij IDEA + * Description: + * User: 马拉圈 + * Date: 2024-10-16 + * Time: 21:30 + */ +public interface QuestionLibraryConstants { + + String QUESTION_LIBRARY_REFERENCE_QUESTIONS_LOCK = "questionLibraryReferenceQuestionsLock:"; + +} diff --git a/src/main/java/com/achobeta/domain/question/controller/QuestionLibraryController.java b/src/main/java/com/achobeta/domain/question/controller/QuestionLibraryController.java index 75929d49..ee253f51 100644 --- a/src/main/java/com/achobeta/domain/question/controller/QuestionLibraryController.java +++ b/src/main/java/com/achobeta/domain/question/controller/QuestionLibraryController.java @@ -4,9 +4,11 @@ import com.achobeta.common.annotation.Intercept; import com.achobeta.common.enums.UserTypeEnum; import com.achobeta.domain.paper.model.converter.LibraryConverter; +import com.achobeta.domain.question.model.dto.LibraryReferenceQuestionDTO; import com.achobeta.domain.question.model.dto.QuestionLibraryDTO; import com.achobeta.domain.question.model.entity.QuestionLibrary; import com.achobeta.domain.question.service.QuestionLibraryService; +import com.achobeta.domain.question.service.QuestionService; import jakarta.validation.Valid; import jakarta.validation.constraints.NotBlank; import lombok.RequiredArgsConstructor; @@ -33,12 +35,23 @@ public class QuestionLibraryController { private final QuestionLibraryService questionLibraryService; + private final QuestionService questionService; + @PostMapping("/create") public SystemJsonResponse createQuestionLibrary(@RequestParam("libType") @NotBlank String libType) { Long questionLibraryId = questionLibraryService.createQuestionLibrary(libType); return SystemJsonResponse.SYSTEM_SUCCESS(questionLibraryId); } + @PostMapping("/reference") + public SystemJsonResponse referenceQuestions(@Valid @RequestBody LibraryReferenceQuestionDTO libraryReferenceQuestionDTO) { + Long libId = libraryReferenceQuestionDTO.getLibId(); + questionLibraryService.checkQuestionLibraryExists(libId); + // 引用 + questionService.referenceQuestions(libraryReferenceQuestionDTO.getLibId(), libraryReferenceQuestionDTO.getQuestionIds()); + return SystemJsonResponse.SYSTEM_SUCCESS(); + } + @PostMapping("/rename") public SystemJsonResponse renameQuestionLibrary(@Valid @RequestBody QuestionLibraryDTO questionLibraryDTO) { // 检查 diff --git a/src/main/java/com/achobeta/domain/question/handler/chain/RemoveQuestionHandlerChain.java b/src/main/java/com/achobeta/domain/question/handler/chain/RemoveQuestionHandlerChain.java index 7a93dad3..c7d5ad61 100644 --- a/src/main/java/com/achobeta/domain/question/handler/chain/RemoveQuestionHandlerChain.java +++ b/src/main/java/com/achobeta/domain/question/handler/chain/RemoveQuestionHandlerChain.java @@ -5,6 +5,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; import java.util.List; @@ -39,6 +40,7 @@ public void doPostConstruct() { } @Override + @Transactional public void handle(Long questionId) { log.info("责任链开始处理 [questionId 为 {}] 的“删除问题”事件", questionId); super.doNextHandler(questionId); diff --git a/src/main/java/com/achobeta/domain/question/model/dao/mapper/QuestionMapper.java b/src/main/java/com/achobeta/domain/question/model/dao/mapper/QuestionMapper.java index abcb0c6a..eb41d0d2 100644 --- a/src/main/java/com/achobeta/domain/question/model/dao/mapper/QuestionMapper.java +++ b/src/main/java/com/achobeta/domain/question/model/dao/mapper/QuestionMapper.java @@ -18,6 +18,8 @@ public interface QuestionMapper extends BaseMapper { // 并不会将结果集加入 page,而是返回值 IPage 里 IPage queryQuestions(IPage page, @Param("libIds") List libIds); + List getQuestions(@Param("libIds") List libIds); + } diff --git a/src/main/java/com/achobeta/domain/question/model/dto/LibraryReferenceQuestionDTO.java b/src/main/java/com/achobeta/domain/question/model/dto/LibraryReferenceQuestionDTO.java new file mode 100644 index 00000000..9de7a50b --- /dev/null +++ b/src/main/java/com/achobeta/domain/question/model/dto/LibraryReferenceQuestionDTO.java @@ -0,0 +1,25 @@ +package com.achobeta.domain.question.model.dto; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.List; + +/** + * Created With Intellij IDEA + * Description: + * User: 马拉圈 + * Date: 2024-10-16 + * Time: 20:22 + */ +@Data +public class LibraryReferenceQuestionDTO { + + @NotNull(message = "题库 id 不能为空") + private Long libId; + + @NotEmpty(message = "问题 id 列表不能为空") + private List questionIds; + +} diff --git a/src/main/java/com/achobeta/domain/question/model/dto/QuestionLibraryDTO.java b/src/main/java/com/achobeta/domain/question/model/dto/QuestionLibraryDTO.java index 3064be85..41cea0b5 100644 --- a/src/main/java/com/achobeta/domain/question/model/dto/QuestionLibraryDTO.java +++ b/src/main/java/com/achobeta/domain/question/model/dto/QuestionLibraryDTO.java @@ -14,7 +14,7 @@ @Data public class QuestionLibraryDTO { - @NotNull(message = "库的 id 不能为空") + @NotNull(message = "题库 id 不能为空") private Long libId; @NotBlank(message = "库的类型不能为空") diff --git a/src/main/java/com/achobeta/domain/question/service/QuestionService.java b/src/main/java/com/achobeta/domain/question/service/QuestionService.java index 2bf8bce9..da9c9079 100644 --- a/src/main/java/com/achobeta/domain/question/service/QuestionService.java +++ b/src/main/java/com/achobeta/domain/question/service/QuestionService.java @@ -23,6 +23,8 @@ public interface QuestionService extends IService { Long addQuestion(List libIds, String title, String standard); + void referenceQuestions(Long libId, List questionIds); + void saveBatchQuestion(QuestionSaveBatchDTO questionSaveBatchDTO); void updateQuestion(Long questionId, List libIds, String title, String standard); diff --git a/src/main/java/com/achobeta/domain/question/service/impl/QuestionServiceImpl.java b/src/main/java/com/achobeta/domain/question/service/impl/QuestionServiceImpl.java index 6c4c475e..c4dd241b 100644 --- a/src/main/java/com/achobeta/domain/question/service/impl/QuestionServiceImpl.java +++ b/src/main/java/com/achobeta/domain/question/service/impl/QuestionServiceImpl.java @@ -3,6 +3,7 @@ import com.achobeta.common.base.BasePageQuery; import com.achobeta.common.base.BasePageResult; import com.achobeta.common.enums.GlobalServiceStatusCode; +import com.achobeta.domain.question.constants.QuestionLibraryConstants; import com.achobeta.domain.question.model.converter.QuestionConverter; import com.achobeta.domain.question.model.dao.mapper.QuestionLibraryMapper; import com.achobeta.domain.question.model.dao.mapper.QuestionMapper; @@ -19,6 +20,8 @@ import com.achobeta.domain.question.service.QuestionLibraryService; import com.achobeta.domain.question.service.QuestionService; import com.achobeta.exception.GlobalServiceException; +import com.achobeta.redis.lock.RedisLock; +import com.achobeta.redis.lock.strategy.SimpleLockStrategy; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import lombok.RequiredArgsConstructor; @@ -27,6 +30,7 @@ import org.springframework.util.CollectionUtils; import java.util.*; +import java.util.stream.Collectors; /** * @author 马拉圈 @@ -38,7 +42,6 @@ public class QuestionServiceImpl extends ServiceImpl implements QuestionService{ - private final QuestionMapper questionMapper; private final QuestionLibraryMapper questionLibraryMapper; @@ -47,6 +50,10 @@ public class QuestionServiceImpl extends ServiceImpl private final LibraryQuestionLinkService libraryQuestionLinkService; + private final RedisLock redisLock; + + private final SimpleLockStrategy simpleLockStrategy; + /** * 流程:dto -> BasePageQuery -> page -> BasePageResult -> vo * @param questionQueryDTO 分页参数 @@ -90,6 +97,23 @@ public Long addQuestion(List libIds, String title, String standard) { return questionId; } + @Override + public void referenceQuestions(Long libId, List questionIds) { + redisLock.tryLockDoSomething(QuestionLibraryConstants.QUESTION_LIBRARY_REFERENCE_QUESTIONS_LOCK + libId, () -> { + Set hash = questionMapper.getQuestions(List.of(libId)).stream().map(Question::getId).collect(Collectors.toSet()); + List libraryQuestionLinkList = questionIds.stream() + .distinct() + .filter(questionId -> Objects.nonNull(questionId) && !hash.contains(questionId)) + .map(questionId -> { + LibraryQuestionLink libraryQuestionLink = new LibraryQuestionLink(); + libraryQuestionLink.setQuestionId(questionId); + libraryQuestionLink.setLibId(libId); + return libraryQuestionLink; + }).toList(); + libraryQuestionLinkService.saveBatch(libraryQuestionLinkList); + }, () -> {}, simpleLockStrategy); + } + @Override @Transactional public void saveBatchQuestion(QuestionSaveBatchDTO questionSaveBatchDTO) { diff --git a/src/main/java/com/achobeta/domain/recruit/handler/ext/RemovePaperPQLinkHandler.java b/src/main/java/com/achobeta/domain/recruit/handler/ext/RemovePaperPQLinkHandler.java index 8c471131..880ae55d 100644 --- a/src/main/java/com/achobeta/domain/recruit/handler/ext/RemovePaperPQLinkHandler.java +++ b/src/main/java/com/achobeta/domain/recruit/handler/ext/RemovePaperPQLinkHandler.java @@ -32,22 +32,20 @@ public class RemovePaperPQLinkHandler extends RemovePaperHandler { @Override public void handle(Long paperId) { List participationIds = activityParticipationService.getParticipationIdsByPaperId(paperId); - if(CollectionUtils.isEmpty(participationIds)) { - return; + if(!CollectionUtils.isEmpty(participationIds)) { + // 删除对应的行 + participationQuestionLinkService.lambdaUpdate() + .in(ParticipationQuestionLink::getParticipationId, participationIds) + .remove(); } - // 删除对应的行 - participationQuestionLinkService.lambdaUpdate() - .in(ParticipationQuestionLink::getParticipationId, participationIds) - .remove(); // 涉及的招新活动的 paperId 置为 null List actIds = recruitmentActivityService.getActIdsByPaperId(paperId); - if(CollectionUtils.isEmpty(actIds)) { - return; + if(!CollectionUtils.isEmpty(actIds)) { + recruitmentActivityService.lambdaUpdate() + .in(RecruitmentActivity::getId, actIds) + .set(RecruitmentActivity::getPaperId, null) + .update(); } - recruitmentActivityService.lambdaUpdate() - .in(RecruitmentActivity::getId, actIds) - .set(RecruitmentActivity::getPaperId, null) - .update(); // 执行下一个 super.doNextHandler(paperId); } diff --git a/src/main/java/com/achobeta/domain/recruit/handler/ext/RemoveQuestionFromPaperPQLinkHandler.java b/src/main/java/com/achobeta/domain/recruit/handler/ext/RemoveQuestionFromPaperPQLinkHandler.java index 5e055b32..61a8eff7 100644 --- a/src/main/java/com/achobeta/domain/recruit/handler/ext/RemoveQuestionFromPaperPQLinkHandler.java +++ b/src/main/java/com/achobeta/domain/recruit/handler/ext/RemoveQuestionFromPaperPQLinkHandler.java @@ -28,14 +28,13 @@ public class RemoveQuestionFromPaperPQLinkHandler extends RemoveQuestionFromPape @Override public void handle(Long paperId, List questionIds) { List participationIds = activityParticipationService.getParticipationIdsByPaperId(paperId); - if(CollectionUtils.isEmpty(participationIds)) { - return; + if(!CollectionUtils.isEmpty(participationIds) && !CollectionUtils.isEmpty(questionIds)) { + // 删除对应的行 + participationQuestionLinkService.lambdaUpdate() + .in(ParticipationQuestionLink::getParticipationId, participationIds) + .in(ParticipationQuestionLink::getQuestionId, questionIds) + .remove(); } - // 删除对应的行 - participationQuestionLinkService.lambdaUpdate() - .in(ParticipationQuestionLink::getParticipationId, participationIds) - .in(ParticipationQuestionLink::getQuestionId, questionIds) - .remove(); // 执行下一个 super.doNextHandler(paperId, questionIds); } diff --git a/src/main/java/com/achobeta/domain/recruit/model/condition/allmatch/StatusCondition.java b/src/main/java/com/achobeta/domain/recruit/model/condition/allmatch/StatusCondition.java index a509681e..d075ab78 100644 --- a/src/main/java/com/achobeta/domain/recruit/model/condition/allmatch/StatusCondition.java +++ b/src/main/java/com/achobeta/domain/recruit/model/condition/allmatch/StatusCondition.java @@ -2,9 +2,9 @@ import com.achobeta.domain.recruit.model.condition.function.StudentCondition; import com.achobeta.domain.student.model.entity.StuResume; +import com.achobeta.util.ObjectUtil; import java.util.ArrayList; -import java.util.Objects; import java.util.function.Predicate; /** @@ -18,11 +18,8 @@ public class StatusCondition extends ArrayList implements StudentCondit @Override public Predicate predicate() { - return stuResume -> this - .stream() - .filter(Objects::nonNull) - .anyMatch(status -> status.equals(stuResume.getStatus().getCode())) - ; + return stuResume -> ObjectUtil.distinctNonNullStream(this) + .anyMatch(status -> status.equals(stuResume.getStatus().getCode())); } } diff --git a/src/main/java/com/achobeta/domain/recruit/model/condition/anymatch/GradeCondition.java b/src/main/java/com/achobeta/domain/recruit/model/condition/anymatch/GradeCondition.java index 40fde831..8bd56780 100644 --- a/src/main/java/com/achobeta/domain/recruit/model/condition/anymatch/GradeCondition.java +++ b/src/main/java/com/achobeta/domain/recruit/model/condition/anymatch/GradeCondition.java @@ -2,9 +2,9 @@ import com.achobeta.domain.recruit.model.condition.function.StudentCondition; import com.achobeta.domain.student.model.entity.StuResume; +import com.achobeta.util.ObjectUtil; import java.util.ArrayList; -import java.util.Objects; import java.util.function.Predicate; /** @@ -18,10 +18,7 @@ public class GradeCondition extends ArrayList implements StudentConditi @Override public Predicate predicate() { - return stuResume -> this - .stream() - .filter(Objects::nonNull) - .anyMatch(grade -> grade.equals(stuResume.getGrade())) - ; + return stuResume -> ObjectUtil.distinctNonNullStream(this) + .anyMatch(grade -> grade.equals(stuResume.getGrade())); } } diff --git a/src/main/java/com/achobeta/domain/recruit/model/condition/anymatch/UserIdCondition.java b/src/main/java/com/achobeta/domain/recruit/model/condition/anymatch/UserIdCondition.java index 599efb2a..bdb2c5fe 100644 --- a/src/main/java/com/achobeta/domain/recruit/model/condition/anymatch/UserIdCondition.java +++ b/src/main/java/com/achobeta/domain/recruit/model/condition/anymatch/UserIdCondition.java @@ -2,9 +2,9 @@ import com.achobeta.domain.recruit.model.condition.function.StudentCondition; import com.achobeta.domain.student.model.entity.StuResume; +import com.achobeta.util.ObjectUtil; import java.util.ArrayList; -import java.util.Objects; import java.util.function.Predicate; /** @@ -18,10 +18,7 @@ public class UserIdCondition extends ArrayList implements StudentCondition @Override public Predicate predicate() { - return stuResume -> this - .stream() - .filter(Objects::nonNull) - .anyMatch(userId -> userId.equals(stuResume.getUserId())) - ; + return stuResume -> ObjectUtil.distinctNonNullStream(this) + .anyMatch(userId -> userId.equals(stuResume.getUserId())); } } diff --git a/src/main/java/com/achobeta/domain/recruit/service/impl/ActivityParticipationServiceImpl.java b/src/main/java/com/achobeta/domain/recruit/service/impl/ActivityParticipationServiceImpl.java index 67036178..247e3252 100644 --- a/src/main/java/com/achobeta/domain/recruit/service/impl/ActivityParticipationServiceImpl.java +++ b/src/main/java/com/achobeta/domain/recruit/service/impl/ActivityParticipationServiceImpl.java @@ -14,6 +14,7 @@ import com.achobeta.redis.lock.RedisLock; import com.achobeta.redis.lock.strategy.ReadLockStrategy; import com.achobeta.redis.lock.strategy.SimpleLockStrategy; +import com.achobeta.util.ObjectUtil; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -103,6 +104,7 @@ public List getParticipationIdsByPaperId(Long paperId) { @Override public List getParticipationPeriods(List participationIds) { + participationIds = ObjectUtil.distinctNonNullStream(participationIds).toList(); if(CollectionUtils.isEmpty(participationIds)) { return new ArrayList<>(); } @@ -111,6 +113,7 @@ public List getParticipationPeriods(List participat @Override public List getParticipationQuestions(List participationIds) { + participationIds = ObjectUtil.distinctNonNullStream(participationIds).toList(); if(CollectionUtils.isEmpty(participationIds)) { return new ArrayList<>(); } diff --git a/src/main/java/com/achobeta/domain/recruit/service/impl/ParticipationQuestionLinkServiceImpl.java b/src/main/java/com/achobeta/domain/recruit/service/impl/ParticipationQuestionLinkServiceImpl.java index 32c0736e..463a4373 100644 --- a/src/main/java/com/achobeta/domain/recruit/service/impl/ParticipationQuestionLinkServiceImpl.java +++ b/src/main/java/com/achobeta/domain/recruit/service/impl/ParticipationQuestionLinkServiceImpl.java @@ -15,7 +15,7 @@ import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; -import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Optional; @@ -76,7 +76,7 @@ public Optional getParticipationQuestionLink(Long par @Override public void putQuestionAnswers(Long participationId, List questionAnswerDTOS) { Long recId = getActivityParticipationActId(participationId); - Map hash = new HashMap<>(); + Map hash = new LinkedHashMap<>(); // 获取答题模板 recruitmentActivityService.getQuestionsByActId(recId).forEach(questionVO -> { hash.put(questionVO.getId(), ActivityParticipationConstants.DEFAULT_ANSWER); diff --git a/src/main/java/com/achobeta/domain/resource/controller/ResourceController.java b/src/main/java/com/achobeta/domain/resource/controller/ResourceController.java index 610c2988..f3453183 100644 --- a/src/main/java/com/achobeta/domain/resource/controller/ResourceController.java +++ b/src/main/java/com/achobeta/domain/resource/controller/ResourceController.java @@ -88,7 +88,6 @@ public SystemJsonResponse getLevels() { } @PostMapping("/upload/one") - @Intercept(permit = {UserTypeEnum.ADMIN, UserTypeEnum.USER}, log = true) public SystemJsonResponse upload(@RequestPart("file") MultipartFile file) { Long userId = BaseContext.getCurrentUser().getUserId(); Long code = resourceService.upload(userId, file); @@ -96,7 +95,7 @@ public SystemJsonResponse upload(@RequestPart("file") MultipartFile file) { } @PostMapping("/upload/syncfeishu/markdown") - @Intercept(permit = {UserTypeEnum.ADMIN}, log = true) + @Intercept(permit = {UserTypeEnum.ADMIN}) public SystemJsonResponse uploadMarkdown(@RequestPart("file") MultipartFile file, @RequestParam(name = "level", required = false) Integer level) { ResourceUtil.checkText(MediaUtil.getContentType(file)); @@ -109,7 +108,7 @@ public SystemJsonResponse uploadMarkdown(@RequestPart("file") MultipartFile file } @PostMapping("/upload/syncfeishu/sheet") - @Intercept(permit = {UserTypeEnum.ADMIN}, log = true) + @Intercept(permit = {UserTypeEnum.ADMIN}) public SystemJsonResponse uploadExcel(@RequestPart("file") MultipartFile file, @RequestParam(name = "level", required = false) Integer level) { ResourceUtil.checkSheet(MediaUtil.getBytes(file)); @@ -122,7 +121,6 @@ public SystemJsonResponse uploadExcel(@RequestPart("file") MultipartFile file, } @PostMapping("/upload/image") - @Intercept(permit = {UserTypeEnum.ADMIN, UserTypeEnum.USER}, log = true) public SystemJsonResponse uploadImage(@RequestPart("file") MultipartFile file) { // 检查 ResourceUtil.checkImage(MediaUtil.getContentType(file)); @@ -132,7 +130,6 @@ public SystemJsonResponse uploadImage(@RequestPart("file") MultipartFile file) { } @PostMapping("/upload/video") - @Intercept(permit = {UserTypeEnum.ADMIN, UserTypeEnum.USER}, log = true) public SystemJsonResponse uploadVideo(@RequestPart("file") MultipartFile file) { // 检查 ResourceUtil.checkVideo(MediaUtil.getContentType(file)); @@ -142,7 +139,6 @@ public SystemJsonResponse uploadVideo(@RequestPart("file") MultipartFile file) { } @PostMapping("/upload/list") - @Intercept(permit = {UserTypeEnum.ADMIN, UserTypeEnum.USER}, log = true) public SystemJsonResponse upload(@RequestPart("file") List fileList) { Long userId = BaseContext.getCurrentUser().getUserId(); List codeList = resourceService.uploadList(userId, fileList); diff --git a/src/main/java/com/achobeta/domain/resource/service/ResourceService.java b/src/main/java/com/achobeta/domain/resource/service/ResourceService.java index cbb878f0..f44222bd 100644 --- a/src/main/java/com/achobeta/domain/resource/service/ResourceService.java +++ b/src/main/java/com/achobeta/domain/resource/service/ResourceService.java @@ -35,7 +35,7 @@ public interface ResourceService { void checkImage(Long code); - void checkAndRemoveImage(Long code, Long old); + Boolean shouldRemove(Long code, Long old); String getSystemUrl(Long code); diff --git a/src/main/java/com/achobeta/domain/resource/service/impl/ObjectStorageMinioServiceImpl.java b/src/main/java/com/achobeta/domain/resource/service/impl/ObjectStorageMinioServiceImpl.java index d7a50673..0068d329 100644 --- a/src/main/java/com/achobeta/domain/resource/service/impl/ObjectStorageMinioServiceImpl.java +++ b/src/main/java/com/achobeta/domain/resource/service/impl/ObjectStorageMinioServiceImpl.java @@ -112,7 +112,7 @@ public String compressImage(Long userId, String fileName) throws Exception { // 压缩图片 bytes = MediaUtil.compressImage(bytes); // 上传图片 - String uniqueFileName = ResourceUtil.getUniqueFileName(userId, "." + MediaUtil.COMPRESS_FORMAT_NAME); + String uniqueFileName = ResourceUtil.getUniqueFileName(userId, MediaUtil.COMPRESS_FORMAT_SUFFIX); minioEngine.upload(uniqueFileName, bytes); return uniqueFileName; } diff --git a/src/main/java/com/achobeta/domain/resource/service/impl/ResourceServiceImpl.java b/src/main/java/com/achobeta/domain/resource/service/impl/ResourceServiceImpl.java index 92fb1877..70526148 100644 --- a/src/main/java/com/achobeta/domain/resource/service/impl/ResourceServiceImpl.java +++ b/src/main/java/com/achobeta/domain/resource/service/impl/ResourceServiceImpl.java @@ -125,11 +125,12 @@ public void checkImage(Long code) { @Override @Transactional - public void checkAndRemoveImage(Long code, Long old) { + public Boolean shouldRemove(Long code, Long old) { if(!code.equals(old)) { checkImage(code); - removeKindly(old); + return Boolean.TRUE; } + return Boolean.FALSE; } @Override @@ -167,7 +168,7 @@ public Long upload(Long userId, String originalName, byte[] data, ResourceAccess if(ResourceUtil.matchType(contentType, ResourceType.IMAGE) && compressionThreshold.compareTo(data.length) <= 0) { // 压缩图片 data = MediaUtil.compressImage(data); - suffix = "." + MediaUtil.COMPRESS_FORMAT_NAME; + suffix = MediaUtil.COMPRESS_FORMAT_SUFFIX; originalName = ResourceUtil.changeSuffix(originalName, suffix); } else { // 使用原后缀 @@ -308,7 +309,7 @@ public void compressImage(Long code) throws Exception { String compressImage = objectStorageService.compressImage(resource.getUserId(), resource.getFileName()); digitalResourceService.renameDigitalResource( resource.getId(), - ResourceUtil.changeExtension(resource.getOriginalName(), MediaUtil.COMPRESS_FORMAT_NAME), + ResourceUtil.changeSuffix(resource.getOriginalName(), MediaUtil.COMPRESS_FORMAT_SUFFIX), compressImage ); } diff --git a/src/main/java/com/achobeta/domain/resumestate/controller/ResumeStateController.java b/src/main/java/com/achobeta/domain/resumestate/controller/ResumeStateController.java index d8600e89..72fe08ec 100644 --- a/src/main/java/com/achobeta/domain/resumestate/controller/ResumeStateController.java +++ b/src/main/java/com/achobeta/domain/resumestate/controller/ResumeStateController.java @@ -73,7 +73,7 @@ public SystemJsonResponse executeEvent(@PathVariable("resumeId") @NotNull Long r Long managerId = BaseContext.getCurrentUser().getUserId(); log.warn("管理员更新简历 {} 为 {} 状态", managerId, resumeStatus); // 不相等则更新 - if(!currentStatus.equals(resumeStatus)) { + if(currentStatus != resumeStatus) { resumeStateService.switchResumeState(resumeId, resumeStatus, ResumeEvent.NEXT); } return SystemJsonResponse.SYSTEM_SUCCESS(); diff --git a/src/main/java/com/achobeta/domain/resumestate/service/impl/ResumeStateServiceImpl.java b/src/main/java/com/achobeta/domain/resumestate/service/impl/ResumeStateServiceImpl.java index b104da31..5238773e 100644 --- a/src/main/java/com/achobeta/domain/resumestate/service/impl/ResumeStateServiceImpl.java +++ b/src/main/java/com/achobeta/domain/resumestate/service/impl/ResumeStateServiceImpl.java @@ -87,7 +87,7 @@ public List getProcessByResumeId(StuResume currentResume) ResumeStatus currentStatus = currentResume.getStatus(); List statusProcesses = resumeStatusProcessService.getProcessByResumeId(resumeId); // 如果没有节点或者最后一个节点不是当前状态,则推进到当前状态 - if(CollectionUtils.isEmpty(statusProcesses) || !currentStatus.equals(statusProcesses.getLast().getResumeStatus())) { + if(CollectionUtils.isEmpty(statusProcesses) || currentStatus != statusProcesses.getLast().getResumeStatus()) { ResumeStatusProcess process = resumeStatusProcessService.createResumeStatusProcess(resumeId, currentStatus, ResumeEvent.NEXT); statusProcesses.add(process); } diff --git a/src/main/java/com/achobeta/domain/schedule/controller/InterviewScheduleController.java b/src/main/java/com/achobeta/domain/schedule/controller/InterviewScheduleController.java index fdc26819..8529a910 100644 --- a/src/main/java/com/achobeta/domain/schedule/controller/InterviewScheduleController.java +++ b/src/main/java/com/achobeta/domain/schedule/controller/InterviewScheduleController.java @@ -2,10 +2,10 @@ import com.achobeta.common.SystemJsonResponse; import com.achobeta.common.annotation.Intercept; +import com.achobeta.common.annotation.MobilePhone; import com.achobeta.common.enums.UserTypeEnum; import com.achobeta.domain.interview.model.dto.InterviewConditionDTO; import com.achobeta.domain.interview.model.vo.InterviewReserveVO; -import com.achobeta.domain.recruit.model.entity.RecruitmentActivity; import com.achobeta.domain.recruit.service.ActivityParticipationService; import com.achobeta.domain.recruit.service.RecruitmentActivityService; import com.achobeta.domain.resource.constants.ResourceConstants; @@ -13,9 +13,9 @@ import com.achobeta.domain.resource.model.vo.OnlineResourceVO; import com.achobeta.domain.schedule.model.dto.ScheduleDTO; import com.achobeta.domain.schedule.model.dto.ScheduleUpdateDTO; +import com.achobeta.domain.schedule.model.dto.SituationQueryDTO; import com.achobeta.domain.schedule.model.vo.ParticipationDetailVO; import com.achobeta.domain.schedule.model.vo.ScheduleDetailVO; -import com.achobeta.domain.schedule.model.vo.ScheduleResumeVO; import com.achobeta.domain.schedule.model.vo.UserSituationVO; import com.achobeta.domain.schedule.service.InterviewScheduleService; import com.achobeta.domain.schedule.service.InterviewerService; @@ -23,7 +23,6 @@ import jakarta.validation.Valid; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Pattern; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.validation.annotation.Validated; @@ -116,42 +115,39 @@ public SystemJsonResponse getInterviewScheduleList(@Valid @RequestBody(required // 检测 Long managerId = BaseContext.getCurrentUser().getUserId(); // 查询 - List interviewScheduleList = interviewScheduleService.getInterviewScheduleList(managerId, InterviewConditionDTO.of(interviewConditionDTO)); + List interviewScheduleList = interviewScheduleService.getInterviewScheduleList(managerId, InterviewConditionDTO.of(interviewConditionDTO)); return SystemJsonResponse.SYSTEM_SUCCESS(interviewScheduleList); } @PostMapping("/list/all") public SystemJsonResponse getInterviewScheduleListAll(@Valid @RequestBody(required = false) InterviewConditionDTO interviewConditionDTO) { // 查询 - List interviewScheduleList = interviewScheduleService.getInterviewScheduleList(null, InterviewConditionDTO.of(interviewConditionDTO)); + List interviewScheduleList = interviewScheduleService.getInterviewScheduleList(null, InterviewConditionDTO.of(interviewConditionDTO)); return SystemJsonResponse.SYSTEM_SUCCESS(interviewScheduleList); } /** * 管理员查看用户参与和预约情况 * - * @param actId - * @return */ - @GetMapping("/situations/{actId}") - public SystemJsonResponse getUserParticipationSituationByActId(@PathVariable("actId") @NotNull Long actId) { + @PostMapping("/situations") + public SystemJsonResponse querySituations(@Valid @RequestBody SituationQueryDTO situationQueryDTO) { // 检测 - recruitmentActivityService.checkRecruitmentActivityExists(actId); + recruitmentActivityService.checkRecruitmentActivityExists(situationQueryDTO.getActId()); // 获取参与本次招新活动的所有用户参与和预约情况 - UserSituationVO situations = interviewScheduleService.getSituationsByActId(actId); + UserSituationVO situations = interviewScheduleService.querySituations(situationQueryDTO); return SystemJsonResponse.SYSTEM_SUCCESS(situations); } - @GetMapping("/print/situations/{actId}") - public SystemJsonResponse printUserParticipationSituationByActId(@PathVariable("actId") @NotNull Long actId, - @RequestParam(name = "level", required = false) Integer level, - @RequestParam(name = "synchronous", required = false) Boolean synchronous) { + @PostMapping("/print/situations") + public SystemJsonResponse printUserParticipationSituations(@Valid @RequestBody SituationQueryDTO situationQueryDTO, + @RequestParam(name = "level", required = false) Integer level, + @RequestParam(name = "synchronous", required = false) Boolean synchronous) { // 检测 - RecruitmentActivity activity = recruitmentActivityService.checkAndGetRecruitmentActivity(actId); ResourceAccessLevel accessLevel = Optional.ofNullable(level).map(ResourceAccessLevel::get).orElse(ResourceConstants.DEFAULT_EXCEL_ACCESS_LEVEL); // 打印表格 Long managerId = BaseContext.getCurrentUser().getUserId(); - OnlineResourceVO onlineResourceVO = interviewScheduleService.printSituations(managerId, activity, accessLevel, synchronous); + OnlineResourceVO onlineResourceVO = interviewScheduleService.printSituations(managerId, situationQueryDTO, accessLevel, synchronous); return SystemJsonResponse.SYSTEM_SUCCESS(onlineResourceVO); } @@ -176,7 +172,7 @@ public SystemJsonResponse getParticipationDetail(@PathVariable("participationId" @GetMapping("/reserve/{scheduleId}") public SystemJsonResponse interviewReserveApply(@PathVariable("scheduleId") @NotNull Long scheduleId, @RequestParam("title") @NotBlank(message = "标题不能为空") String title, - @RequestParam(name = "mobile", required = false) @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号非法") String mobile) { + @RequestParam(name = "mobile", required = false) @MobilePhone String mobile) { // 检查 interviewScheduleService.checkInterviewScheduleExists(scheduleId); // 当前管理员 diff --git a/src/main/java/com/achobeta/domain/schedule/model/dao/mapper/InterviewScheduleMapper.java b/src/main/java/com/achobeta/domain/schedule/model/dao/mapper/InterviewScheduleMapper.java index e7b914b7..ab6b937c 100644 --- a/src/main/java/com/achobeta/domain/schedule/model/dao/mapper/InterviewScheduleMapper.java +++ b/src/main/java/com/achobeta/domain/schedule/model/dao/mapper/InterviewScheduleMapper.java @@ -1,10 +1,10 @@ package com.achobeta.domain.schedule.model.dao.mapper; import com.achobeta.domain.interview.model.dto.InterviewConditionDTO; +import com.achobeta.domain.schedule.model.dto.SituationQueryDTO; import com.achobeta.domain.schedule.model.entity.InterviewSchedule; import com.achobeta.domain.schedule.model.vo.ParticipationScheduleVO; import com.achobeta.domain.schedule.model.vo.ScheduleDetailVO; -import com.achobeta.domain.schedule.model.vo.ScheduleResumeVO; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import org.apache.ibatis.annotations.Param; @@ -18,9 +18,9 @@ */ public interface InterviewScheduleMapper extends BaseMapper { - List getInterviewScheduleList(@Param("managerId") Long managerId, @Param("condition") InterviewConditionDTO interviewConditionDTO); + List getInterviewScheduleList(@Param("managerId") Long managerId, @Param("condition") InterviewConditionDTO interviewConditionDTO); - List getSituationsByActId(@Param("actId") Long actId); + List querySituations(@Param("condition") SituationQueryDTO situationQueryDTO); ParticipationScheduleVO getSituationsByParticipationId(@Param("participationId") Long participationId); diff --git a/src/main/java/com/achobeta/domain/schedule/model/dao/mapper/InterviewerMapper.java b/src/main/java/com/achobeta/domain/schedule/model/dao/mapper/InterviewerMapper.java index b74aaf3c..e212940c 100644 --- a/src/main/java/com/achobeta/domain/schedule/model/dao/mapper/InterviewerMapper.java +++ b/src/main/java/com/achobeta/domain/schedule/model/dao/mapper/InterviewerMapper.java @@ -1,7 +1,11 @@ package com.achobeta.domain.schedule.model.dao.mapper; import com.achobeta.domain.schedule.model.entity.Interviewer; +import com.achobeta.domain.schedule.model.vo.ScheduleInterviewerVO; import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; /** * @author 马拉圈 @@ -11,6 +15,9 @@ */ public interface InterviewerMapper extends BaseMapper { + List getInterviewersByScheduleIds(@Param("scheduleIds") List scheduleIds); + + } diff --git a/src/main/java/com/achobeta/domain/schedule/model/dto/SituationQueryDTO.java b/src/main/java/com/achobeta/domain/schedule/model/dto/SituationQueryDTO.java new file mode 100644 index 00000000..5c3dc36b --- /dev/null +++ b/src/main/java/com/achobeta/domain/schedule/model/dto/SituationQueryDTO.java @@ -0,0 +1,28 @@ +package com.achobeta.domain.schedule.model.dto; + +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * Created With Intellij IDEA + * Description: + * User: 马拉圈 + * Date: 2024-10-15 + * Time: 0:14 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class SituationQueryDTO { + + @NotNull(message = "活动 id 不能为空") + private Long actId; + +// @NotEmpty(message = "状态列表不能为空") + private List statusList; + +} diff --git a/src/main/java/com/achobeta/domain/schedule/model/vo/ScheduleInterviewerVO.java b/src/main/java/com/achobeta/domain/schedule/model/vo/ScheduleInterviewerVO.java new file mode 100644 index 00000000..37e7ce36 --- /dev/null +++ b/src/main/java/com/achobeta/domain/schedule/model/vo/ScheduleInterviewerVO.java @@ -0,0 +1,19 @@ +package com.achobeta.domain.schedule.model.vo; + +import lombok.Data; + +import java.util.List; + +/** + * Created With Intellij IDEA + * Description: + * User: 马拉圈 + * Date: 2024-10-14 + * Time: 16:20 + */ +@Data +public class ScheduleInterviewerVO extends ScheduleVO { + + private List interviewerVOList; + +} diff --git a/src/main/java/com/achobeta/domain/schedule/service/InterviewScheduleService.java b/src/main/java/com/achobeta/domain/schedule/service/InterviewScheduleService.java index bcaa9514..4b6a3ff7 100644 --- a/src/main/java/com/achobeta/domain/schedule/service/InterviewScheduleService.java +++ b/src/main/java/com/achobeta/domain/schedule/service/InterviewScheduleService.java @@ -2,13 +2,12 @@ import com.achobeta.domain.interview.model.dto.InterviewConditionDTO; import com.achobeta.domain.interview.model.vo.InterviewReserveVO; -import com.achobeta.domain.recruit.model.entity.RecruitmentActivity; import com.achobeta.domain.resource.enums.ResourceAccessLevel; import com.achobeta.domain.resource.model.vo.OnlineResourceVO; +import com.achobeta.domain.schedule.model.dto.SituationQueryDTO; import com.achobeta.domain.schedule.model.entity.InterviewSchedule; import com.achobeta.domain.schedule.model.vo.ParticipationDetailVO; import com.achobeta.domain.schedule.model.vo.ScheduleDetailVO; -import com.achobeta.domain.schedule.model.vo.ScheduleResumeVO; import com.achobeta.domain.schedule.model.vo.UserSituationVO; import com.baomidou.mybatisplus.extension.service.IService; @@ -26,23 +25,31 @@ public interface InterviewScheduleService extends IService { Optional getInterviewSchedule(Long scheduleId); - List getInterviewScheduleList(Long managerId, InterviewConditionDTO interviewConditionDTO); + List getInterviewScheduleList(Long managerId, InterviewConditionDTO interviewConditionDTO); /** - * @param actId 活动 id + * @param situationQueryDTO 查询条件 * @return 学生们参与情况,每一个学生包括: * 1. 学生的基础信息 * 2. 学生时间段选择情况 * 3. 学生面试预约情况 * 并统计各个时间段选中次数 */ - UserSituationVO getSituationsByActId(Long actId); + UserSituationVO querySituations(SituationQueryDTO situationQueryDTO); ScheduleDetailVO getInterviewScheduleDetail(Long scheduleId); ParticipationDetailVO getDetailActivityParticipation(Long participationId); - OnlineResourceVO printSituations(Long managerId, RecruitmentActivity activity, ResourceAccessLevel level, Boolean synchronous); + /** + * 打印参与情况为表格 + * @param managerId 管理员 id + * @param situationQueryDTO 查询条件 + * @param level 资源等级 + * @param synchronous 是否同步飞书 + * @return 链接 + */ + OnlineResourceVO printSituations(Long managerId, SituationQueryDTO situationQueryDTO, ResourceAccessLevel level, Boolean synchronous); InterviewReserveVO interviewReserveApply(Long scheduleId, String title, String mobile); diff --git a/src/main/java/com/achobeta/domain/schedule/service/InterviewerService.java b/src/main/java/com/achobeta/domain/schedule/service/InterviewerService.java index 2955d89b..1176d0d7 100644 --- a/src/main/java/com/achobeta/domain/schedule/service/InterviewerService.java +++ b/src/main/java/com/achobeta/domain/schedule/service/InterviewerService.java @@ -1,6 +1,7 @@ package com.achobeta.domain.schedule.service; import com.achobeta.domain.schedule.model.entity.Interviewer; +import com.achobeta.domain.schedule.model.vo.ScheduleInterviewerVO; import com.baomidou.mybatisplus.extension.service.IService; import java.util.List; @@ -19,6 +20,8 @@ public interface InterviewerService extends IService { List getInterviewersByScheduleId(Long scheduleId); + List getInterviewersByScheduleIds(List scheduleIds); + // 写入 ------------------------------------------ Long createInterviewer(Long managerId, Long scheduleId); diff --git a/src/main/java/com/achobeta/domain/schedule/service/impl/InterviewScheduleServiceImpl.java b/src/main/java/com/achobeta/domain/schedule/service/impl/InterviewScheduleServiceImpl.java index 50614fe4..faa76fea 100644 --- a/src/main/java/com/achobeta/domain/schedule/service/impl/InterviewScheduleServiceImpl.java +++ b/src/main/java/com/achobeta/domain/schedule/service/impl/InterviewScheduleServiceImpl.java @@ -14,6 +14,7 @@ import com.achobeta.domain.recruit.model.vo.TimePeriodCountVO; import com.achobeta.domain.recruit.model.vo.TimePeriodVO; import com.achobeta.domain.recruit.service.ActivityParticipationService; +import com.achobeta.domain.recruit.service.RecruitmentActivityService; import com.achobeta.domain.recruit.service.TimePeriodService; import com.achobeta.domain.resource.enums.ExcelTemplateEnum; import com.achobeta.domain.resource.enums.ResourceAccessLevel; @@ -21,6 +22,7 @@ import com.achobeta.domain.resource.service.ResourceService; import com.achobeta.domain.schedule.model.converter.SituationConverter; import com.achobeta.domain.schedule.model.dao.mapper.InterviewScheduleMapper; +import com.achobeta.domain.schedule.model.dto.SituationQueryDTO; import com.achobeta.domain.schedule.model.entity.InterviewSchedule; import com.achobeta.domain.schedule.model.entity.Interviewer; import com.achobeta.domain.schedule.model.vo.*; @@ -29,6 +31,7 @@ import com.achobeta.exception.GlobalServiceException; import com.achobeta.redis.lock.RedisLock; import com.achobeta.redis.lock.strategy.SimpleLockStrategy; +import com.achobeta.util.ObjectUtil; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.lark.oapi.service.vc.v1.model.ApplyReserveRespBody; import lombok.RequiredArgsConstructor; @@ -57,6 +60,8 @@ public class InterviewScheduleServiceImpl extends ServiceImpl getInterviewSchedule(Long scheduleId) { } @Override - public List getInterviewScheduleList(Long managerId, InterviewConditionDTO interviewConditionDTO) { - return interviewScheduleMapper.getInterviewScheduleList(managerId, interviewConditionDTO); + public List getInterviewScheduleList(Long managerId, InterviewConditionDTO interviewConditionDTO) { + Map scheduleDetailVOMap = interviewScheduleMapper.getInterviewScheduleList(managerId, interviewConditionDTO).stream().collect(Collectors.toMap( + ScheduleDetailVO::getId, + scheduleDetailVO -> { + // 处理 MP 给枚举默认值的情况 + List interviewVOList = scheduleDetailVO.getInterviewVOList().stream().filter(i -> Objects.nonNull(i.getId())).toList(); + scheduleDetailVO.setInterviewVOList(interviewVOList); + return scheduleDetailVO; + }, + (oldData, newData) -> newData + )); + List scheduleIds = new ArrayList<>(scheduleDetailVOMap.keySet()); + interviewerService.getInterviewersByScheduleIds(scheduleIds).stream() + .filter(scheduleDetailVO -> scheduleDetailVOMap.containsKey(scheduleDetailVO.getId())) + .forEach(scheduleDetailVO -> { + scheduleDetailVOMap.get(scheduleDetailVO.getId()).setInterviewerVOList(scheduleDetailVO.getInterviewerVOList()); + }); + return scheduleDetailVOMap.values().stream() + .sorted(Comparator.comparingLong(s -> s.getStartTime().getTime())) + .toList(); } /** @@ -98,9 +121,9 @@ public List getInterviewScheduleList(Long managerId, Interview * 4. 构造返回值返回 */ @Override - public UserSituationVO getSituationsByActId(Long actId) { + public UserSituationVO querySituations(SituationQueryDTO situationQueryDTO) { // periodId --> 时间段计数器 - Map countMap = timePeriodService.getTimePeriodsByActId(actId) + Map countMap = timePeriodService.getTimePeriodsByActId(situationQueryDTO.getActId()) .stream() .collect(Collectors.toMap( TimePeriodVO::getId, @@ -108,7 +131,9 @@ public UserSituationVO getSituationsByActId(Long actId) { (oldData, newData) -> newData) ); // participationId --> 用户预约情况 - Map userParticipationVOMap = interviewScheduleMapper.getSituationsByActId(actId) + List statusList = ObjectUtil.distinctNonNullStream(situationQueryDTO.getStatusList()).toList(); + situationQueryDTO.setStatusList(statusList); + Map userParticipationVOMap = interviewScheduleMapper.querySituations(situationQueryDTO) .stream() .collect(Collectors.toMap( ParticipationScheduleVO::getParticipationId, @@ -181,10 +206,11 @@ public ParticipationDetailVO getDetailActivityParticipation(Long participationId } @Override - public OnlineResourceVO printSituations(Long managerId, RecruitmentActivity activity, ResourceAccessLevel level, Boolean synchronous) { + public OnlineResourceVO printSituations(Long managerId, SituationQueryDTO situationQueryDTO, ResourceAccessLevel level, Boolean synchronous) { + RecruitmentActivity activity = recruitmentActivityService.checkAndGetRecruitmentActivity(situationQueryDTO.getActId()); // 构造数据 Map resultMap = new LinkedHashMap<>(); - getSituationsByActId(activity.getId()).getUserParticipationVOS().forEach(situation -> { + querySituations(situationQueryDTO).getUserParticipationVOS().forEach(situation -> { ActivitySituationExcelTemplate activitySituationExcelTemplate = new ActivitySituationExcelTemplate(); activitySituationExcelTemplate.setTimePeriodVOS(situation.getTimePeriodVOS()); activitySituationExcelTemplate.setScheduleVOS(situation.getScheduleVOS()); @@ -209,6 +235,7 @@ public OnlineResourceVO printSituations(Long managerId, RecruitmentActivity acti activity.getTitle(), synchronous ); + } @Override diff --git a/src/main/java/com/achobeta/domain/schedule/service/impl/InterviewerServiceImpl.java b/src/main/java/com/achobeta/domain/schedule/service/impl/InterviewerServiceImpl.java index bbe0e702..584d8c48 100644 --- a/src/main/java/com/achobeta/domain/schedule/service/impl/InterviewerServiceImpl.java +++ b/src/main/java/com/achobeta/domain/schedule/service/impl/InterviewerServiceImpl.java @@ -3,12 +3,16 @@ import com.achobeta.common.enums.GlobalServiceStatusCode; import com.achobeta.domain.schedule.model.dao.mapper.InterviewerMapper; import com.achobeta.domain.schedule.model.entity.Interviewer; +import com.achobeta.domain.schedule.model.vo.ScheduleInterviewerVO; import com.achobeta.domain.schedule.service.InterviewerService; import com.achobeta.exception.GlobalServiceException; +import com.achobeta.util.ObjectUtil; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; +import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -22,6 +26,8 @@ public class InterviewerServiceImpl extends ServiceImpl implements InterviewerService{ + private final InterviewerMapper interviewerMapper; + @Override public Optional getInterviewer(Long managerId, Long scheduleId) { return this.lambdaQuery() @@ -37,6 +43,15 @@ public List getInterviewersByScheduleId(Long scheduleId) { .list(); } + @Override + public List getInterviewersByScheduleIds(List scheduleIds) { + scheduleIds = ObjectUtil.distinctNonNullStream(scheduleIds).toList(); + if(CollectionUtils.isEmpty(scheduleIds)) { + return new ArrayList<>(); + } + return interviewerMapper.getInterviewersByScheduleIds(scheduleIds); + } + @Override public Long createInterviewer(Long managerId, Long scheduleId) { return getInterviewer(managerId, scheduleId).orElseGet(() -> { diff --git a/src/main/java/com/achobeta/domain/shortlink/controller/ShortLinkController.java b/src/main/java/com/achobeta/domain/shortlink/controller/ShortLinkController.java index d5fb70c8..5f3d0b72 100644 --- a/src/main/java/com/achobeta/domain/shortlink/controller/ShortLinkController.java +++ b/src/main/java/com/achobeta/domain/shortlink/controller/ShortLinkController.java @@ -1,8 +1,8 @@ package com.achobeta.domain.shortlink.controller; import com.achobeta.common.SystemJsonResponse; +import com.achobeta.common.annotation.Accessible; import com.achobeta.common.annotation.Intercept; -import com.achobeta.common.annotation.IsAccessible; import com.achobeta.common.enums.UserTypeEnum; import com.achobeta.domain.shortlink.model.dto.ShortLinkQueryDTO; import com.achobeta.domain.shortlink.model.vo.ShortLinkQueryVO; @@ -51,7 +51,7 @@ public RedirectView getShortLink(@PathVariable("code") @NotBlank String code) { */ @PostMapping("/trans") public SystemJsonResponse transferAndSaveShortLink(HttpServletRequest request, - @RequestParam("url") @NotBlank @IsAccessible(message = "链接不可访问") String url) { + @RequestParam("url") @NotBlank @Accessible(message = "链接不可访问") String url) { // 转化 String shortLinkURL = shortLinkService.transShortLinkURL(request, url); log.info("原链接:{} -> 短链接:{}", url, shortLinkURL); diff --git a/src/main/java/com/achobeta/domain/student/model/dto/StuSimpleResumeDTO.java b/src/main/java/com/achobeta/domain/student/model/dto/StuSimpleResumeDTO.java index 59658c8a..7b90253f 100644 --- a/src/main/java/com/achobeta/domain/student/model/dto/StuSimpleResumeDTO.java +++ b/src/main/java/com/achobeta/domain/student/model/dto/StuSimpleResumeDTO.java @@ -1,6 +1,7 @@ package com.achobeta.domain.student.model.dto; import com.achobeta.common.annotation.IntRange; +import com.achobeta.common.annotation.MobilePhone; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; @@ -45,7 +46,7 @@ public class StuSimpleResumeDTO implements Serializable { private String email; @NotBlank(message = "手机号不能为空") - @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号非法") + @MobilePhone private String phoneNumber; @NotBlank(message = "加入ab理由不能为空") diff --git a/src/main/java/com/achobeta/domain/student/service/impl/StuResumeServiceImpl.java b/src/main/java/com/achobeta/domain/student/service/impl/StuResumeServiceImpl.java index 4360213f..673bce5d 100644 --- a/src/main/java/com/achobeta/domain/student/service/impl/StuResumeServiceImpl.java +++ b/src/main/java/com/achobeta/domain/student/service/impl/StuResumeServiceImpl.java @@ -18,6 +18,7 @@ import com.achobeta.exception.GlobalServiceException; import com.achobeta.redis.lock.RedisLock; import com.achobeta.redis.lock.strategy.SimpleLockStrategy; +import com.achobeta.util.ObjectUtil; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -80,7 +81,8 @@ public void submitResume(StuResumeDTO stuResumeDTO, Long userId) { StuSimpleResumeDTO resumeDTO = stuResumeDTO.getStuSimpleResumeDTO(); // 检测 - resourceService.checkAndRemoveImage(resumeDTO.getImage(), stuResume.getImage()); + Long oldImage = stuResume.getImage(); + Boolean shouldRemove = resourceService.shouldRemove(resumeDTO.getImage(), oldImage); //附件列表 List stuAttachmentDTOList = stuResumeDTO.getStuAttachmentDTOList(); @@ -91,6 +93,11 @@ public void submitResume(StuResumeDTO stuResumeDTO, Long userId) { //保存附件信息 saveStuAttachment(stuAttachmentDTOList, stuResume.getId()); + + // 等提交成功后再删除 + if(Boolean.TRUE.equals(shouldRemove)) { + resourceService.removeKindly(oldImage); + } }, () -> {}, simpleLockStrategy); } @@ -152,7 +159,7 @@ private StuResume getStuResume(QueryResumeDTO queryResumeDTO) { @Transactional public void updateResumeInfo(StuResume stuResume, StuSimpleResumeDTO resumeDTO) { ResumeStatus resumeStatus = stuResume.getStatus(); - if (Objects.isNull(resumeStatus) || ResumeStatus.DRAFT.equals(resumeStatus)) { + if (Objects.isNull(resumeStatus) || ResumeStatus.DRAFT == resumeStatus) { //简历状态若为草稿则更新为待筛选 stuResume.setStatus(ResumeStatus.PENDING_SELECTION); // 添加一个简历过程节点 @@ -230,6 +237,10 @@ public StuResume checkResumeSubmitCount(StuSimpleResumeDTO resumeDTO, Long userI @Override public List queryStuList(Long batchId, List userIds) { + userIds = ObjectUtil.distinctNonNullStream(userIds).toList(); + if(CollectionUtils.isEmpty(userIds)) { + return new ArrayList<>(); + } return lambdaQuery() .eq(StuResume::getBatchId, batchId) .in(StuResume::getUserId, userIds) diff --git a/src/main/java/com/achobeta/domain/users/service/impl/MemberServiceImpl.java b/src/main/java/com/achobeta/domain/users/service/impl/MemberServiceImpl.java index 2691a82f..fe6da17b 100644 --- a/src/main/java/com/achobeta/domain/users/service/impl/MemberServiceImpl.java +++ b/src/main/java/com/achobeta/domain/users/service/impl/MemberServiceImpl.java @@ -90,6 +90,7 @@ public List queryMemberList(Long batchId) { return memberMapper.queryMemberList(batchId) .stream() .peek(member -> { + // 处理 MP 给枚举设置默认值的现象 if(Objects.isNull(member.getId())) { member.setSimpleStudentVO(null); } diff --git a/src/main/java/com/achobeta/domain/users/service/impl/UserServiceImpl.java b/src/main/java/com/achobeta/domain/users/service/impl/UserServiceImpl.java index 88421b44..8ec37b3d 100644 --- a/src/main/java/com/achobeta/domain/users/service/impl/UserServiceImpl.java +++ b/src/main/java/com/achobeta/domain/users/service/impl/UserServiceImpl.java @@ -56,12 +56,15 @@ public void updateUser(Long userId, UserDTO userDTO) { // 设置默认头像 Long avatar = userDTO.getAvatar(); UserEntity userEntity = UserConverter.INSTANCE.userDTOToUser(userDTO); - getUserById(userId).map(UserEntity::getAvatar).ifPresent(code -> { - resourceService.checkAndRemoveImage(avatar, code); - }); + Long oldAvatar = getUserById(userId).map(UserEntity::getAvatar).orElse(null); + Boolean shouldRemove = resourceService.shouldRemove(avatar, oldAvatar); // 更新 this.lambdaUpdate() .eq(UserEntity::getId, userId) .update(userEntity); + // 更新成功再删除 + if(Boolean.TRUE.equals(shouldRemove)) { + resourceService.removeKindly(oldAvatar); + } } } diff --git a/src/main/java/com/achobeta/email/provider/EmailSenderProvider.java b/src/main/java/com/achobeta/email/provider/EmailSenderProvider.java index c2942f63..fcff7b6c 100644 --- a/src/main/java/com/achobeta/email/provider/EmailSenderProvider.java +++ b/src/main/java/com/achobeta/email/provider/EmailSenderProvider.java @@ -3,18 +3,16 @@ import cn.hutool.extra.spring.SpringUtil; import com.achobeta.common.enums.GlobalServiceStatusCode; import com.achobeta.email.config.EmailSenderConfig; -import com.achobeta.email.config.EmailSenderProperties; import com.achobeta.email.provider.strategy.ProvideStrategy; import com.achobeta.exception.GlobalServiceException; +import com.achobeta.util.ObjectUtil; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.InitializingBean; import org.springframework.mail.javamail.JavaMailSenderImpl; import org.springframework.stereotype.Component; import org.springframework.util.CollectionUtils; -import java.util.ArrayList; import java.util.List; -import java.util.Optional; /** * Created With Intellij IDEA @@ -36,9 +34,7 @@ public class EmailSenderProvider implements InitializingBean { @Override public void afterPropertiesSet() throws Exception { // 构造邮件发送器实现 - this.senderList = new ArrayList<>(); - List senders = emailSenderConfig.getSenders(); - Optional.ofNullable(senders).stream().flatMap(List::stream).forEach(sender -> { + this.senderList = ObjectUtil.distinctNonNullStream(emailSenderConfig.getSenders()).map(sender -> { // 邮件发送者 JavaMailSenderImpl javaMailSender = new JavaMailSenderImpl(); javaMailSender.setHost(sender.getHost()); @@ -48,8 +44,8 @@ public void afterPropertiesSet() throws Exception { javaMailSender.setProtocol(sender.getProtocol()); javaMailSender.setDefaultEncoding(sender.getDefaultEncoding()); javaMailSender.setJavaMailProperties(sender.getProperties()); - senderList.add(javaMailSender); - }); + return javaMailSender; + }).toList(); // 若不存在一个实现则抛出异常(启动项目时) if(CollectionUtils.isEmpty(this.senderList)) { throw new GlobalServiceException(GlobalServiceStatusCode.EMAIL_SENDER_NOT_EXISTS); diff --git a/src/main/java/com/achobeta/feishu/aspect/FeishuRequestAspect.java b/src/main/java/com/achobeta/feishu/aspect/FeishuRequestAspect.java index af0b5168..68a26571 100644 --- a/src/main/java/com/achobeta/feishu/aspect/FeishuRequestAspect.java +++ b/src/main/java/com/achobeta/feishu/aspect/FeishuRequestAspect.java @@ -2,7 +2,7 @@ import com.achobeta.common.enums.GlobalServiceStatusCode; import com.achobeta.exception.GlobalServiceException; -import com.achobeta.feishu.token.FeishuTenantAccessToken; +import com.achobeta.feishu.token.FeishuTenantSession; import com.lark.oapi.core.response.BaseResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -24,7 +24,7 @@ @RequiredArgsConstructor public class FeishuRequestAspect { - private final FeishuTenantAccessToken feishuTenantAccessToken; + private final FeishuTenantSession feishuTenantSession; // 虽然不排除没有返回值的情况,idea 插件识别切点目标方法里没有无返回值的目标方法,但实际上无返回值的目标方法也会执行 AfterReturning,result 为 null 罢了 // 所以要专门排除! @@ -41,7 +41,7 @@ public void doAfterReturning(Object result) { log.info("飞书请求成功"); } else { log.info("飞书 token 刷新"); - feishuTenantAccessToken.refreshToken(); + feishuTenantSession.refreshToken(); throw new GlobalServiceException(response.getMsg(), GlobalServiceStatusCode.REQUEST_NOT_VALID); } }else { diff --git a/src/main/java/com/achobeta/feishu/token/FeishuTenantAccessToken.java b/src/main/java/com/achobeta/feishu/token/FeishuTenantSession.java similarity index 98% rename from src/main/java/com/achobeta/feishu/token/FeishuTenantAccessToken.java rename to src/main/java/com/achobeta/feishu/token/FeishuTenantSession.java index 43523eb0..50d04f79 100644 --- a/src/main/java/com/achobeta/feishu/token/FeishuTenantAccessToken.java +++ b/src/main/java/com/achobeta/feishu/token/FeishuTenantSession.java @@ -20,7 +20,7 @@ */ @Component @RequiredArgsConstructor -public class FeishuTenantAccessToken { +public class FeishuTenantSession { private final Client feishuClient; diff --git a/src/main/java/com/achobeta/handler/GlobalExceptionHandler.java b/src/main/java/com/achobeta/handler/GlobalExceptionHandler.java index 35527199..32f29484 100644 --- a/src/main/java/com/achobeta/handler/GlobalExceptionHandler.java +++ b/src/main/java/com/achobeta/handler/GlobalExceptionHandler.java @@ -1,12 +1,14 @@ package com.achobeta.handler; import com.achobeta.common.SystemJsonResponse; -import com.achobeta.common.enums.GlobalServiceStatusCode; +import com.achobeta.config.RequestIdConfig; import com.achobeta.exception.GlobalServiceException; import com.mysql.jdbc.MysqlDataTruncation; import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.ConstraintViolation; import jakarta.validation.ConstraintViolationException; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.tomcat.util.http.fileupload.FileUploadException; import org.springframework.dao.DataIntegrityViolationException; @@ -32,38 +34,43 @@ */ @Slf4j @RestControllerAdvice +@RequiredArgsConstructor public class GlobalExceptionHandler { + private final RequestIdConfig requestIdConfig; + + private void logError(HttpServletRequest request, HttpServletResponse response, Exception e) { + log.error("请求 {} 访问接口 {},错误信息 {}", + response.getHeader(requestIdConfig.getHeader()), + request.getRequestURI(), + e.getMessage() + ); + } + @ExceptionHandler(GlobalServiceException.class) - public SystemJsonResponse handleGlobalServiceException(GlobalServiceException e, HttpServletRequest request) { - String requestURI = request.getRequestURI(); - String message = e.getMessage(); - GlobalServiceStatusCode statusCode = e.getStatusCode(); - log.error("请求地址'{}', {}: '{}'", requestURI, statusCode, message); - return SystemJsonResponse.CUSTOMIZE_MSG_ERROR(statusCode, message); + public SystemJsonResponse handleGlobalServiceException(GlobalServiceException e, HttpServletRequest request, HttpServletResponse response) { + logError(request, response, e); + return SystemJsonResponse.CUSTOMIZE_MSG_ERROR(e.getStatusCode(), e.getMessage()); } @ExceptionHandler({FileUploadException.class}) - public SystemJsonResponse handleFileUploadException(FileUploadException e, HttpServletRequest request) { - String requestURI = request.getRequestURI(); - String message = e.getMessage(); - log.error("请求地址'{}', '{}'", requestURI, message); + public SystemJsonResponse handleFileUploadException(FileUploadException e, HttpServletRequest request, HttpServletResponse response) { + logError(request, response, e); + String message = "文件上传异常"; return SystemJsonResponse.CUSTOMIZE_MSG_ERROR(RESOURCE_NOT_VALID, message); } @ExceptionHandler({DataIntegrityViolationException.class}) - public SystemJsonResponse handleDataIntegrityViolationException(DataIntegrityViolationException e, HttpServletRequest request) { - String requestURI = request.getRequestURI(); - String message = e.getCause() instanceof MysqlDataTruncation ? "文本长度超出限制" : "数据异常"; - log.error("请求地址'{}', '{}', '{}'", requestURI, message, e.getMessage()); + public SystemJsonResponse handleDataIntegrityViolationException(DataIntegrityViolationException e, HttpServletRequest request, HttpServletResponse response) { + logError(request, response, e); + String message = e.getCause() instanceof MysqlDataTruncation ? "数据截断,请检查长度、范围和类型" : "数据非法"; return SystemJsonResponse.CUSTOMIZE_MSG_ERROR(SYSTEM_SERVICE_ERROR, message); } @ExceptionHandler({SQLException.class}) - public SystemJsonResponse handleSQLException(SQLException e, HttpServletRequest request) { - String requestURI = request.getRequestURI(); - String message = "数据异常"; - log.error("请求地址'{}', '{}', '{}'", requestURI, message, e.getMessage()); + public SystemJsonResponse handleSQLException(SQLException e, HttpServletRequest request, HttpServletResponse response) { + logError(request, response, e); + String message = "数据访问与交互异常"; return SystemJsonResponse.CUSTOMIZE_MSG_ERROR(SYSTEM_SERVICE_ERROR, message); } @@ -71,9 +78,8 @@ public SystemJsonResponse handleSQLException(SQLException e, HttpServletRequest * 自定义验证异常 */ @ExceptionHandler(ConstraintViolationException.class) - public SystemJsonResponse constraintViolationException(ConstraintViolationException e, HttpServletRequest request) { - String requestURI = request.getRequestURI(); - log.error("请求地址'{}', 自定义验证异常'{}'", requestURI, e.getMessage()); + public SystemJsonResponse constraintViolationException(ConstraintViolationException e, HttpServletRequest request, HttpServletResponse response) { + logError(request, response, e); String message = e.getConstraintViolations().stream() .map(ConstraintViolation::getMessage) .filter(Objects::nonNull) @@ -82,9 +88,8 @@ public SystemJsonResponse constraintViolationException(ConstraintViolationExcept } @ExceptionHandler(MethodArgumentNotValidException.class) - public SystemJsonResponse ValidationHandler(MethodArgumentNotValidException e, HttpServletRequest request) { - String requestURI = request.getRequestURI(); - log.error("请求地址'{}', 自定义验证异常'{}'", requestURI, e.getMessage()); + public SystemJsonResponse ValidationHandler(MethodArgumentNotValidException e, HttpServletRequest request, HttpServletResponse response) { + logError(request, response, e); String message = e.getBindingResult().getFieldErrors().stream() .map(FieldError::getDefaultMessage) .filter(Objects::nonNull) diff --git a/src/main/java/com/achobeta/interceptor/UserInterceptor.java b/src/main/java/com/achobeta/interceptor/UserInterceptor.java index f8222a62..7e5518d9 100644 --- a/src/main/java/com/achobeta/interceptor/UserInterceptor.java +++ b/src/main/java/com/achobeta/interceptor/UserInterceptor.java @@ -88,15 +88,16 @@ public UserHelper getUserHelper() { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + // 设置请求 id + long requestId = requestIdGenerator.nextId(); + response.setHeader(requestIdConfig.getHeader(), String.valueOf(requestId)); + //可以解决拦截器跨域问题 if (!(handler instanceof HandlerMethod)) { // 并不处理非目标方法的请求 // todo: 例如通过本服务,但不是通过目标方法获取资源的请求,而这些请求需要进行其他的处理! return Boolean.TRUE; } - // 设置请求 id - long requestId = requestIdGenerator.nextId(); - response.setHeader(requestIdConfig.getHeader(), String.valueOf(requestId)); // 获取目标方法 Method targetMethod = ((HandlerMethod) handler).getMethod(); // 获取 intercept 注解实例 @@ -123,17 +124,11 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { try { - if(handler instanceof HandlerMethod handlerMethod) { - // 获取目标方法 - Method targetMethod = handlerMethod.getMethod(); - if(InterceptHelper.shouldPrintLog(targetMethod)) { - String requestId = response.getHeader(requestIdConfig.getHeader()); - log.warn("请求 {} 访问接口 {},响应 HTTP 状态码 {},错误信息 {}", - requestId, request.getRequestURI(), response.getStatus(), - Optional.ofNullable(ex).map(Exception::getMessage).orElse(null) - ); - } - } + String requestId = response.getHeader(requestIdConfig.getHeader()); + log.warn("请求 {} 访问接口 {},响应 HTTP 状态码 {},错误信息 {}", + requestId, request.getRequestURI(), response.getStatus(), + Optional.ofNullable(ex).map(Exception::getMessage).orElse(null) + ); } finally { log.info("删除本地线程变量"); BaseContext.removeCurrentUser(); diff --git a/src/main/java/com/achobeta/template/engine/HtmlEngine.java b/src/main/java/com/achobeta/template/engine/HtmlEngine.java index 74a6bcc5..31376695 100644 --- a/src/main/java/com/achobeta/template/engine/HtmlEngine.java +++ b/src/main/java/com/achobeta/template/engine/HtmlEngine.java @@ -2,14 +2,8 @@ import com.achobeta.template.model.po.ReplaceResource; import com.achobeta.template.model.po.Resource; +import com.achobeta.template.util.MarkdownUtil; import com.achobeta.template.util.TemplateUtil; -import com.vladsch.flexmark.ext.tables.TablesExtension; -import com.vladsch.flexmark.ext.toc.TocExtension; -import com.vladsch.flexmark.html.HtmlRenderer; -import com.vladsch.flexmark.parser.Parser; -import com.vladsch.flexmark.parser.ParserEmulationProfile; -import com.vladsch.flexmark.util.ast.Node; -import com.vladsch.flexmark.util.data.MutableDataSet; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; import org.thymeleaf.TemplateEngine; @@ -41,29 +35,6 @@ public HtmlBuilder builder() { public class HtmlBuilder { - private final static MutableDataSet OPTIONS; - - private final static Parser PARSER; - - private final static HtmlRenderer HTML_RENDERER; - - static { - OPTIONS = new MutableDataSet() - .setFrom(ParserEmulationProfile.MARKDOWN) - // 支持 [TOC]目录 以及 表格 - .set(Parser.EXTENSIONS, List.of(TocExtension.create(), TablesExtension.create())) - ; - PARSER = Parser.builder(OPTIONS).build(); - HTML_RENDERER = HtmlRenderer.builder(OPTIONS).build(); - } - - private String markdownToHtml(String markdown) { - // 解析 Markdown 文本为节点 - Node document = PARSER.parse(markdown); - // 将 Markdown 节点渲染为 HTML - return HTML_RENDERER.render(document); - } - private final StringBuffer htmlBuffer = new StringBuffer(); public String build() { @@ -104,7 +75,7 @@ public HtmlBuilder append(List resourceList) { // md -> html 追加 public HtmlBuilder appendMarkdown(String markdown) { - String html = markdownToHtml(markdown); + String html = MarkdownUtil.markdownToHtml(markdown); return append(html); } @@ -156,7 +127,7 @@ public HtmlBuilder replace(List resourceList) { * 3. 紧接着调用 replaceMarkdown,传入 uniqueSymbol 和原 markdown 文本,文本转换为 html 并替换 uniqueSymbol 的位置 */ public HtmlBuilder replaceMarkdown(String target, String markdown) { - String html = markdownToHtml(markdown); + String html = MarkdownUtil.markdownToHtml(markdown); return replace(target, html); } @@ -167,7 +138,7 @@ public HtmlBuilder replaceMarkdown(ReplaceResource resource) { // md 转化为 html 替换对应的 target public HtmlBuilder replaceMarkdown(List resourceList) { - String finalHtml = TemplateUtil.replaceSafely(build(), resourceList, this::markdownToHtml); + String finalHtml = TemplateUtil.replaceSafely(build(), resourceList, MarkdownUtil::markdownToHtml); return reset(finalHtml); } diff --git a/src/main/java/com/achobeta/template/util/MarkdownUtil.java b/src/main/java/com/achobeta/template/util/MarkdownUtil.java new file mode 100644 index 00000000..cbcf394b --- /dev/null +++ b/src/main/java/com/achobeta/template/util/MarkdownUtil.java @@ -0,0 +1,108 @@ +package com.achobeta.template.util; + + +import com.vladsch.flexmark.ext.abbreviation.AbbreviationExtension; +import com.vladsch.flexmark.ext.admonition.AdmonitionExtension; +import com.vladsch.flexmark.ext.anchorlink.AnchorLinkExtension; +import com.vladsch.flexmark.ext.aside.AsideExtension; +import com.vladsch.flexmark.ext.definition.DefinitionExtension; +import com.vladsch.flexmark.ext.emoji.EmojiExtension; +import com.vladsch.flexmark.ext.footnotes.FootnoteExtension; +import com.vladsch.flexmark.ext.gfm.issues.GfmIssuesExtension; +import com.vladsch.flexmark.ext.gfm.strikethrough.StrikethroughSubscriptExtension; +import com.vladsch.flexmark.ext.gfm.tasklist.TaskListExtension; +import com.vladsch.flexmark.ext.gfm.users.GfmUsersExtension; +import com.vladsch.flexmark.ext.gitlab.GitLabExtension; +import com.vladsch.flexmark.ext.ins.InsExtension; +import com.vladsch.flexmark.ext.media.tags.MediaTagsExtension; +import com.vladsch.flexmark.ext.resizable.image.ResizableImageExtension; +import com.vladsch.flexmark.ext.superscript.SuperscriptExtension; +import com.vladsch.flexmark.ext.tables.TablesExtension; +import com.vladsch.flexmark.ext.toc.TocExtension; +import com.vladsch.flexmark.ext.typographic.TypographicExtension; +import com.vladsch.flexmark.ext.wikilink.WikiLinkExtension; +import com.vladsch.flexmark.formatter.Formatter; +import com.vladsch.flexmark.html.HtmlRenderer; +import com.vladsch.flexmark.html2md.converter.FlexmarkHtmlConverter; +import com.vladsch.flexmark.parser.Parser; +import com.vladsch.flexmark.parser.ParserEmulationProfile; +import com.vladsch.flexmark.util.ast.Node; +import com.vladsch.flexmark.util.data.MutableDataSet; + +import java.util.Arrays; + +/** + * Created With Intellij IDEA + * Description: + * User: 马拉圈 + * Date: 2024-10-17 + * Time: 11:16 + */ +public class MarkdownUtil { + + private final static MutableDataSet OPTIONS; + + private final static Parser PARSER; + + private final static HtmlRenderer HTML_RENDERER; + + private final static FlexmarkHtmlConverter HTML_CONVERTER; + + private final static Formatter MARKDOWN_FORMATTER; + + static { + OPTIONS = new MutableDataSet() + // 指定 Markdown 标准为 COMMONMARK(使用 ParserEmulationProfile.MARKDOWN 可能会有一些语法失效!) + .setFrom(ParserEmulationProfile.COMMONMARK) + .set(Parser.EXTENSIONS, Arrays.asList(new Parser.ParserExtension[]{ + // 设置一些常扩展 + TocExtension.create(), + TablesExtension.create(), + AbbreviationExtension.create(), +// AutolinkExtension.create(), + AnchorLinkExtension.create(), + AsideExtension.create(), + AdmonitionExtension.create(), + GfmIssuesExtension.create(), + GfmUsersExtension.create(), + SuperscriptExtension.create(), + StrikethroughSubscriptExtension.create(), + GitLabExtension.create(), + FootnoteExtension.create(), + TaskListExtension.create(), + InsExtension.create(), + TypographicExtension.create(), + DefinitionExtension.create(), + AbbreviationExtension.create(), + ResizableImageExtension.create(), + MediaTagsExtension.create(), + EmojiExtension.create(), + WikiLinkExtension.create(), + })).set(HtmlRenderer.SOFT_BREAK, "
\n"); + PARSER = Parser.builder(OPTIONS).build(); + HTML_RENDERER = HtmlRenderer.builder(OPTIONS).build(); + HTML_CONVERTER = FlexmarkHtmlConverter.builder(OPTIONS).build(); + MARKDOWN_FORMATTER = Formatter.builder(OPTIONS).build(); + } + + public static String markdownToHtml(String markdown) { + // 解析 Markdown 文本为节点 + Node document = PARSER.parse(markdown); + // 将 Markdown 节点渲染为 HTML + return HTML_RENDERER.render(document); + } + + public static String htmlToMarkdown(String html) { + // 将 HTML 转化为 Markdown + return HTML_CONVERTER.convert(html); + } + + public static String markdownPrettyUp(String markdown) { + + // 解析 Markdown 文本为节点 + Node document = PARSER.parse(markdown); + // 格式化 Markdown + return MARKDOWN_FORMATTER.render(document); + } + +} diff --git a/src/main/java/com/achobeta/util/HttpRequestUtil.java b/src/main/java/com/achobeta/util/HttpRequestUtil.java index 3f4b8664..4888050b 100644 --- a/src/main/java/com/achobeta/util/HttpRequestUtil.java +++ b/src/main/java/com/achobeta/util/HttpRequestUtil.java @@ -1,9 +1,11 @@ package com.achobeta.util; import cn.hutool.http.HttpRequest; +import cn.hutool.http.HttpResponse; import cn.hutool.http.HttpUtil; import cn.hutool.http.Method; import com.achobeta.common.enums.HttpRequestEnum; +import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpHeaders; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.StringUtils; @@ -22,17 +24,20 @@ /** * Created With Intellij IDEA - * Description: * User: 马拉圈 * Date: 2024-09-26 * Time: 7:41 + * Description: 用 Gson 序列化,已有业务依赖 Gson,不建议修改为其他序列化器 */ +@Slf4j public class HttpRequestUtil { public final static Map JSON_CONTENT_TYPE_HEADER = Map.of(HttpHeaders.CONTENT_TYPE, "application/json; charset=utf-8"); public static final Pattern HTTP_URL_PATTERN = Pattern.compile("^(?i)(http|https):(//(([^@\\[/?#]*)@)?(\\[[\\p{XDigit}:.]*[%\\p{Alnum}]*]|[^\\[/?#:]*)(:(\\{[^}]+\\}?|[^/?#]*))?)?([^?#]*)(\\?([^#]*))?(#(.*))?"); + private static final int MAX_REDIRECT_COUNT = 10; + public static boolean isHttpUrl(String url) { return StringUtils.hasText(url) && HTTP_URL_PATTERN.matcher(url).matches(); } @@ -41,7 +46,19 @@ public static boolean isAccessible(HttpURLConnection connection) throws IOExcept return Objects.nonNull(connection) && connection.getResponseCode() / 100 == 2; } + public static boolean isAccessible(HttpResponse response) throws IOException { + return Objects.nonNull(response) && response.getStatus() / 100 == 2; + } + public static boolean isAccessible(String url) throws IOException { + // 尝试两种方式去校验 + try { + if(isAccessible(getRequestAndExecute(url))) { + return Boolean.TRUE; + } + } catch (Exception e) { + log.warn(e.getMessage()); + } return isAccessible(openConnection(url)); } @@ -54,6 +71,19 @@ public static HttpURLConnection openConnection(String url) throws IOException { } } + public static HttpResponse getRequestAndExecute(String url) { + return getRequestAndExecute(url, 1); + } + + public static HttpResponse getRequestAndExecute(String url, int count) { + HttpResponse response = isHttpUrl(url) ? HttpUtil.createRequest(Method.GET, url).execute() : null; + if(Objects.nonNull(response) && response.getStatus() / 100 == 3 && count <= MAX_REDIRECT_COUNT) { + return getRequestAndExecute(response.header("Location"), count + 1); // Location 就是最深的那个地址了 + } else { + return response; + } + } + public static String hiddenQueryString(String url) { return StringUtils.hasText(url) && url.contains("?") ? url.substring(0, url.indexOf("?")) : url; } diff --git a/src/main/java/com/achobeta/util/HttpServletUtil.java b/src/main/java/com/achobeta/util/HttpServletUtil.java index 52b0134a..8056a793 100644 --- a/src/main/java/com/achobeta/util/HttpServletUtil.java +++ b/src/main/java/com/achobeta/util/HttpServletUtil.java @@ -29,10 +29,16 @@ public static Optional getAttributes() { return Optional.ofNullable((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()); } + /** + * 获取本次请求的 HttpServletRequest 对象 + */ public static HttpServletRequest getRequest() { return getAttributes().map(ServletRequestAttributes::getRequest).orElseThrow(GlobalServiceException::new); } + /** + * 获取本次请求的 HttpServletResponse 对象 + */ public static HttpServletResponse getResponse() { return getAttributes().map(ServletRequestAttributes::getResponse).orElseThrow(GlobalServiceException::new); } diff --git a/src/main/java/com/achobeta/util/MediaUtil.java b/src/main/java/com/achobeta/util/MediaUtil.java index a6b6d4a7..335a83e1 100644 --- a/src/main/java/com/achobeta/util/MediaUtil.java +++ b/src/main/java/com/achobeta/util/MediaUtil.java @@ -1,5 +1,6 @@ package com.achobeta.util; +import cn.hutool.http.HttpResponse; import com.achobeta.common.enums.GlobalServiceStatusCode; import com.achobeta.exception.GlobalServiceException; import lombok.extern.slf4j.Slf4j; @@ -27,6 +28,8 @@ public class MediaUtil { public final static String COMPRESS_FORMAT_NAME = "jpg"; // 压缩图片格式 + public final static String COMPRESS_FORMAT_SUFFIX = "." + COMPRESS_FORMAT_NAME; // 压缩图片格式 + public final static float COMPRESS_SCALE = 1.0f; // 压缩图片大小 public final static float COMPRESS_QUALITY = 0.5f; // 压缩图片质量 @@ -49,6 +52,16 @@ public static byte[] compressImage(byte[] bytes) { } public static InputStream getInputStream(String url) throws IOException { + // 尝试两次去获取 + try { + HttpResponse response = HttpRequestUtil.getRequestAndExecute(url); + InputStream inputStream = HttpRequestUtil.isAccessible(response) ? response.bodyStream() : null; + if(Objects.nonNull(inputStream)) { + return inputStream; + } + } catch (Exception e) { + log.warn(e.getMessage()); + } HttpURLConnection connection = HttpRequestUtil.openConnection(url); return HttpRequestUtil.isAccessible(connection) ? connection.getInputStream() : null; } diff --git a/src/main/java/com/achobeta/util/ObjectUtil.java b/src/main/java/com/achobeta/util/ObjectUtil.java index 295575d8..f87db997 100644 --- a/src/main/java/com/achobeta/util/ObjectUtil.java +++ b/src/main/java/com/achobeta/util/ObjectUtil.java @@ -88,4 +88,8 @@ public static void forEach(C object, Class fieldClazz, Consumer con .forEach(consumer); } + public static Stream distinctNonNullStream(Collection collection) { + return stream(collection).distinct().filter(Objects::nonNull); + } + } diff --git a/src/main/resources/mapper/evaluate/ext/InterviewCommentExtMapper.xml b/src/main/resources/mapper/evaluate/ext/InterviewCommentExtMapper.xml index e72295ae..696c6ce9 100644 --- a/src/main/resources/mapper/evaluate/ext/InterviewCommentExtMapper.xml +++ b/src/main/resources/mapper/evaluate/ext/InterviewCommentExtMapper.xml @@ -25,12 +25,12 @@ select c.id, c.content, c.create_time, c.update_time, m.id m_id, m.username m_username, m.nickname m_nickname,m.email m_email, - m.phone_number m_phone_number, m.avatar m_acvtar + m.phone_number m_phone_number, m.avatar m_avatar from interview i left join interview_comment c on c.interview_id = i.id and c.is_deleted = 0 and i.is_deleted = 0 left join user m on m.id = c.manager_id and m.is_deleted = 0 and c.is_deleted = 0 where i.id = #{interviewId,jdbcType=BIGINT} and c.id is not null and i.is_deleted = 0 - order by c.create_time + order by c.create_time asc diff --git a/src/main/resources/mapper/evaluate/ext/InterviewQuestionScoreExtMapper.xml b/src/main/resources/mapper/evaluate/ext/InterviewQuestionScoreExtMapper.xml index 2aacc018..afb0e0c5 100644 --- a/src/main/resources/mapper/evaluate/ext/InterviewQuestionScoreExtMapper.xml +++ b/src/main/resources/mapper/evaluate/ext/InterviewQuestionScoreExtMapper.xml @@ -14,14 +14,18 @@ select q.id, q.title, q.standard, q.create_time, q.update_time, avg(s.score) average from question q - left join interview_question_score s on q.id = s.question_id and s.is_deleted = 0 and q.is_deleted = 0 and s.score != -1 - where - q.is_deleted = 0 and q.id - - #{questionId,jdbcType=BIGINT} - + left join interview_question_score s on q.id = s.question_id and s.is_deleted = 0 and q.is_deleted = 0 and s.score >= 0 + left join interview i on i.id = s.interview_id and s.is_deleted = 0 and i.is_deleted = 0 + + q.id is not null and q.is_deleted = 0 and i.id is not null + + + #{questionId,jdbcType=BIGINT} + + + group by q.id - order by q.id + order by q.create_time diff --git a/src/main/resources/mapper/evaluate/ext/InterviewSummaryExtMapper.xml b/src/main/resources/mapper/evaluate/ext/InterviewSummaryExtMapper.xml index cb2106dc..5158bc45 100644 --- a/src/main/resources/mapper/evaluate/ext/InterviewSummaryExtMapper.xml +++ b/src/main/resources/mapper/evaluate/ext/InterviewSummaryExtMapper.xml @@ -5,18 +5,18 @@ - - + + diff --git a/src/main/resources/mapper/feedback/UserFeedbackMapper.xml b/src/main/resources/mapper/feedback/UserFeedbackMapper.xml index bd83f350..2f580a4e 100644 --- a/src/main/resources/mapper/feedback/UserFeedbackMapper.xml +++ b/src/main/resources/mapper/feedback/UserFeedbackMapper.xml @@ -9,18 +9,19 @@ - + - + + id,user_id,batch_id, - message_id,title,content, + message_id,tittle,content, attchment,is_handle,version, is_deleted,create_time,update_time diff --git a/src/main/resources/mapper/interview/ext/InterviewExtMapper.xml b/src/main/resources/mapper/interview/ext/InterviewExtMapper.xml index da48a2f1..afe8fc3a 100644 --- a/src/main/resources/mapper/interview/ext/InterviewExtMapper.xml +++ b/src/main/resources/mapper/interview/ext/InterviewExtMapper.xml @@ -17,14 +17,12 @@ - - select -- 面试预约与面试官多对多,在本条 sql 容易出现重复行 - distinct i.id, i.title, i.status, + distinct i.id, i.title, i.status, i.description, i.address, s.id s_id, s.participation_id s_participation_id, s.start_time s_start_time, s.end_time s_end_time, from user m @@ -87,6 +85,7 @@ left join recruitment_batch b on b.id = a.batch_id and a.is_deleted = 0 and b.is_deleted = 0 left join stu_resume r on r.batch_id = b.id and r.user_id = stu.id and r.is_deleted = 0 and b.is_deleted = 0 and stu.is_deleted = 0 where i.id = #{interviewId,jdbcType=BIGINT} and i.is_deleted = 0 + order by s.start_time asc, s.end_time asc diff --git a/src/main/resources/mapper/member/ext/MemberExtMapper.xml b/src/main/resources/mapper/member/ext/MemberExtMapper.xml index 1c25ae76..7bffaaaa 100644 --- a/src/main/resources/mapper/member/ext/MemberExtMapper.xml +++ b/src/main/resources/mapper/member/ext/MemberExtMapper.xml @@ -26,17 +26,18 @@ m.id, m.manager_id, m.parent_id, stu.username u_username, stu.nickname u_nickname, stu.email u_email, stu.phone_number u_phone_number, - stu.user_type u_user_type, stu.avatar u_avatar, + stu.user_type u_user_type, stu.avatar u_avatar, stu.create_time, from user stu left join member m on stu.id = m.manager_id and stu.is_deleted = 0 and m.is_deleted = 0 left join stu_resume r on r.id = m.resume_id and r.is_deleted = 0 and m.is_deleted = 0 - stu.user_type = 2 and stu.is_deleted = 0 + stu.id is not null and stu.user_type = 2 and stu.is_deleted = 0 and r.batch_id = #{batchId,jdbcType=BIGINT} + order by stu.create_time asc diff --git a/src/main/resources/mapper/message/MessageMapper.xml b/src/main/resources/mapper/message/MessageMapper.xml index 2977949a..efc5abeb 100644 --- a/src/main/resources/mapper/message/MessageMapper.xml +++ b/src/main/resources/mapper/message/MessageMapper.xml @@ -12,6 +12,7 @@ + diff --git a/src/main/resources/mapper/message/MessageTemplateMapper.xml b/src/main/resources/mapper/message/MessageTemplateMapper.xml index e11c0190..6c7daaf8 100644 --- a/src/main/resources/mapper/message/MessageTemplateMapper.xml +++ b/src/main/resources/mapper/message/MessageTemplateMapper.xml @@ -9,6 +9,7 @@ + diff --git a/src/main/resources/mapper/paper/ext/PaperQuestionLinkExtMapper.xml b/src/main/resources/mapper/paper/ext/PaperQuestionLinkExtMapper.xml index 0d43205f..d359fa5e 100644 --- a/src/main/resources/mapper/paper/ext/PaperQuestionLinkExtMapper.xml +++ b/src/main/resources/mapper/paper/ext/PaperQuestionLinkExtMapper.xml @@ -6,11 +6,12 @@ diff --git a/src/main/resources/mapper/paper/ext/QuestionPaperExtMapper.xml b/src/main/resources/mapper/paper/ext/QuestionPaperExtMapper.xml index 612ac9c7..6c027d12 100644 --- a/src/main/resources/mapper/paper/ext/QuestionPaperExtMapper.xml +++ b/src/main/resources/mapper/paper/ext/QuestionPaperExtMapper.xml @@ -16,19 +16,38 @@ resultMap="com.achobeta.domain.paper.model.dao.mapper.QuestionPaperMapper.BaseResultMap"> select -- 试卷与试卷库多对多,在本条 sql 容易出现重复行 - distinct p.id, p.title, p.description, p.create_time, p.update_time + distinct p.id, p.title, p.description, p.create_time, p.update_time, lp.create_time lp_create_time from question_paper_library l left join library_paper_link lp on l.id = lp.lib_id and l.is_deleted = 0 and lp.is_deleted = 0 left join question_paper p on p.id = lp.paper_id and p.is_deleted = 0 and lp.is_deleted = 0 p.id is not null and l.is_deleted = 0 - and l.id - + #{libId,jdbcType=BIGINT} + order by lp_create_time asc + + + diff --git a/src/main/resources/mapper/paper/ext/QuestionPaperLibraryExtMapper.xml b/src/main/resources/mapper/paper/ext/QuestionPaperLibraryExtMapper.xml index 1edfb210..9aed00d7 100644 --- a/src/main/resources/mapper/paper/ext/QuestionPaperLibraryExtMapper.xml +++ b/src/main/resources/mapper/paper/ext/QuestionPaperLibraryExtMapper.xml @@ -19,10 +19,11 @@ diff --git a/src/main/resources/mapper/question/ext/QuestionExtMapper.xml b/src/main/resources/mapper/question/ext/QuestionExtMapper.xml index 4b3145de..293cbc0b 100644 --- a/src/main/resources/mapper/question/ext/QuestionExtMapper.xml +++ b/src/main/resources/mapper/question/ext/QuestionExtMapper.xml @@ -16,18 +16,37 @@ resultMap="com.achobeta.domain.question.model.dao.mapper.QuestionMapper.BaseResultMap"> select -- 问题与题库多对多,在本条 sql 容易出现重复行 - distinct q.id, q.title, q.standard, q.create_time, q.update_time + distinct q.id, q.title, q.standard, q.create_time, q.update_time, lq.create_time lq_create_time from question_library l left join library_question_link lq on l.id = lq.lib_id and l.is_deleted = 0 and lq.is_deleted = 0 left join question q on q.id = lq.question_id and q.is_deleted = 0 and lq.is_deleted = 0 q.id is not null and l.is_deleted = 0 - and l.id - + #{libId,jdbcType=BIGINT} + order by lq_create_time asc + + + diff --git a/src/main/resources/mapper/question/ext/QuestionLibararyExtMapper.xml b/src/main/resources/mapper/question/ext/QuestionLibararyExtMapper.xml index 8101f804..a3e3bb24 100644 --- a/src/main/resources/mapper/question/ext/QuestionLibararyExtMapper.xml +++ b/src/main/resources/mapper/question/ext/QuestionLibararyExtMapper.xml @@ -19,11 +19,12 @@ diff --git a/src/main/resources/mapper/recruit/ext/ActivityParticipationExtMapper.xml b/src/main/resources/mapper/recruit/ext/ActivityParticipationExtMapper.xml index 8323ec75..d167e22c 100644 --- a/src/main/resources/mapper/recruit/ext/ActivityParticipationExtMapper.xml +++ b/src/main/resources/mapper/recruit/ext/ActivityParticipationExtMapper.xml @@ -29,12 +29,13 @@ diff --git a/src/main/resources/mapper/recruit/ext/RecruitmentBatchExtMapper.xml b/src/main/resources/mapper/recruit/ext/RecruitmentBatchExtMapper.xml index 7ccb9f79..e005fa32 100644 --- a/src/main/resources/mapper/recruit/ext/RecruitmentBatchExtMapper.xml +++ b/src/main/resources/mapper/recruit/ext/RecruitmentBatchExtMapper.xml @@ -6,10 +6,11 @@ diff --git a/src/main/resources/mapper/schedule/ext/InterviewScheduleExtMapper.xml b/src/main/resources/mapper/schedule/ext/InterviewScheduleExtMapper.xml index f23092fe..c319b840 100644 --- a/src/main/resources/mapper/schedule/ext/InterviewScheduleExtMapper.xml +++ b/src/main/resources/mapper/schedule/ext/InterviewScheduleExtMapper.xml @@ -4,15 +4,6 @@ "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> - - - - - - - - - @@ -25,7 +16,9 @@ - + + + @@ -34,19 +27,21 @@ - select -- 面试预约与面试官多对多,在本条 sql 容易出现重复行 distinct s.id, s.participation_id, s.start_time, s.end_time, + i.id i_id, i.title i_title, i.status i_status, from user m - left join interviewer i on m.id = i.manager_id and m.is_deleted = 0 and i.is_deleted = 0 - left join interview_schedule s on i.schedule_id = s.id and i.is_deleted = 0 and s.is_deleted = 0 - left join activity_participation p on p.id = s.participation_id and p.is_deleted = 0 and s.is_deleted = 0 - left join user stu on p.stu_id = stu.id and p.is_deleted = 0 and stu.is_deleted = 0 - left join recruitment_activity a on p.act_id = a.id and p.is_deleted = 0 and a.is_deleted = 0 - left join recruitment_batch b on a.batch_id = b.id and a.is_deleted = 0 and b.is_deleted = 0 - left join stu_resume r on r.batch_id = b.id and r.user_id = stu.id and r.is_deleted = 0 and b.is_deleted = 0 and stu.is_deleted = 0 + left join interviewer ir on m.id = ir.manager_id and m.is_deleted = 0 and ir.is_deleted = 0 + left join interview_schedule s on ir.schedule_id = s.id and ir.is_deleted = 0 and s.is_deleted = 0 + left join activity_participation p on p.id = s.participation_id and p.is_deleted = 0 and s.is_deleted = 0 + left join user stu on p.stu_id = stu.id and p.is_deleted = 0 and stu.is_deleted = 0 + left join recruitment_activity a on p.act_id = a.id and p.is_deleted = 0 and a.is_deleted = 0 + left join recruitment_batch b on a.batch_id = b.id and a.is_deleted = 0 and b.is_deleted = 0 + left join stu_resume r on r.batch_id = b.id and r.user_id = stu.id and r.is_deleted = 0 and b.is_deleted = 0 and stu.is_deleted = 0 + left join interview i on i.schedule_id = s.id and i.is_deleted = 0 and s.is_deleted = 0 s.id is not null and m.is_deleted = 0 @@ -62,7 +57,7 @@ order by s.start_time asc, s.end_time asc - select p.id, s.id s_id, s.participation_id s_participation_id, s.start_time s_start_time, s.end_time s_end_time, @@ -72,7 +67,15 @@ left join recruitment_batch b on a.batch_id = b.id and a.is_deleted = 0 and b.is_deleted = 0 left join stu_resume r on r.batch_id = b.id and r.user_id = stu.id and r.is_deleted = 0 and b.is_deleted = 0 and stu.is_deleted = 0 left join interview_schedule s on s.participation_id = p.id and s.is_deleted = 0 and p.is_deleted = 0 - where a.id = #{actId,jdbcType=BIGINT} and r.id is not null and p.is_deleted = 0 + + a.id = #{condition.actId,jdbcType=BIGINT} and r.id is not null and p.is_deleted = 0 + -- and r.status 放在外面,foreach 内部为空导致没有 in,可能会因为 r.status 为 0 而没有查询到一些行 + + + #{status,jdbcType=INTEGER} + + + order by s.start_time asc, s.end_time asc @@ -93,7 +96,7 @@ + select + s.id, s.participation_id, s.start_time, s.end_time, + m.id m_id, m.username m_username, m.nickname m_nickname, m.email m_email, + m.phone_number m_phone_number, m.avatar m_acvtar + from interview_schedule s + left join interviewer i on s.id = i.schedule_id and s.is_deleted = 0 and i.is_deleted = 0 + left join user m on m.id = i.manager_id and m.is_deleted = 0 and i.is_deleted = 0 + + s.id is not null and s.is_deleted = 0 and m.id is not null + + + #{scheduleId,jdbcType=BIGINT} + + + + order by s.start_time asc, s.end_time asc + + + diff --git a/src/main/resources/templates/interview-experience-model.html b/src/main/resources/templates/interview-experience-model.html index 5d4913c9..20480f96 100644 --- a/src/main/resources/templates/interview-experience-model.html +++ b/src/main/resources/templates/interview-experience-model.html @@ -30,8 +30,8 @@

-

- +

+
得分(0-10): 问题超纲 @@ -39,8 +39,6 @@
- 历史平均分: -
标准答案: