commit b66313008398c8949e3590786d19db796d81b31e Author: lensferno Date: Sun Mar 5 21:05:38 2023 +0800 一周目存档 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b3ea45a --- /dev/null +++ b/.gitignore @@ -0,0 +1,107 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr +.mvn + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +*/.flattened-pom.xml +/.flattened-pom.xml + +out + +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +/pubspec.lock +**/doc/api/ +.dart_tool/ +.packages +build/ + +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +*.log +*.class +mvnw +mvnw.cmd + +/database/ +/delombok/ +*/.mvn diff --git a/README.md b/README.md new file mode 100644 index 0000000..554b3e2 --- /dev/null +++ b/README.md @@ -0,0 +1,43 @@ +# disillusion + +某个课设的完整工程代码 + +其实核心就只是后端部分模拟的数据库以及后端对数据的处理 + +用的是java + +写得不咋的,纯纯的答辩 + +一大堆东西只是写着玩的,基本上就是写着写着写嗨了的产物 + +这里的git仓库是一周目的存档,原仓库是分开来的,这里都放到了一起 + +以后基本上不会再碰这个东西了 + +嘛,反正也没人看 + +--- + +环境要求: + +后端: +- Java 17+ + +客户端: +- Java 17+,带Javafx SDK运行环境 + +--- + +关于客户端的运行 + +由于Java高版本(8+后版本以及大部分openjdk发行版)将javafx库从标准库中分离成为了独立项目,因此如要运行客户端,需要手动加上jvm运行参数: + +`--module-path "./javafx-sdk/lib" --add-modules javafx.controls,javafx.fxml --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-exports javafx.graphics/com.sun.javafx.scene=ALL-UNNAMED` + +可参见:[【Java】错误: 缺少 JavaFX 运行时组件, 需要使用该组件来运行此应用程序 的解决方案](https://blog.csdn.net/weixin_44746306/article/details/117768300) + +Javafx sdk可在:[JavaFX](https://gluonhq.com/products/javafx/)下载 + +当然,也可以[直接下载release](https://git.ciduid.top/lensfrex/disillusion-archive/releases) + +~~真的会有人看来吗?~~ \ No newline at end of file diff --git a/disillusion-client-jfx17/pom.xml b/disillusion-client-jfx17/pom.xml new file mode 100644 index 0000000..fee31f9 --- /dev/null +++ b/disillusion-client-jfx17/pom.xml @@ -0,0 +1,140 @@ + + + 4.0.0 + + net.lensfrex + disillusion-client-jfx11 + 1.0-SNAPSHOT + disillusion-client-jfx11 + + + UTF-8 + 5.9.1 + + + + + org.junit.jupiter + junit-jupiter-api + ${junit.version} + test + + + org.junit.jupiter + junit-jupiter-engine + ${junit.version} + test + + + + + com.jfoenix + jfoenix + 9.0.10 + + + + com.squareup.okhttp3 + okhttp + 4.10.0 + + + org.projectlombok + lombok + 1.18.24 + + + ch.qos.logback + logback-classic + 1.4.5 + + + + cn.hutool + hutool-core + 5.8.13 + + + + cn.hutool + hutool-json + 5.8.12 + + + + cn.hutool + hutool-crypto + 5.8.13 + + + net.coobird + thumbnailator + 0.4.19 + + + + io.github.typhon0 + AnimateFX + 1.2.4 + + + + + org.openjfx + javafx + 17 + pom + + + org.openjfx + javafx-base + 17 + + + + org.openjfx + javafx-controls + 17 + + + org.openjfx + javafx-fxml + 17 + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 17 + 17 + + 3.8.1 + + + org.openjfx + javafx-maven-plugin + 0.0.8 + + net.lensfrex.disillusion.client.ClientMain + + + + + com.gluonhq + gluonfx-maven-plugin + 1.0.16 + + host + net.lensfrex.disillusion.client.ClientMain + + + + + + \ No newline at end of file diff --git a/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/ClientMain.java b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/ClientMain.java new file mode 100644 index 0000000..df6b61e --- /dev/null +++ b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/ClientMain.java @@ -0,0 +1,48 @@ +package net.lensfrex.disillusion.client; + +import cn.hutool.core.thread.ThreadUtil; +import javafx.application.Application; +import javafx.fxml.FXMLLoader; +import javafx.scene.Scene; +import javafx.stage.Stage; +import net.lensfrex.disillusion.client.config.Constants; +import net.lensfrex.disillusion.client.controller.LoginMainController; +import okhttp3.OkHttpClient; + +import java.io.File; +import java.io.IOException; + +public class ClientMain extends Application { + private static final OkHttpClient okhttp = Constants.globalClient; + + private static Stage stage; + + public static Stage getStage() { + return stage; + } + + @Override + public void start(Stage stage) throws IOException { + ClientMain.stage = stage; + + // 抗锯齿 + System.setProperty("prism.lcdtext", "false"); + File tokenFile = new File(Constants.tokenFile); + if (tokenFile.exists()) { + + } + + FXMLLoader fxmlLoader = new FXMLLoader(ClientMain.class.getResource("fxml/loginMain.fxml")); + Scene scene = new Scene(fxmlLoader.load(), 1000, 600); + stage.setTitle("disillusion - login"); + stage.setScene(scene); + stage.show(); + + LoginMainController loginMainController = fxmlLoader.getController(); + loginMainController.setMainStage(stage); + } + + public static void main(String[] args) { + launch(); + } +} \ No newline at end of file diff --git a/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/config/Constants.java b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/config/Constants.java new file mode 100644 index 0000000..5545ae3 --- /dev/null +++ b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/config/Constants.java @@ -0,0 +1,104 @@ +package net.lensfrex.disillusion.client.config; + +import okhttp3.OkHttpClient; + +import java.net.InetSocketAddress; +import java.net.Proxy; + +public class Constants { + public static final String tokenFile = "token"; + + public static final String numberPattern = "^\\d+$"; + +// public static final OkHttpClient globalClient = new OkHttpClient.Builder() +// .proxy(new Proxy(Proxy.Type.HTTP, new InetSocketAddress("127.0.0.1", 8080))) +// .build(); + + public static final OkHttpClient globalClient = new OkHttpClient(); + + public static final String SERVER_ADDRESS = "http://127.0.0.1:8888"; + public static final String BASE_URL = "/api"; + + public static class UserRequestUrls { + public static final String LOGIN_URL = SERVER_ADDRESS + BASE_URL + "/user/login"; + public static final String REGISTER_URL = SERVER_ADDRESS + BASE_URL + "/user/register"; + public static final String TOKEN_REFRESH_URL = SERVER_ADDRESS + BASE_URL + "/user/refresh"; + + public static final String USER_INFO_EDIT_URL = SERVER_ADDRESS + BASE_URL + "/user/info/edit"; + public static final String USER_INFO_VIEW_URL = SERVER_ADDRESS + BASE_URL + "/user/info/view"; + + public static final String USER_PUBLIC_INFO_VIEW_URL = SERVER_ADDRESS + BASE_URL + "/user/info/view/public"; + + public static final String USER_AVATAR_VIEW_URL = SERVER_ADDRESS + BASE_URL + "/user/info/avatar"; + public static final String USER_AVATAR_EDIT_URL = SERVER_ADDRESS + BASE_URL + "/user/info/avatar/upload"; + } + + public static class ResumeRequestUrls { + public static final String RESUME_UPLOAD_URL = SERVER_ADDRESS + BASE_URL + "/student/resume/add"; + public static final String RESUME_GET_ALL_URL = SERVER_ADDRESS + BASE_URL + "/student/resume/all"; + public static final String RESUME_DELETE_URL = SERVER_ADDRESS + BASE_URL + "/student/resume/delete"; + public static final String RESUME_FILE_URL = SERVER_ADDRESS + BASE_URL + "/student/resume/getFile"; + public static final String RESUME_UPDATE_URL = SERVER_ADDRESS + BASE_URL + "/student/resume/update"; + public static final String RESUME_VIEW_URL = SERVER_ADDRESS + BASE_URL + "/student/resume/view"; + } + + public static class ApplicationRequestUrls { + public static final String APPLICATION_PUBLISH_URL = SERVER_ADDRESS + BASE_URL + "/student/application/publish"; + public static final String APPLICATION_DELETE_URL = SERVER_ADDRESS + BASE_URL + "/student/application/delete"; + public static final String APPLICATION_STUDENT_VIEW_URL = SERVER_ADDRESS + BASE_URL + "/student/application/view"; + + public static final String APPLICATION_EMPLOYER_VIEW_URL = SERVER_ADDRESS + BASE_URL + "/employer/application/view"; + public static final String APPLICATION_DEAL_URL = SERVER_ADDRESS + BASE_URL + "/employer/application/deal"; + } + + public static class OfferRequestUrls { + + public static final String OFFER_FILTER_EMPLOYER = "by_employer"; + public static final String OFFER_FILTER_JOB_NAME = "by_job_name"; + public static final String OFFER_FILTER_SALARY = "by_salary"; + + public static final String GET_ALL_OFFER_URL = SERVER_ADDRESS + BASE_URL + "/offer/getAll"; + public static final String GET_OFFER_URL = SERVER_ADDRESS + BASE_URL + "/offer/get"; + public static final String OFFER_PROGRESS_URL = SERVER_ADDRESS + BASE_URL + "/offer/progress"; + + public static final String OFFER_GET_BY_EMPLOYER_SELF_URL = SERVER_ADDRESS + BASE_URL + "/employer/offer/view"; + public static final String OFFER_DELETE_URL = SERVER_ADDRESS + BASE_URL + "/employer/offer/delete"; + public static final String OFFER_PUBLISH_URL = SERVER_ADDRESS + BASE_URL + "/employer/offer/publish"; + public static final String OFFER_UPDATE_URL = SERVER_ADDRESS + BASE_URL + "/employer/offer/update"; + + public static final String OFFER_SEARCH_URL = SERVER_ADDRESS + BASE_URL + "/offer/search"; + } + + public static final String USER_TYPE_STUDENT = "0"; + public static final String USER_TYPE_EMPLOYER = "1"; + + public static final String sample = " 前期经过自愿报名并审核成功的学生,根据所报微专业的培养方案,自行选择相应课程。\n" + + "\n" + + " 9.认真对照培养方案,选课时务必仔细核对课程信息,确保“课程代码”、“课程名称”、“学分”这三项重要信息与培养方案要求相符。\n" + + "\n" + + " 10.禁止同一时间修读两门及以上课程,系统会提示时间冲突并禁止选课。提前了解专业教学进程(实习、实践、课程设计等)情况,不能选择与本专业其它教学任务周次有冲突的课堂。\n" + + "\n" + + " 11.因培养方案变动、转专业等原因,计划修读的课程确定不再开课的,请先查询本科生课程学分认定对照表并咨询开课学院,再进行选课。待课程修读完获得学分后,再按照本科生院发布的相关要求办理学分认定。\n" + + "\n" + + " 12.学生选课、听课、考核必须一致。学生不能参加未选择课程的学习和考核,即使参加了学习和考核,如果没有选课,也是无效的,将不记录成绩。\n" + + "\n" + + " 13.学生凭学号、密码选课,认真对待,并对自己的选课行为负责。密码必须妥善保管,不得代替他人选课,不得借用、盗用他人学号及密码选课。\n" + + "\n" + + " 14.学生登录本科教学综合管理系统需通过学校统一身份认证平台登录,登录方式、密码重置方式详见以下两个通知:https://jwc.wust.edu.cn/2022/0226/c1925a255654/page.htm、https://jwc.wust.edu.cn/2022/0302/c1925a255786/page.htm。"; + + public static class Svg { + public static final String deleteSvg = "M96 128h832v64H96zM128 256h768l-89.024 704H217.024z M384 64h256v96h-256z"; + + public static final String sendSvg = + "M1008 6.286q18.857 13.714 15.429 36.571l-146.286 877.714q-2.857 16.571-18.286 25.714-8 4.571-17.714 4.571-6.286 " + + "0-13.714-2.857l-258.857-105.714-138.286 168.571q-10.286 13.143-28 13.143-7.429 " + + "0-12.571-2.286-10.857-4-17.429-13.429t-6.571-20.857v-199.429l493.714-605.143-610.857 " + + "528.571-225.714-92.571q-21.143-8-22.857-31.429-1.143-22.857 18.286-33.714l950.857-548.571q8.571-5.143 18.286-5.143 " + + "11.429 0 20.571 6.286z"; + + public static final String viewSvg = + "M320.06 255.58m28 0l328.07 0q28 0 28 28l0 0q0 28-28 28l-328.07 0q-28 0-28-28l0 0q0-28 28-28Z " + + "M320.06 420.21m28 0l200.42 0q28 0 28 28l0 0q0 28-28 28l-200.42 0q-28 0-28-28l0 0q0-28 28-28Z " + + "M888.35 320h-56.19V135.61a72 72 0 0 0-72-72H264a72 72 0 0 0-72 72V320h-55.79a72 72 0 0 0-72 72v496.22a72 72 0 0 0 72 72h752.14a72 72 0 0 0 72-72V392a72 72 0 0 0-72-72z m0 72v47.59c-0.53 0.27-1.05 0.56-1.57 0.86L832.16 472v-80zM264 135.61h496.16v377l-151.33 88.31-96.55 55.74L264 513.33V135.61zM192 392v79.77l-55.83-32.24V392z m-55.79 496.22V522.67l56.2 32.45 298 173A43 43 0 0 0 502 732.7a35.88 35.88 0 0 0 9.52 1.51h1.61a35.58 35.58 0 0 0 11.65-2.21 42.58 42.58 0 0 0 9-3.94L645 663.17l243.35-140.5v365.55z"; + } +} diff --git a/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/CommunicativeController.java b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/CommunicativeController.java new file mode 100644 index 0000000..9d8445b --- /dev/null +++ b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/CommunicativeController.java @@ -0,0 +1,8 @@ +package net.lensfrex.disillusion.client.controller; + +// 可通信的Controller +public interface CommunicativeController { + void loadData(T initData); + void initialize(); + class InitData {} +} diff --git a/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/LoginMainController.java b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/LoginMainController.java new file mode 100644 index 0000000..dac2768 --- /dev/null +++ b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/LoginMainController.java @@ -0,0 +1,180 @@ +package net.lensfrex.disillusion.client.controller; + +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import com.jfoenix.controls.*; +import javafx.application.Platform; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.scene.Scene; +import javafx.scene.control.ToggleGroup; +import javafx.scene.layout.StackPane; +import javafx.scene.layout.VBox; +import javafx.stage.Stage; +import lombok.extern.slf4j.Slf4j; +import net.lensfrex.disillusion.client.ClientMain; +import net.lensfrex.disillusion.client.config.Constants; +import net.lensfrex.disillusion.client.controller.page.employer.EmployerMainController; +import net.lensfrex.disillusion.client.controller.page.student.StudentMainController; +import net.lensfrex.disillusion.client.util.Consumer; +import net.lensfrex.disillusion.client.util.OkhttpUtil; +import net.lensfrex.disillusion.client.util.ResponseHandler; +import net.lensfrex.disillusion.client.util.ThreadSafeDialogMaker; +import okhttp3.OkHttpClient; +import okhttp3.Response; + +import java.net.URL; + +@Slf4j +public class LoginMainController { + + @FXML + private StackPane rootPane; + + @FXML + private JFXTextField usernameTextField; + + @FXML + private JFXPasswordField passwordTextField; + + private ThreadSafeDialogMaker dialogMaker; + + private Stage mainStage; + + public void initialize() { + dialogMaker = new ThreadSafeDialogMaker(rootPane); + } + + public void setMainStage(Stage mainStage) { + this.mainStage = mainStage; + } + + @FXML + void doLogin(ActionEvent event) { + new OkhttpUtil().url(Constants.UserRequestUrls.LOGIN_URL) + .form("username", usernameTextField.getText()) + .form("password", passwordTextField.getText()) + .failCallback(e -> Platform.runLater(() -> { + dialogMaker.createMessageDialog("啊哈", "登录时发生了网络问题:\n" + e); + })) + .successCallback(loginSuccessCallback) + .executeAsync(OkhttpUtil.RequestMethod.POST); + } + + private final Consumer loginSuccessCallback = response -> { + ResponseHandler.Result result = ResponseHandler.parse(response, s -> dialogMaker.createMessageDialog("啊哈", nullSafe(s))); + if (!result.success) { + return; + } + + JSONObject data = JSONUtil.parseObj(result.data); + String token = data.getStr("token"); + String userType = data.getStr("user_type"); + + switch (userType) { + case "0" -> Platform.runLater(() -> this.jumpStudentMainWindow(usernameTextField.getText(), token)); + case "1" -> Platform.runLater(() -> this.jumpEmployMainWindow(usernameTextField.getText(), token)); + } + }; + + private static final URL studentMainWindowUrl = ClientMain.class.getResource("fxml/student/studentMain.fxml"); + + private static final URL employerMainWindowUrl = ClientMain.class.getResource("fxml/employer/employerMain.fxml"); + + private void jumpStudentMainWindow(String username, String token) { + try { + FXMLLoader fxmlLoader = new FXMLLoader(studentMainWindowUrl); + Scene scene = new Scene(fxmlLoader.load(), 1572, 832); + mainStage.setScene(scene); + scene.getStylesheets().add(ClientMain.class.getResource("css/style.css").toExternalForm()); + + StudentMainController.InitData initData = StudentMainController.InitData.builder() + .username(username) + .token(token) + .build(); + + StudentMainController controller = fxmlLoader.getController(); + controller.setData(initData); + + mainStage.setTitle("disillusion - student"); + mainStage.setOnCloseRequest(v -> System.exit(0)); + mainStage.setX(100); + mainStage.setY(100); + mainStage.show(); + } catch (Exception e) { + e.printStackTrace(); + dialogMaker.createMessageDialog("Oppos...😱>︿<", "程序内部出错...请联系@lensfrex这家伙\n调试信息:\n" + e); + } + } + + private void jumpEmployMainWindow(String username, String token) { + try { + FXMLLoader fxmlLoader = new FXMLLoader(employerMainWindowUrl); + Scene scene = new Scene(fxmlLoader.load(), 1572, 832); + mainStage.setScene(scene); + scene.getStylesheets().add(ClientMain.class.getResource("css/style.css").toExternalForm()); + + EmployerMainController controller = fxmlLoader.getController(); + controller.setData(token); + + mainStage.setTitle("disillusion - employer"); + mainStage.setOnCloseRequest(v -> System.exit(0)); + mainStage.setX(100); + mainStage.setY(100); + mainStage.show(); + } catch (Exception e) { + e.printStackTrace(); + dialogMaker.createMessageDialog("Oppos...😱>︿<", "程序内部出错...请联系@lensfrex这家伙\n调试信息:\n" + e); + } + } + + @FXML + void doRegister(ActionEvent event) { + JFXRadioButton studentChoice = new JFXRadioButton("学生"); + studentChoice.setUserData(Constants.USER_TYPE_STUDENT); + JFXRadioButton employerChoice = new JFXRadioButton("用人单位"); + employerChoice.setUserData(Constants.USER_TYPE_EMPLOYER); + + ToggleGroup userTypeGroup = new ToggleGroup(); + studentChoice.setToggleGroup(userTypeGroup); + employerChoice.setToggleGroup(userTypeGroup); + userTypeGroup.selectToggle(studentChoice); + + VBox choiceBox = new VBox(studentChoice, employerChoice); + choiceBox.setSpacing(10); + + JFXButton cancelButton = new JFXButton("取消"); + JFXButton okButton = new JFXButton("好的"); + okButton.setOnAction(actionEvent -> { + String userTypeString = userTypeGroup.getSelectedToggle().getUserData().toString(); + this.register(userTypeString); + }); + + dialogMaker.createDialog("选择要注册的用户类型", choiceBox, cancelButton, okButton); + } + + private void register(String userTypeString) { + new OkhttpUtil() + .url(Constants.UserRequestUrls.REGISTER_URL) + .form("username", usernameTextField.getText()) + .form("password", passwordTextField.getText()) + .form("userType", userTypeString) + .failCallback(e -> { + dialogMaker.createMessageDialog("啊哈", "注册时发生了网络问题:\n" + e); + }) + .successCallback(response -> { + ResponseHandler.Result result = ResponseHandler.parse(response, msg -> dialogMaker.createMessageDialog("啊哈", nullSafe(msg))); + if (!result.success) { + return; + } + + dialogMaker.createMessageDialog("Message", "好了"); + }) + .executeAsync(OkhttpUtil.RequestMethod.POST); + } + + private static String nullSafe(String str) { + return str == null ? "null" : str; + } +} diff --git a/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/components/ComponentFactory.java b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/components/ComponentFactory.java new file mode 100644 index 0000000..7a28ef3 --- /dev/null +++ b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/components/ComponentFactory.java @@ -0,0 +1,54 @@ +package net.lensfrex.disillusion.client.controller.components; + +import com.jfoenix.svg.SVGGlyph; +import javafx.animation.KeyFrame; +import javafx.animation.KeyValue; +import javafx.animation.Timeline; +import javafx.fxml.FXMLLoader; +import javafx.scene.Node; +import javafx.scene.control.Button; +import javafx.scene.paint.Color; +import javafx.util.Duration; +import net.lensfrex.disillusion.client.ClientMain; +import net.lensfrex.disillusion.client.controller.components.employer.offer.EmployerOfferDetailPane; + +import java.io.IOException; + +import static javafx.animation.Interpolator.EASE_BOTH; + +public class ComponentFactory { + public static T loadComponent(String location, Class clazz) + throws IOException, InstantiationException, IllegalAccessException { + + T controller = clazz.newInstance(); + return loadComponent(location, controller); + } + + public static T loadComponent(String location, T controller) + throws IOException { + + FXMLLoader loader = new FXMLLoader(ClientMain.class.getResource(location)); + return loadComponent(loader, controller); + } + + public static T loadComponent(FXMLLoader loader, T controller) + throws IOException { + + loader.setRoot(controller); + loader.setController(controller); + + return loader.load(); + } + + public static void setCardIcon(Button button, String svg) { + SVGGlyph glyph = new SVGGlyph(-1, "send", svg, Color.WHITE); + glyph.setSize(20, 20); + button.setGraphic(glyph); + + Timeline animation = new Timeline(new KeyFrame(Duration.millis(240), + new KeyValue(button.scaleXProperty(), 1, EASE_BOTH), + new KeyValue(button.scaleYProperty(), 1, EASE_BOTH))); + animation.setDelay(Duration.millis(100 + 100)); + animation.play(); + } +} diff --git a/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/components/employer/application/ApplicationDealPane.java b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/components/employer/application/ApplicationDealPane.java new file mode 100644 index 0000000..769c501 --- /dev/null +++ b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/components/employer/application/ApplicationDealPane.java @@ -0,0 +1,32 @@ +package net.lensfrex.disillusion.client.controller.components.employer.application; + +import com.jfoenix.controls.JFXRadioButton; +import javafx.fxml.FXML; +import javafx.scene.control.ToggleGroup; +import javafx.scene.layout.HBox; +import javafx.scene.layout.VBox; + +public class ApplicationDealPane extends VBox { + + @FXML + private JFXRadioButton acceptRadioButton; + + @FXML + private JFXRadioButton rejectRadioButton; + + private final ToggleGroup opinionGroup = new ToggleGroup(); + + public void initialize() { + acceptRadioButton.setToggleGroup(opinionGroup); + acceptRadioButton.setUserData("accept"); + + rejectRadioButton.setToggleGroup(opinionGroup); + rejectRadioButton.setUserData("reject"); + + opinionGroup.selectToggle(acceptRadioButton); + } + + public String getOpinion() { + return (String) opinionGroup.getSelectedToggle().getUserData(); + } +} diff --git a/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/components/employer/application/ApplicationListItem.java b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/components/employer/application/ApplicationListItem.java new file mode 100644 index 0000000..a7e991f --- /dev/null +++ b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/components/employer/application/ApplicationListItem.java @@ -0,0 +1,96 @@ +package net.lensfrex.disillusion.client.controller.components.employer.application; + +import cn.hutool.json.JSONUtil; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.HBox; +import javafx.scene.text.Text; +import lombok.extern.slf4j.Slf4j; +import net.lensfrex.disillusion.client.config.Constants; +import net.lensfrex.disillusion.client.controller.components.ComponentFactory; +import net.lensfrex.disillusion.client.data.response.ApplicationResponse; +import net.lensfrex.disillusion.client.data.response.PublicStudentInfoResponse; +import net.lensfrex.disillusion.client.util.OkhttpUtil; +import net.lensfrex.disillusion.client.util.ResponseHandler; +import net.lensfrex.disillusion.client.util.ThreadSafeDialogMaker; + +import java.io.IOException; + +@Slf4j +public class ApplicationListItem extends HBox { + private final ApplicationResponse application; + private final ThreadSafeDialogMaker dialogMaker; + private final String token; + + private PublicStudentInfoResponse studentInfo; + + @FXML + private Text studentNameText; + + @FXML + private Text describeText; + + public ApplicationListItem(ApplicationResponse application, + ThreadSafeDialogMaker dialogMaker, String token) { + + this.application = application; + this.dialogMaker = dialogMaker; + this.token = token; + } + + public void initialize() { + this.getStudentInfo(); + } + + private void getStudentInfo() { + new OkhttpUtil() + .url(Constants.UserRequestUrls.USER_PUBLIC_INFO_VIEW_URL) + .addQueryParam("user", application.getPublisherId()) + .failCallback(e -> log.warn("获取学生信息时发生网络错误:\n" + e)) + .successCallback(response -> new ResponseHandler(response) + .errorCallback(s -> log.warn("获取学生信息不成功:\n" + s)) + .successCallback(s -> { + studentInfo = JSONUtil.toBean(s, PublicStudentInfoResponse.class); + studentNameText.setText(studentInfo.getRealName()); + describeText.setText(studentInfo.getShortDescribe()); + }) + .process()) + .executeAsync(OkhttpUtil.RequestMethod.GET); + } + + @FXML + void dealApplication(ActionEvent event) throws IOException, InstantiationException, IllegalAccessException { + ApplicationDealPane dealPane = ComponentFactory.loadComponent("fxml/employer/application/applicationDealPane.fxml", + ApplicationDealPane.class); + dialogMaker.createDialogWithOKAndCancel("处理Offer申请", dealPane, event1 -> this.dealApplication(dealPane.getOpinion())); + } + + private void dealApplication(String opinion) { + new OkhttpUtil() + .url(Constants.ApplicationRequestUrls.APPLICATION_DEAL_URL) + .token(token) + .addQueryParam("id", application.getId()) + .addQueryParam("opinion", opinion) + .failCallback(e -> dialogMaker.createMessageDialog("啊哈", "处理offer申请时出现网络错误:\n" + e)) + .successCallback(response -> new ResponseHandler(response) + .errorCallback(s -> dialogMaker.createMessageDialog("啊哈", "处理offer申请失败:\n" + s)) + .successCallback(s -> dialogMaker.createMessageDialog("好好好", "处理offer申请成功:\n" + opinion)) + .process()) + .executeAsync(OkhttpUtil.RequestMethod.POST); + } + + @FXML + void viewResume(ActionEvent event) throws IOException { + ResumeDownloadPane downloadPane = ComponentFactory.loadComponent("fxml/employer/application/resumeDownloadPane.fxml", + new ResumeDownloadPane(application.getResumeId(), studentInfo.getRealName(), dialogMaker, token)); + dialogMaker.createDialogWithOneBtn("下载简历", downloadPane); + } + + @FXML + void viewStudentInfo(MouseEvent event) throws IOException { + StudentInfoPage page = ComponentFactory.loadComponent("fxml/employer/application/studentProfile.fxml", + new StudentInfoPage(studentInfo)); + dialogMaker.createDialogWithOneBtn("学生信息", page); + } +} diff --git a/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/components/employer/application/ApplicationListPage.java b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/components/employer/application/ApplicationListPage.java new file mode 100644 index 0000000..354bee5 --- /dev/null +++ b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/components/employer/application/ApplicationListPage.java @@ -0,0 +1,75 @@ +package net.lensfrex.disillusion.client.controller.components.employer.application; + +import cn.hutool.json.JSONUtil; +import javafx.application.Platform; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.layout.StackPane; +import javafx.scene.layout.VBox; +import javafx.scene.text.Font; +import javafx.scene.text.Text; +import lombok.extern.slf4j.Slf4j; +import net.lensfrex.disillusion.client.config.Constants; +import net.lensfrex.disillusion.client.controller.components.ComponentFactory; +import net.lensfrex.disillusion.client.data.response.ApplicationResponse; +import net.lensfrex.disillusion.client.data.response.OfferResponse; +import net.lensfrex.disillusion.client.util.OkhttpUtil; +import net.lensfrex.disillusion.client.util.ResponseHandler; +import net.lensfrex.disillusion.client.util.ThreadSafeDialogMaker; + +import java.io.IOException; +import java.util.List; + +@Slf4j +public class ApplicationListPage extends StackPane { + @FXML + private VBox applicationContainer; + + private final OfferResponse offer; + + private final ThreadSafeDialogMaker dialogMaker; + private final String token; + + public ApplicationListPage(ThreadSafeDialogMaker dialogMaker, String token, OfferResponse offer) { + this.dialogMaker = dialogMaker; + this.token = token; + this.offer = offer; + } + + public void initialize() { + new OkhttpUtil() + .url(Constants.ApplicationRequestUrls.APPLICATION_EMPLOYER_VIEW_URL) + .token(token) + .addQueryParam("offer_id", offer.getId()) + .failCallback(e -> dialogMaker.createMessageDialog("啊哈", "获取求职申请列表时发生了网络问题:\n" + e)) + .successCallback(response -> new ResponseHandler(response) + .errorCallback(s -> dialogMaker.createMessageDialog("啊哈", "获取求职申请不成功:\n" + s)) + .successCallback(s -> Platform.runLater(() -> this.showApplicationList(s))) + .process()) + .executeAsync(OkhttpUtil.RequestMethod.GET); + } + + private void showApplicationList(String jsonData) { + try { + List applications = JSONUtil.toList(jsonData, ApplicationResponse.class); + if (applications.isEmpty()) { + Text text = new Text("这里空空如也..."); + text.setFont(Font.font(20)); + applicationContainer.getChildren().add(text); + } + + ObservableList items = FXCollections.observableArrayList(); + for (ApplicationResponse application : applications) { + ApplicationListItem item = ComponentFactory.loadComponent("fxml/employer/application/applicationListItem.fxml", + new ApplicationListItem(application, dialogMaker, token)); + items.add(item); + } + + applicationContainer.getChildren().addAll(items); + } catch (IOException e) { + e.printStackTrace(); + log.error("加载offer申请页面时出错"); + } + } +} \ No newline at end of file diff --git a/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/components/employer/application/ApplicationOfferCard.java b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/components/employer/application/ApplicationOfferCard.java new file mode 100644 index 0000000..c115bef --- /dev/null +++ b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/components/employer/application/ApplicationOfferCard.java @@ -0,0 +1,106 @@ +package net.lensfrex.disillusion.client.controller.components.employer.application; + +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.StackPane; +import javafx.scene.text.Text; +import lombok.extern.slf4j.Slf4j; +import net.lensfrex.disillusion.client.config.Constants; +import net.lensfrex.disillusion.client.controller.components.ComponentFactory; +import net.lensfrex.disillusion.client.data.response.OfferResponse; +import net.lensfrex.disillusion.client.util.OkhttpUtil; +import net.lensfrex.disillusion.client.util.ResponseHandler; +import net.lensfrex.disillusion.client.util.ThreadSafeDialogMaker; + +@Slf4j +public class ApplicationOfferCard extends StackPane { + private final OfferResponse offer; + + private ThreadSafeDialogMaker dialogMaker; + + private String token; + + private static final String[] colors = new String[]{ + "-fx-background-color: #8F3F7E", + "-fx-background-color: #B5305F", + "-fx-background-color: #CE584A", + "-fx-background-color: #DB8D5C", + "-fx-background-color: #DA854E", + "-fx-background-color: #E9AB44", + "-fx-background-color: #99C286", + "-fx-background-color: #01A05E", + "-fx-background-color: #4A8895", + "-fx-background-color: #16669B", + "-fx-background-color: #2F65A5", + }; + + public ApplicationOfferCard(OfferResponse offer) { + this.setUserData(offer); + this.offer = offer; + } + + public void loadData(String token, ThreadSafeDialogMaker dialogMaker) { + this.token = token; + this.dialogMaker = dialogMaker; + + // 查询offer招募进度 + new OkhttpUtil() + .url(Constants.OfferRequestUrls.OFFER_PROGRESS_URL) + .token(token) + .addQueryParam("id", offer.getId()) + .failCallback(e -> log.warn("获取offer进度时出现网络错误:", e)) + .successCallback(response -> new ResponseHandler(response) + .errorCallback(s -> log.warn("获取offer进度不成功:" + s)) + .successCallback(s -> processText.setText("招募进度:" + s)) + .process() + ) + .executeAsync(OkhttpUtil.RequestMethod.GET); + } + + public void initialize() { + String color = colors[Math.abs(offer.getJobName().hashCode()) % colors.length]; + header.setStyle(header.getStyle() + color); + + jobName.setText(offer.getJobName()); + } + + @FXML + private StackPane rootPane; + + @FXML + private StackPane header; + + @FXML + private Label jobName; + + @FXML + private StackPane body; + + @FXML + private Text processText; + + @FXML + void viewApplications(MouseEvent event) { + new OkhttpUtil() + .url(Constants.ApplicationRequestUrls.APPLICATION_EMPLOYER_VIEW_URL) + .token(token) + .addQueryParam("offer_id", offer.getId()) + .failCallback(e -> dialogMaker.createMessageDialog("啊哈", "获取求职申请列表时发生了网络问题:\n" + e)) + .successCallback(response -> new ResponseHandler(response) + .errorCallback(s -> dialogMaker.createMessageDialog("啊哈", "获取求职申请不成功:\n" + s)) + .successCallback(s -> showApplicationListPage()) + .process()) + .executeAsync(OkhttpUtil.RequestMethod.GET); + } + + private void showApplicationListPage() { + try { + ApplicationListPage applicationListPage = ComponentFactory.loadComponent("fxml/employer/application/applicationListPage.fxml", + new ApplicationListPage(dialogMaker, token, offer)); + dialogMaker.createDialogWithOneBtn(String.format("投递给 '%s' 的求职申请", offer.getJobName()), applicationListPage); + } catch (Exception e) { + log.error("显示简历申请列表页面时出错", e); + } + } +} \ No newline at end of file diff --git a/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/components/employer/application/ApplicationTableItem.java b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/components/employer/application/ApplicationTableItem.java new file mode 100644 index 0000000..7e6baa5 --- /dev/null +++ b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/components/employer/application/ApplicationTableItem.java @@ -0,0 +1,24 @@ +package net.lensfrex.disillusion.client.controller.components.employer.application; + + +import com.jfoenix.controls.datamodels.treetable.RecursiveTreeObject; +import net.lensfrex.disillusion.client.data.response.ApplicationResponse; +import net.lensfrex.disillusion.client.data.response.PublicStudentInfoResponse; + +public class ApplicationTableItem extends RecursiveTreeObject { + private final ApplicationResponse application; + private final PublicStudentInfoResponse student; + + public ApplicationTableItem(ApplicationResponse application, PublicStudentInfoResponse student) { + this.application = application; + this.student = student; + } + + public ApplicationResponse getApplication() { + return application; + } + + public PublicStudentInfoResponse getStudent() { + return student; + } +} \ No newline at end of file diff --git a/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/components/employer/application/ResumeDownloadPane.java b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/components/employer/application/ResumeDownloadPane.java new file mode 100644 index 0000000..322fe0f --- /dev/null +++ b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/components/employer/application/ResumeDownloadPane.java @@ -0,0 +1,69 @@ +package net.lensfrex.disillusion.client.controller.components.employer.application; + +import cn.hutool.core.codec.Base64; +import cn.hutool.core.io.FileUtil; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.scene.layout.VBox; +import javafx.stage.DirectoryChooser; +import net.lensfrex.disillusion.client.config.Constants; +import net.lensfrex.disillusion.client.util.OkhttpUtil; +import net.lensfrex.disillusion.client.util.ResponseHandler; +import net.lensfrex.disillusion.client.util.ThreadSafeDialogMaker; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; + +public class ResumeDownloadPane extends VBox { + @FXML + private VBox rootPane; + + private final String fileId; + private final String fileName; + + private final ThreadSafeDialogMaker dialogMaker; + private final String token; + + public ResumeDownloadPane(String fileId, String fileName, ThreadSafeDialogMaker dialogMaker, String token) { + this.fileId = fileId; + this.fileName = fileName; + this.dialogMaker = dialogMaker; + this.token = token; + } + + @FXML + void download(ActionEvent event) { + DirectoryChooser directoryChooser = new DirectoryChooser(); + directoryChooser.setTitle("要将简历文件保存到哪?"); + File saveFile = directoryChooser.showDialog(rootPane.getScene().getWindow()); + if (saveFile == null) { + return; + } + + downloadResume(saveFile.getAbsolutePath(), fileName); + } + + private void downloadResume(String saveDir, String fileName) { + Path target = Paths.get(saveDir, fileName); + + new OkhttpUtil() + .url(Constants.ResumeRequestUrls.RESUME_FILE_URL) + .addHeader("token", token) + .addQueryParam("id", fileId) + .failCallback(e -> dialogMaker.createMessageDialog("啊哈", "下载简历文件时出现网络问题:\n" + e)) + .successCallback(response -> new ResponseHandler(response) + .errorCallback(s -> dialogMaker.createMessageDialog("啊哈", "下载简历文件不成功:" + s)) + .successCallback(bytes -> { + Files.write(target, Base64.decode(bytes), StandardOpenOption.CREATE, StandardOpenOption.WRITE); + Path saved = FileUtil.rename(target, + String.format("%s_%s.%s", fileName, fileId, FileUtil.getType(target.toFile())), + true); + dialogMaker.createMessageDialog("好好好", "下载完成,保存至:\n" + saved); + }) + .process()) + .executeAsync(OkhttpUtil.RequestMethod.GET); + } +} diff --git a/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/components/employer/application/StudentInfoPage.java b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/components/employer/application/StudentInfoPage.java new file mode 100644 index 0000000..6531c52 --- /dev/null +++ b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/components/employer/application/StudentInfoPage.java @@ -0,0 +1,90 @@ +package net.lensfrex.disillusion.client.controller.components.employer.application; + +import com.jfoenix.controls.JFXTextArea; +import com.jfoenix.controls.JFXTextField; +import javafx.application.Platform; +import javafx.fxml.FXML; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.HBox; +import javafx.scene.text.Text; +import lombok.extern.slf4j.Slf4j; +import net.lensfrex.disillusion.client.config.Constants; +import net.lensfrex.disillusion.client.data.response.PublicStudentInfoResponse; +import net.lensfrex.disillusion.client.util.OkhttpUtil; +import okhttp3.Response; + +import java.io.ByteArrayInputStream; + +@Slf4j +public class StudentInfoPage extends HBox { + @FXML + private HBox rootPane; + + @FXML + private ImageView avatarImageView; + + @FXML + private Text usernameText; + + @FXML + private Text signText; + + @FXML + private JFXTextField realNameTextField; + + @FXML + private JFXTextField ageTextField; + + @FXML + private Text sexText; + + @FXML + private JFXTextField contactTextField; + + @FXML + private JFXTextField signTextField; + + @FXML + private JFXTextArea describeTextArea; + + private final PublicStudentInfoResponse studentInfo; + + public StudentInfoPage(PublicStudentInfoResponse studentInfo) { + this.studentInfo = studentInfo; + } + + @FXML + public void initialize() { + realNameTextField.setText(studentInfo.getRealName()); + signText.setText(studentInfo.getShortDescribe()); + signTextField.setText(studentInfo.getShortDescribe()); + describeTextArea.setText(studentInfo.getDescribe()); + contactTextField.setText(studentInfo.getPhoneNumber()); + ageTextField.setText(String.valueOf(studentInfo.getAge())); + sexText.setText(studentInfo.getSex() == 0 ? "男" : "女"); + + new OkhttpUtil() + .url(Constants.UserRequestUrls.USER_AVATAR_VIEW_URL) + .addQueryParam("user", studentInfo.getId()) + .failCallback(e -> log.warn("获取头像时发生网络错误:\n" + e)) + .successCallback(response -> Platform.runLater(() -> setAvatar(avatarImageView, response))) + .executeAsync(OkhttpUtil.RequestMethod.GET); + } + + public static void setAvatar(ImageView avatarImageView, Response response) { + try { + assert response.body() != null; + byte[] data = response.body().bytes(); + + setAvatar(avatarImageView, data); + } catch (Exception e) { + log.warn("获取头像数据时出现异常", e); + } + } + + public static void setAvatar(ImageView avatarImageView, byte[] data) { + Image avatarImage = new Image(new ByteArrayInputStream(data)); + avatarImageView.setImage(avatarImage); + } +} diff --git a/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/components/employer/offer/EmployerOfferCard.java b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/components/employer/offer/EmployerOfferCard.java new file mode 100644 index 0000000..a3806ee --- /dev/null +++ b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/components/employer/offer/EmployerOfferCard.java @@ -0,0 +1,134 @@ +package net.lensfrex.disillusion.client.controller.components.employer.offer; + +import com.jfoenix.controls.JFXButton; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.StackPane; +import javafx.scene.text.Text; +import lombok.extern.slf4j.Slf4j; +import net.lensfrex.disillusion.client.config.Constants; +import net.lensfrex.disillusion.client.controller.components.ComponentFactory; +import net.lensfrex.disillusion.client.data.request.OfferPublishRequest; +import net.lensfrex.disillusion.client.data.response.OfferResponse; +import net.lensfrex.disillusion.client.util.Consumer; +import net.lensfrex.disillusion.client.util.OkhttpUtil; +import net.lensfrex.disillusion.client.util.ResponseHandler; +import net.lensfrex.disillusion.client.util.ThreadSafeDialogMaker; + +import java.io.IOException; + +@Slf4j +public class EmployerOfferCard extends StackPane { + private final OfferResponse offer; + + private ThreadSafeDialogMaker dialogMaker; + + private String token; + + private Consumer deleteCallback; + private Consumer updateCallback; + + private static final String[] colors = new String[]{ + "-fx-background-color: #8F3F7E", + "-fx-background-color: #B5305F", + "-fx-background-color: #CE584A", + "-fx-background-color: #DB8D5C", + "-fx-background-color: #DA854E", + "-fx-background-color: #E9AB44", + "-fx-background-color: #99C286", + "-fx-background-color: #01A05E", + "-fx-background-color: #4A8895", + "-fx-background-color: #16669B", + "-fx-background-color: #2F65A5", + }; + + public EmployerOfferCard(OfferResponse offer) { + this.setUserData(offer); + this.offer = offer; + } + + public void loadData(String token, ThreadSafeDialogMaker dialogMaker, + Consumer deleteCallback, Consumer updateCallback) { + this.token = token; + this.dialogMaker = dialogMaker; + this.deleteCallback = deleteCallback; + this.updateCallback = updateCallback; + + new OkhttpUtil() + .url(Constants.OfferRequestUrls.OFFER_PROGRESS_URL) + .token(token) + .addQueryParam("id", offer.getId()) + .failCallback(e -> log.warn("获取offer进度时出现网络错误:", e)) + .successCallback(response -> new ResponseHandler(response) + .errorCallback(s -> log.warn("获取offer进度不成功:" + s)) + .successCallback(s -> processText.setText("招募进度:" + s)) + .process()) + .executeAsync(OkhttpUtil.RequestMethod.GET); + } + + public void initialize() { + String color = colors[Math.abs(offer.getJobName().hashCode()) % colors.length]; + header.setStyle(header.getStyle() + color); + + jobName.setText(offer.getJobName()); + + ComponentFactory.setCardIcon(offerDeleteBtn, Constants.Svg.deleteSvg); + } + + @FXML + private StackPane rootPane; + + @FXML + private StackPane header; + + @FXML + private Label jobName; + + @FXML + private StackPane body; + + @FXML + private Text processText; + + @FXML + private JFXButton offerDeleteBtn; + + @FXML + void deleteOffer(ActionEvent event) { + dialogMaker.createDialogWithOKAndCancel("Really?", String.format("真的要删除Offer '%s'", offer.getJobName()), event1 -> new OkhttpUtil() + .url(Constants.OfferRequestUrls.OFFER_DELETE_URL) + .token(token) + .addQueryParam("id", offer.getId()) + .failCallback(e -> dialogMaker.createMessageDialog("啊哈", "删除offer时出现网络问题:\n" + e)) + .successCallback(response -> new ResponseHandler(response) + .errorCallback(s -> dialogMaker.createMessageDialog("啊哈", "删除offer不成功:\n" + s)) + .successCallback(deleteCallback) + .process()) + .executeAsync(OkhttpUtil.RequestMethod.POST) + ); + } + + @FXML + void viewDetail(MouseEvent event) throws IOException { + EmployerOfferDetailPane offerDetailPane = + ComponentFactory.loadComponent("fxml/employer/offer/offerDetailPane.fxml", new EmployerOfferDetailPane(offer)); + + dialogMaker.createDialogWithOKAndCancel("更新Offer信息", offerDetailPane, e -> this.updateOffer(offerDetailPane.getData())); + } + + private void updateOffer(OfferPublishRequest offerPublishRequest) { + new OkhttpUtil() + .url(Constants.OfferRequestUrls.OFFER_UPDATE_URL) + .token(token) + .addQueryParam("id", offer.getId()) + .body(offerPublishRequest) + .failCallback(e -> dialogMaker.createMessageDialog("啊哈", "更新offer信息时发生了网络问题:\n" + e)) + .successCallback(response -> new ResponseHandler(response) + .errorCallback(s -> dialogMaker.createMessageDialog("啊哈", "更新offer信息不成功:\n" + s)) + .successCallback(s -> updateCallback.accept(s)) + .process()) + .executeAsync(OkhttpUtil.RequestMethod.POST); + } +} \ No newline at end of file diff --git a/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/components/employer/offer/EmployerOfferDetailPane.java b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/components/employer/offer/EmployerOfferDetailPane.java new file mode 100644 index 0000000..982e77d --- /dev/null +++ b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/components/employer/offer/EmployerOfferDetailPane.java @@ -0,0 +1,93 @@ +package net.lensfrex.disillusion.client.controller.components.employer.offer; + +import cn.hutool.core.util.RandomUtil; +import com.jfoenix.controls.JFXTextArea; +import com.jfoenix.controls.JFXTextField; +import javafx.fxml.FXML; +import javafx.scene.layout.VBox; +import net.lensfrex.disillusion.client.config.Constants; +import net.lensfrex.disillusion.client.data.request.OfferPublishRequest; +import net.lensfrex.disillusion.client.data.response.OfferResponse; + +public class EmployerOfferDetailPane extends VBox { + @FXML + private VBox rootPane; + + @FXML + private JFXTextField jobNameTextField; + + @FXML + private JFXTextField salaryTextField; + + @FXML + private JFXTextField planNumberTextField; + + @FXML + private JFXTextField placeTextField; + + @FXML + private JFXTextField requirementTextField; + + @FXML + private JFXTextArea detailTextArea; + + private OfferResponse offer; + + // 无参构造方法,勿删 + public EmployerOfferDetailPane() { + } + + public EmployerOfferDetailPane(OfferResponse offer) { + this.offer = offer; + } + + public void initialize() { + if (offer == null) { + jobNameTextField.setText(RandomUtil.randomString(Constants.sample, RandomUtil.randomInt(3, 5))); + placeTextField.setText(RandomUtil.randomString(Constants.sample, RandomUtil.randomInt(3, 5))); + requirementTextField.setText(RandomUtil.randomString(Constants.sample, RandomUtil.randomInt(5, 10))); + salaryTextField.setText(RandomUtil.randomNumbers(5)); + planNumberTextField.setText(RandomUtil.randomNumbers(2)); + detailTextArea.setText(RandomUtil.randomString(Constants.sample, RandomUtil.randomInt(300, 700))); + return; + } + + String jobName = offer.getJobName(); + String place = offer.getPlace(); + String requirement = offer.getRequirement(); + Integer salary = offer.getSalary(); + Integer targetNumber = offer.getTargetNumber(); + String detail = offer.getDetail(); + + jobNameTextField.setText(jobName); + placeTextField.setText(place); + requirementTextField.setText(requirement); + salaryTextField.setText(salary.toString()); + planNumberTextField.setText(targetNumber.toString()); + detailTextArea.setText(detail); + } + + public OfferPublishRequest getData() { + OfferPublishRequest request = new OfferPublishRequest(); + request.setJobName(jobNameTextField.getText()); + request.setPlace(placeTextField.getText()); + request.setRequirement(requirementTextField.getText()); + request.setDetail(detailTextArea.getText()); + + String targetNumberText = planNumberTextField.getText(); + if (!targetNumberText.matches(Constants.numberPattern)) { + request.setTargetNumber(-1); + } else { + request.setTargetNumber(Integer.parseInt(targetNumberText)); + } + + String salaryText = salaryTextField.getText(); + if (!salaryText.matches(Constants.numberPattern)) { + request.setSalary(-1); + } else { + request.setSalary(Integer.parseInt(salaryText)); + } + + return request; + } +} diff --git a/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/components/student/application/ApplicationCard.java b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/components/student/application/ApplicationCard.java new file mode 100644 index 0000000..450e77c --- /dev/null +++ b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/components/student/application/ApplicationCard.java @@ -0,0 +1,140 @@ +package net.lensfrex.disillusion.client.controller.components.student.application; + +import cn.hutool.json.JSONUtil; +import com.jfoenix.controls.JFXButton; +import javafx.application.Platform; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.StackPane; +import javafx.scene.paint.Color; +import javafx.scene.text.Text; +import lombok.extern.slf4j.Slf4j; +import net.lensfrex.disillusion.client.config.Constants; +import net.lensfrex.disillusion.client.controller.components.ComponentFactory; +import net.lensfrex.disillusion.client.controller.components.student.offer.OfferDetailPane; +import net.lensfrex.disillusion.client.data.response.ApplicationResponse; +import net.lensfrex.disillusion.client.data.response.OfferResponse; +import net.lensfrex.disillusion.client.util.Consumer; +import net.lensfrex.disillusion.client.util.OkhttpUtil; +import net.lensfrex.disillusion.client.util.ResponseHandler; +import net.lensfrex.disillusion.client.util.ThreadSafeDialogMaker; + +import java.io.IOException; + +@Slf4j +public class ApplicationCard extends StackPane { + private static final String[] colors = new String[]{ + "-fx-background-color: #8F3F7E", + "-fx-background-color: #B5305F", + "-fx-background-color: #CE584A", + "-fx-background-color: #DB8D5C", + "-fx-background-color: #DA854E", + "-fx-background-color: #E9AB44", + "-fx-background-color: #99C286", + "-fx-background-color: #01A05E", + "-fx-background-color: #4A8895", + "-fx-background-color: #16669B", + "-fx-background-color: #2F65A5", + }; + + private final String token; + private final ThreadSafeDialogMaker dialogMaker; + private final ApplicationResponse application; + + private final Consumer deleteCallback; + + private OfferResponse offer; + + public ApplicationCard(String token, + ThreadSafeDialogMaker dialogMaker, + ApplicationResponse application, + Consumer deleteCallback) { + + this.token = token; + this.dialogMaker = dialogMaker; + this.application = application; + this.deleteCallback = deleteCallback; + } + + @FXML + private StackPane rootPane; + + @FXML + private StackPane header; + + @FXML + private Label jobName; + + @FXML + private StackPane body; + + @FXML + private Text statusText; + + @FXML + private JFXButton applicationDeleteBtn; + + public void initialize() { + String color = colors[Math.abs(application.getOfferId().hashCode()) % colors.length]; + header.setStyle(header.getStyle() + color); + + this.showJobName(); + + statusText.setText("状态:" + getStatusText(application.getStatus())); + if ("REJECTED".equals(application.getStatus())){ + statusText.setFill(Color.valueOf("#C62828")); + } else if ("ACCEPTED".equals(application.getStatus())) { + statusText.setFill(Color.valueOf("#2E7D32")); + } + ComponentFactory.setCardIcon(applicationDeleteBtn, Constants.Svg.deleteSvg); + } + + private void showJobName() { + new OkhttpUtil() + .url(Constants.OfferRequestUrls.GET_OFFER_URL) + .addQueryParam("id", application.getOfferId()) + .failCallback(e -> dialogMaker.createMessageDialog("啊哈", "获取职位申请时发生了错误:" + e)) + .successCallback(response -> new ResponseHandler(response).successCallback(data -> { + this.offer = JSONUtil.toBean(data, OfferResponse.class); + Platform.runLater(() -> jobName.setText(offer.getJobName())); + }).process()) + .executeAsync(OkhttpUtil.RequestMethod.GET); + } + + private String getStatusText(String status) { + switch (status) { + case "WAITING": + return "等待中"; + case "ACCEPTED": + return "已接受"; + case "REJECTED": + return "已拒绝"; + default: + return status; + } + } + + @FXML + void deleteApplication(ActionEvent event) { + dialogMaker.createDialogWithOKAndCancel("确认操作?", String.format("真的要删除 '%s' 的申请吗?", offer.getJobName()), + e -> new OkhttpUtil() + .url(Constants.ApplicationRequestUrls.APPLICATION_DELETE_URL) + .addQueryParam("id", application.getId()) + .token(token) + .failCallback(ex -> dialogMaker.createMessageDialog("啊哈", "获取职位申请时发生了错误:" + ex)) + .successCallback(response -> new ResponseHandler(response) + .errorCallback(s -> dialogMaker.createMessageDialog("啊哈", "删除offer申请不成功:\n" + s)) + .successCallback(deleteCallback) + .process()) + .executeAsync(OkhttpUtil.RequestMethod.POST)); + } + + @FXML + void viewDetail(MouseEvent event) throws IOException { + OfferDetailPane detailPage = + ComponentFactory.loadComponent("fxml/student/offer/offerDetail.fxml", new OfferDetailPane(dialogMaker, this.offer)); + dialogMaker.createDialogWithOneBtn("Offer详情", detailPage); + } +} diff --git a/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/components/student/application/ApplicationPostPane.java b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/components/student/application/ApplicationPostPane.java new file mode 100644 index 0000000..7d4f0c3 --- /dev/null +++ b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/components/student/application/ApplicationPostPane.java @@ -0,0 +1,83 @@ +package net.lensfrex.disillusion.client.controller.components.student.application; + +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import com.jfoenix.controls.JFXComboBox; +import javafx.application.Platform; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.layout.HBox; +import lombok.extern.slf4j.Slf4j; +import net.lensfrex.disillusion.client.config.Constants; +import net.lensfrex.disillusion.client.data.ResponseCode; +import net.lensfrex.disillusion.client.data.response.ResumeResponse; +import net.lensfrex.disillusion.client.util.OkhttpUtil; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Slf4j +public class ApplicationPostPane extends HBox { + private final ObservableList resumeChoiceList = FXCollections.observableArrayList(); + private Map resumeNameIdMap; + + @FXML + private JFXComboBox resumeComboBox; + + private final String token; + + public ApplicationPostPane(String token) { + this.token = token; + } + + public void initialize() { + this.getResumes(); + } + + public String getChooseResumeId() { + String selectedName = resumeComboBox.getSelectionModel().getSelectedItem(); + if (selectedName == null) { + return null; + } + + return resumeNameIdMap.get(selectedName); + } + + private void getResumes() { + new OkhttpUtil() + .url(Constants.ResumeRequestUrls.RESUME_GET_ALL_URL) + .token(token) + .successCallback(response -> { + assert response.body() != null; + String responseString = response.body().string(); + JSONObject responseJson = JSONUtil.parseObj(responseString); + + Integer code = responseJson.getInt("code"); + if (code == null || !code.equals(ResponseCode.SUCCESS.getCode())) { + String message = responseJson.getStr("message"); + log.warn("获取简历时发生错误:" + message); + } + + List resumes = JSONUtil.toList(responseJson.getJSONArray("data"), ResumeResponse.class); + Platform.runLater(() -> this.addResumeItems(resumes)); + }) + .failCallback(e -> log.warn("获取简历时发生错误:" + e)) + .executeAsync(OkhttpUtil.RequestMethod.GET); + } + + private void addResumeItems(List resumes) { + resumeNameIdMap = new HashMap<>(resumes.size()); + for (ResumeResponse resume : resumes) { + if (resume == null) { + continue; + } + resumeChoiceList.add(resume.getTitle()); + resumeNameIdMap.put(resume.getTitle(), resume.getId()); + } + + resumeComboBox.setItems(resumeChoiceList); + resumeComboBox.getSelectionModel().select(0); + } +} diff --git a/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/components/student/offer/EmployerInfoPage.java b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/components/student/offer/EmployerInfoPage.java new file mode 100644 index 0000000..24fad80 --- /dev/null +++ b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/components/student/offer/EmployerInfoPage.java @@ -0,0 +1,119 @@ +package net.lensfrex.disillusion.client.controller.components.student.offer; + +import cn.hutool.core.codec.Base64; +import cn.hutool.core.io.FileUtil; +import com.jfoenix.controls.JFXTextArea; +import com.jfoenix.controls.JFXTextField; +import javafx.application.Platform; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.HBox; +import javafx.scene.text.Text; +import javafx.stage.FileChooser; +import lombok.extern.slf4j.Slf4j; +import net.lensfrex.disillusion.client.config.Constants; +import net.lensfrex.disillusion.client.data.response.EmployerInfoResponse; +import net.lensfrex.disillusion.client.data.response.PublicEmployerInfoResponse; +import net.lensfrex.disillusion.client.util.ImageUtil; +import net.lensfrex.disillusion.client.util.OkhttpUtil; +import net.lensfrex.disillusion.client.util.ResponseHandler; +import net.lensfrex.disillusion.client.util.ThreadSafeDialogMaker; +import okhttp3.Response; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.function.Consumer; + +@Slf4j +public class EmployerInfoPage extends HBox { + @FXML + private HBox rootPane; + + @FXML + private ImageView avatarImageView; + + @FXML + private Text usernameText; + + @FXML + private Text shortDescribeText; + + @FXML + private JFXTextField employerNameTextField; + + @FXML + private JFXTextField industryTextField; + + @FXML + private JFXTextField contactTextField; + + @FXML + private JFXTextField addressTextField; + + @FXML + private JFXTextField shortDescribeTextField; + + @FXML + private JFXTextArea describeTextArea; + + private File newAvatarFile; + + private ThreadSafeDialogMaker dialogMaker; + + private final PublicEmployerInfoResponse employerInfo; + + public EmployerInfoPage(PublicEmployerInfoResponse employerInfo) { + this.employerInfo = employerInfo; + } + + public void initialize() { + this.updateDisplay(); + + new OkhttpUtil() + .url(Constants.UserRequestUrls.USER_AVATAR_VIEW_URL) + .addQueryParam("user", employerInfo.getId()) + .successCallback(response -> Platform.runLater(() -> setAvatar(avatarImageView, response))) + .executeAsync(OkhttpUtil.RequestMethod.GET); + } + + private void updateDisplay() { + String name = employerInfo.getName(); + String place = employerInfo.getPlace(); + String contract = employerInfo.getContract(); + String shortDescribe = employerInfo.getShortDescribe(); + String describe = employerInfo.getDescribe(); + String industry = employerInfo.getIndustry(); + + usernameText.setText(name); + shortDescribeText.setText(shortDescribe); + + employerNameTextField.setText(name); + industryTextField.setText(industry); + contactTextField.setText(contract); + addressTextField.setText(place); + shortDescribeTextField.setText(shortDescribe); + describeTextArea.setText(describe); + } + + public static void setAvatar(ImageView avatarImageView, Response response) { + try { + assert response.body() != null; + byte[] data = response.body().bytes(); + + setAvatar(avatarImageView, data); + } catch (Exception e) { + log.warn("获取头像数据时出现异常", e); + } + } + + public static void setAvatar(ImageView avatarImageView, byte[] data) { + Image avatarImage = new Image(new ByteArrayInputStream(data)); + avatarImageView.setImage(avatarImage); + } +} diff --git a/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/components/student/offer/OfferDetailPane.java b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/components/student/offer/OfferDetailPane.java new file mode 100644 index 0000000..fc9bde2 --- /dev/null +++ b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/components/student/offer/OfferDetailPane.java @@ -0,0 +1,95 @@ +package net.lensfrex.disillusion.client.controller.components.student.offer; + +import cn.hutool.json.JSONUtil; +import com.jfoenix.controls.JFXTextArea; +import javafx.fxml.FXML; +import javafx.scene.layout.VBox; +import javafx.scene.text.Text; +import lombok.extern.slf4j.Slf4j; +import net.lensfrex.disillusion.client.config.Constants; +import net.lensfrex.disillusion.client.controller.components.ComponentFactory; +import net.lensfrex.disillusion.client.data.response.OfferResponse; +import net.lensfrex.disillusion.client.data.response.PublicEmployerInfoResponse; +import net.lensfrex.disillusion.client.util.OkhttpUtil; +import net.lensfrex.disillusion.client.util.ResponseHandler; +import net.lensfrex.disillusion.client.util.ThreadSafeDialogMaker; + +import java.io.IOException; + +@Slf4j +public class OfferDetailPane extends VBox { + + private final ThreadSafeDialogMaker dialogMaker; + + public OfferDetailPane(ThreadSafeDialogMaker dialogMaker, OfferResponse offer, + PublicEmployerInfoResponse employerInfo) { + this.dialogMaker = dialogMaker; + this.offer = offer; + this.employerInfo = employerInfo; + } + + public OfferDetailPane(ThreadSafeDialogMaker dialogMaker, OfferResponse offer) { + this.dialogMaker = dialogMaker; + this.offer = offer; + this.getEmployerInfo(); + } + + private void getEmployerInfo() { + new OkhttpUtil() + .url(Constants.UserRequestUrls.USER_PUBLIC_INFO_VIEW_URL) + .addQueryParam("user", offer.getPublisherId()) + .failCallback(e -> log.warn("获取企业信息时发生网络错误:\n" + e)) + .successCallback(response -> new ResponseHandler(response) + .errorCallback(s -> log.warn("获取企业信息不成功:\n" + s)) + .successCallback(s -> { + employerInfo = JSONUtil.toBean(s, PublicEmployerInfoResponse.class); + employerText.setText(employerInfo.getName()); + }) + .process()) + .executeAsync(OkhttpUtil.RequestMethod.GET); + } + + public void initialize() { + if (employerInfo != null) { + employerText.setText(employerInfo.getName()); + } + + jobNameText.setText(offer.getJobName()); + placeText.setText(offer.getPlace()); + requirementText.setText(offer.getRequirement()); + salaryText.setText(offer.getSalary().toString()); + targetNumberText.setText(offer.getTargetNumber().toString()); + detailText.setText(offer.getDetail()); + } + + @FXML + void showEmployerDetail() throws IOException { + EmployerInfoPage employerInfoPage = + ComponentFactory.loadComponent("fxml/student/offer/employerProfile.fxml", new EmployerInfoPage(employerInfo)); + dialogMaker.createDialogWithOneBtn("企业详情", employerInfoPage); + } + + private final OfferResponse offer; + private PublicEmployerInfoResponse employerInfo; + + @FXML + private Text jobNameText; + + @FXML + private Text salaryText; + + @FXML + private Text employerText; + + @FXML + private Text placeText; + + @FXML + private Text targetNumberText; + + @FXML + private Text requirementText; + + @FXML + private JFXTextArea detailText; +} diff --git a/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/components/student/offer/StudentOfferCard.java b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/components/student/offer/StudentOfferCard.java new file mode 100644 index 0000000..86d1b3b --- /dev/null +++ b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/components/student/offer/StudentOfferCard.java @@ -0,0 +1,174 @@ +package net.lensfrex.disillusion.client.controller.components.student.offer; + +import cn.hutool.json.JSONUtil; +import com.jfoenix.controls.JFXButton; +import javafx.application.Platform; +import javafx.event.ActionEvent; +import javafx.event.Event; +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.layout.StackPane; +import javafx.scene.text.Text; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.extern.slf4j.Slf4j; +import net.lensfrex.disillusion.client.config.Constants; +import net.lensfrex.disillusion.client.controller.CommunicativeController; +import net.lensfrex.disillusion.client.controller.components.ComponentFactory; +import net.lensfrex.disillusion.client.controller.components.student.application.ApplicationPostPane; +import net.lensfrex.disillusion.client.data.request.ApplicationRequest; +import net.lensfrex.disillusion.client.data.response.OfferResponse; +import net.lensfrex.disillusion.client.data.response.PublicEmployerInfoResponse; +import net.lensfrex.disillusion.client.util.OkhttpUtil; +import net.lensfrex.disillusion.client.util.ResponseHandler; +import net.lensfrex.disillusion.client.util.ThreadSafeDialogMaker; + +import java.io.IOException; + +@Slf4j +public class StudentOfferCard extends StackPane implements CommunicativeController { + private final OfferResponse offer; + private PublicEmployerInfoResponse employerInfo; + + private ThreadSafeDialogMaker dialogMaker; + + private String token; + + private static final String[] colors = new String[]{ + "-fx-background-color: #8F3F7E", + "-fx-background-color: #B5305F", + "-fx-background-color: #CE584A", + "-fx-background-color: #DB8D5C", + "-fx-background-color: #DA854E", + "-fx-background-color: #E9AB44", + "-fx-background-color: #99C286", + "-fx-background-color: #01A05E", + "-fx-background-color: #4A8895", + "-fx-background-color: #16669B", + "-fx-background-color: #2F65A5", + }; + + public StudentOfferCard(OfferResponse offer) { + this.offer = offer; + } + + @Override + public void loadData(InitData initData) { + this.dialogMaker = initData.dialogMaker; + this.token = initData.token; + } + + public void initialize() { + String color = colors[Math.abs(offer.getJobName().hashCode()) % colors.length]; + header.setStyle(header.getStyle() + color); + + jobName.setText(offer.getJobName()); + + getEmployerInfo(); + workingPlaceText.setText("工作地点:" + offer.getPlace()); + + if (offer.getSalary() >= 1_0000) { + salaryText.setText("薪资:" + offer.getSalary() / 1_0000.0 + "w"); + } else { + salaryText.setText("薪资:" + offer.getSalary() / 1000.0 + "k"); + } + + ComponentFactory.setCardIcon(applicationSendBtn, Constants.Svg.sendSvg); + + new OkhttpUtil() + .url(Constants.OfferRequestUrls.OFFER_PROGRESS_URL) + .addQueryParam("id", offer.getId()) + .failCallback(e -> log.warn("获取offer进度时出现网络问题:" + e)) + .successCallback(response -> new ResponseHandler(response) + .errorCallback(s -> log.warn("获取offer进度失败:" + s)) + .successCallback(s -> Platform.runLater(() -> progressText.setText(s))) + .process()) + .executeAsync(OkhttpUtil.RequestMethod.GET); + } + + private void getEmployerInfo() { + new OkhttpUtil() + .url(Constants.UserRequestUrls.USER_PUBLIC_INFO_VIEW_URL) + .addQueryParam("user", offer.getPublisherId()) + .failCallback(e -> log.warn("获取企业信息时发生网络错误:\n" + e)) + .successCallback(response -> new ResponseHandler(response) + .errorCallback(s -> log.warn("获取企业信息不成功:\n" + s)) + .successCallback(s -> { + employerInfo = JSONUtil.toBean(s, PublicEmployerInfoResponse.class); + Platform.runLater(() -> employerNameText.setText("工作单位:" + employerInfo.getName())); + }) + .process()) + .executeAsync(OkhttpUtil.RequestMethod.GET); + } + + @Data + @Builder + @EqualsAndHashCode(callSuper = false) + public static class InitData extends CommunicativeController.InitData { + private ThreadSafeDialogMaker dialogMaker; + private String token; + } + + @FXML + private StackPane rootPane; + + @FXML + private StackPane header; + + @FXML + private Label jobName; + + @FXML + private StackPane body; + + @FXML + private Text progressText; + + @FXML + private Text salaryText; + + @FXML + private Text employerNameText; + + @FXML + private Text workingPlaceText; + + @FXML + private JFXButton applicationSendBtn; + + @FXML + void viewDetail(Event event) throws IOException { + OfferDetailPane detailPage = ComponentFactory.loadComponent("fxml/student/offer/offerDetail.fxml", + new OfferDetailPane(dialogMaker, this.offer, employerInfo)); + dialogMaker.createDialogWithOneBtn("Offer详情", detailPage); + } + + @FXML + void sendApplication(ActionEvent event) { + try { + ApplicationPostPane page = ComponentFactory.loadComponent("fxml/student/application/applicationPostPage.fxml", + new ApplicationPostPane(this.token)); + + dialogMaker.createDialogWithOKAndCancel("发送求职申请", page, + e -> this.sendApplication(page.getChooseResumeId()) + ); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private void sendApplication(String resumeId) { + ApplicationRequest request = new ApplicationRequest(offer.getId(), resumeId); + new OkhttpUtil() + .url(Constants.ApplicationRequestUrls.APPLICATION_PUBLISH_URL) + .body(request) + .addHeader("token", token) + .failCallback(e -> dialogMaker.createMessageDialog("啊哈", "发送offer申请时出现网络错误")) + .successCallback(response -> new ResponseHandler(response) + .errorCallback(s -> dialogMaker.createMessageDialog("啊哈", "offer申请发送失败:\n" + s)) + .successCallback(s -> dialogMaker.createMessageDialog("好好好", "offer申请已发送,请等待处理")) + .process()) + .executeAsync(OkhttpUtil.RequestMethod.POST); + } +} diff --git a/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/components/student/resume/ResumeCard.java b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/components/student/resume/ResumeCard.java new file mode 100644 index 0000000..562628c --- /dev/null +++ b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/components/student/resume/ResumeCard.java @@ -0,0 +1,172 @@ +package net.lensfrex.disillusion.client.controller.components.student.resume; + +import cn.hutool.core.codec.Base64; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import com.jfoenix.controls.JFXButton; +import javafx.application.Platform; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.StackPane; +import javafx.scene.text.Text; +import net.lensfrex.disillusion.client.config.Constants; +import net.lensfrex.disillusion.client.controller.components.ComponentFactory; +import net.lensfrex.disillusion.client.data.ResponseCode; +import net.lensfrex.disillusion.client.data.request.ResumeRequest; +import net.lensfrex.disillusion.client.data.response.ResumeResponse; +import net.lensfrex.disillusion.client.util.OkhttpUtil; +import net.lensfrex.disillusion.client.util.ResponseHandler; +import net.lensfrex.disillusion.client.util.ThreadSafeDialogMaker; +import okhttp3.*; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.io.IOException; +import java.util.function.Consumer; + +public class ResumeCard extends StackPane { + private static final String[] colors = new String[]{ + "-fx-background-color: #8F3F7E", + "-fx-background-color: #B5305F", + "-fx-background-color: #CE584A", + "-fx-background-color: #DB8D5C", + "-fx-background-color: #DA854E", + "-fx-background-color: #E9AB44", + "-fx-background-color: #99C286", + "-fx-background-color: #01A05E", + "-fx-background-color: #4A8895", + "-fx-background-color: #16669B", + "-fx-background-color: #2F65A5", + }; + + private static final OkHttpClient okhttpClient = Constants.globalClient; + + private final ResumeResponse resume; + + private final String token; + + private final ThreadSafeDialogMaker dialogMaker; + + private final Consumer updateCallback; + private final Consumer deleteCallback; + + public ResumeCard(ResumeResponse resume, String token, ThreadSafeDialogMaker dialogMaker, + Consumer updateCallback, Consumer deleteCallback) { + this.resume = resume; + this.token = token; + this.dialogMaker = dialogMaker; + + this.updateCallback = updateCallback; + this.deleteCallback = deleteCallback; + } + + @FXML + private StackPane rootPane; + + @FXML + private StackPane header; + + @FXML + private Label resumeNameText; + + @FXML + private StackPane body; + + @FXML + private Text remarkText; + + @FXML + private JFXButton resumeDeleteBtn; + + public void initialize() { + String color = colors[Math.abs(resume.getTitle().hashCode()) % colors.length]; + header.setStyle(header.getStyle() + color); + + resumeNameText.setText(resume.getTitle()); + remarkText.setText(resume.getRemark()); + + ComponentFactory.setCardIcon(resumeDeleteBtn, Constants.Svg.deleteSvg); + } + + @FXML + void showResumeDetail(MouseEvent event) throws IOException { + ResumeUploadPane resumeUploadPane = + ComponentFactory.loadComponent("fxml/student/resume/resumeUpload.fxml", new ResumeUploadPane(dialogMaker)); + + ResumeUploadPane.InitData initData = new ResumeUploadPane.InitData(); + initData.setName(resume.getTitle()); + initData.setRemark(resume.getRemark()); + resumeUploadPane.loadData(initData); + + dialogMaker.createDialogWithOKAndCancel("更新简历", resumeUploadPane, e -> { + try { + ResumeRequest request = new ResumeRequest(); + request.setTitle(resumeUploadPane.getResumeName()); + request.setRemark(resumeUploadPane.getRemark()); + + File resumeFile = resumeUploadPane.getResumeFile(); + if (resumeFile != null) { + request.setResumeFileBase64(Base64.encode(resumeFile)); + } + + this.updateResume(request); + } catch (Exception ex) { + ex.printStackTrace(); + dialogMaker.createMessageDialog("啊哈", "读取简历文件时发生错误...\n" + ex); + } + }); + } + + private void updateResume(ResumeRequest resumeRequest) { + String json = JSONUtil.toJsonStr(resumeRequest); + RequestBody requestBody = RequestBody.create(json, MediaType.get("application/json")); + + Request uploadRequest = new Request.Builder() + .url(Constants.ResumeRequestUrls.RESUME_UPDATE_URL + "?id=" + resume.getId()) + .addHeader("token", token) + .post(requestBody) + .build(); + + okhttpClient.newCall(uploadRequest).enqueue(resumeUpdateCallback); + } + + private final Callback resumeUpdateCallback = new Callback() { + @Override + public void onFailure(@NotNull Call call, @NotNull IOException e) { + dialogMaker.createMessageDialog("啊哈", "上传简历时发生了网络错误\n" + e); + } + + @Override + public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException { + assert response.body() != null; + String responseString = response.body().string(); + JSONObject responseJson = JSONUtil.parseObj(responseString); + + Integer code = responseJson.getInt("code"); + if (code == null || !code.equals(ResponseCode.SUCCESS.getCode())) { + String message = responseJson.getStr("message"); + dialogMaker.createMessageDialog("啊哈", "获取用户信息时出错了...\n" + message); + } else { + dialogMaker.createMessageDialog("好耶", "上传成功"); + Platform.runLater(() -> updateCallback.accept(response)); + } + } + }; + + @FXML + void deleteResume(ActionEvent event) { + dialogMaker.createDialogWithOKAndCancel("Really?", String.format("真的要删除简历 '%s' 吗?", resume.getTitle()), + e -> new OkhttpUtil() + .url(Constants.ResumeRequestUrls.RESUME_DELETE_URL) + .token(token) + .addQueryParam("id", resume.getId()) + .failCallback(e1 -> dialogMaker.createMessageDialog("啊哈", "删除简历时发生了网络错误\n" + e1)) + .successCallback(response -> new ResponseHandler(response) + .errorCallback(s -> dialogMaker.createMessageDialog("啊哈", "删除简历时出错了...\n" + s)) + .successCallback(response1 -> Platform.runLater(() -> deleteCallback.accept(response))) + .process()) + .executeAsync(OkhttpUtil.RequestMethod.POST)); + } +} diff --git a/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/components/student/resume/ResumeUploadPane.java b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/components/student/resume/ResumeUploadPane.java new file mode 100644 index 0000000..da8b84e --- /dev/null +++ b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/components/student/resume/ResumeUploadPane.java @@ -0,0 +1,95 @@ +package net.lensfrex.disillusion.client.controller.components.student.resume; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.RandomUtil; +import com.jfoenix.controls.JFXTextField; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.scene.layout.VBox; +import javafx.scene.text.Text; +import javafx.stage.FileChooser; +import lombok.Data; +import lombok.EqualsAndHashCode; +import net.lensfrex.disillusion.client.config.Constants; +import net.lensfrex.disillusion.client.controller.CommunicativeController; +import net.lensfrex.disillusion.client.util.ThreadSafeDialogMaker; + +import java.io.File; + +public class ResumeUploadPane extends VBox implements CommunicativeController { + private final ThreadSafeDialogMaker dialogMaker; + + public ResumeUploadPane(ThreadSafeDialogMaker dialogMaker) { + this.dialogMaker = dialogMaker; + } + + @Override + public void loadData(InitData initData) { + resumeNameTextField.setText(initData.getName()); + remarkTextField.setText(initData.getRemark()); + } + + @Override + public void initialize() { + resumeNameTextField.setText(RandomUtil.randomString(Constants.sample, 5)); + remarkTextField.setText(RandomUtil.randomString(Constants.sample, 15)); + } + + @Data + @EqualsAndHashCode(callSuper = false) + public static class InitData extends CommunicativeController.InitData { + private String name; + private String remark; + } + + @FXML + private VBox rootPane; + + @FXML + private JFXTextField resumeNameTextField; + + @FXML + private JFXTextField remarkTextField; + + @FXML + private Text fileNameText; + + private File resumeFile; + + @FXML + void addFile(ActionEvent event) { + FileChooser fileChooser = new FileChooser(); + fileChooser.setTitle("告诉我在哪?"); + File chooseFile = fileChooser.showOpenDialog(rootPane.getScene().getWindow()); + + if (chooseFile != null) { + String fileType = FileUtil.getType(chooseFile); + switch (fileType) { + case "png": + case "jpg": + case "jpeg": + case "webm": + case "pdf": + case "doc": + case "docx": + fileNameText.setText(chooseFile.getName()); + resumeFile = chooseFile; + break; + default: + dialogMaker.createMessageDialog("(;´д`)ゞ", "简历文件不支持:" + fileType + "\n仅支持png, jpg, webm, pdf, word"); + } + } + } + + public String getResumeName() { + return resumeNameTextField.getText(); + } + + public String getRemark() { + return remarkTextField.getText(); + } + + public File getResumeFile() { + return resumeFile; + } +} diff --git a/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/page/employer/EmployerApplicationManagePage.java b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/page/employer/EmployerApplicationManagePage.java new file mode 100644 index 0000000..972061b --- /dev/null +++ b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/page/employer/EmployerApplicationManagePage.java @@ -0,0 +1,91 @@ +package net.lensfrex.disillusion.client.controller.page.employer; + +import cn.hutool.json.JSONUtil; +import com.jfoenix.effects.JFXDepthManager; +import javafx.application.Platform; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.scene.Node; +import javafx.scene.control.ScrollPane; +import javafx.scene.layout.FlowPane; +import javafx.scene.layout.VBox; +import lombok.extern.slf4j.Slf4j; +import net.lensfrex.disillusion.client.config.Constants; +import net.lensfrex.disillusion.client.controller.components.ComponentFactory; +import net.lensfrex.disillusion.client.controller.components.employer.application.ApplicationOfferCard; +import net.lensfrex.disillusion.client.controller.components.student.resume.ResumeCard; +import net.lensfrex.disillusion.client.data.response.OfferResponse; +import net.lensfrex.disillusion.client.util.OkhttpUtil; +import net.lensfrex.disillusion.client.util.ResponseHandler; +import net.lensfrex.disillusion.client.util.ThreadSafeDialogMaker; + +import java.util.List; + +@Slf4j +public class EmployerApplicationManagePage extends VBox { + @FXML + private VBox rootPane; + + @FXML + private ScrollPane scrollPane; + + @FXML + private FlowPane offerContainer; + + private String token; + private ThreadSafeDialogMaker dialogMaker; + + @FXML + void refresh(ActionEvent event) { + this.refreshOfferList(); + } + + public void loadData(String token, ThreadSafeDialogMaker dialogMaker) { + this.token = token; + this.dialogMaker = dialogMaker; + this.refreshOfferList(); + } + + public void initialize() { + offerContainer.getChildren().addAll(offerLost); + } + + private final ObservableList offerLost = FXCollections.observableArrayList(); + + private void refreshOfferList() { + new OkhttpUtil() + .url(Constants.OfferRequestUrls.OFFER_GET_BY_EMPLOYER_SELF_URL) + .token(token) + .failCallback(e -> dialogMaker.createMessageDialog("啊哈", "获取offer列表时发生了网络错误\n" + e)) + .successCallback(response -> new ResponseHandler(response) + .errorCallback(s -> dialogMaker.createMessageDialog("啊哈", "获取offer列表不成功\n" + s)) + .successCallback(data -> { + List result = JSONUtil.toList(data, OfferResponse.class); + Platform.runLater(() -> showOffers(result)); + }) + .process()) + .executeAsync(OkhttpUtil.RequestMethod.GET); + } + + private void showOffers(List offers) { + try { + ObservableList cards = offerContainer.getChildren(); + cards.clear(); + for (OfferResponse offer : offers) { + if (offer == null) { + continue; + } + ApplicationOfferCard card = + ComponentFactory.loadComponent("fxml/employer/application/applicationOfferCard.fxml", new ApplicationOfferCard(offer)); + card.loadData(token, dialogMaker); + JFXDepthManager.setDepth(card, 1); + cards.add(card); + } + } catch (Exception e) { + e.printStackTrace(); + log.warn("展示offer卡片时发生错误"); + } + } +} diff --git a/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/page/employer/EmployerMainController.java b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/page/employer/EmployerMainController.java new file mode 100644 index 0000000..469be6e --- /dev/null +++ b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/page/employer/EmployerMainController.java @@ -0,0 +1,136 @@ +package net.lensfrex.disillusion.client.controller.page.employer; + +import animatefx.animation.FadeIn; +import animatefx.animation.FadeOut; +import cn.hutool.json.JSONUtil; +import javafx.application.Platform; +import javafx.collections.ObservableList; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.scene.Node; +import javafx.scene.image.ImageView; +import javafx.scene.layout.Pane; +import javafx.scene.layout.StackPane; +import javafx.scene.text.Text; +import lombok.extern.slf4j.Slf4j; +import net.lensfrex.disillusion.client.config.Constants; +import net.lensfrex.disillusion.client.controller.components.ComponentFactory; +import net.lensfrex.disillusion.client.controller.page.student.StudentProfilePage; +import net.lensfrex.disillusion.client.data.response.EmployerInfoResponse; +import net.lensfrex.disillusion.client.util.DialogMaker; +import net.lensfrex.disillusion.client.util.OkhttpUtil; +import net.lensfrex.disillusion.client.util.ResponseHandler; +import net.lensfrex.disillusion.client.util.ThreadSafeDialogMaker; + +import java.util.HashMap; +import java.util.Map; + +@Slf4j +public class EmployerMainController { + @FXML + private StackPane rootPane; + + @FXML + private ImageView avatarImageView; + + @FXML + private Text employerName; + + @FXML + private Text usernameText; + + @FXML + private Text shortDescribeText; + + @FXML + private StackPane containerPane; + + private ThreadSafeDialogMaker threadSafeDialogMaker; + + private String token; + + private EmployerInfoResponse employerInfo; + + private final Map pages = new HashMap<>(4); + + public void setData(String token) { + this.token = token; + this.fetchUserInfo(); + } + + public void initialize() { + threadSafeDialogMaker = new ThreadSafeDialogMaker(rootPane); + try { + pages.put("profile", ComponentFactory.loadComponent("fxml/employer/profile.fxml", EmployerProfilePage.class)); + pages.put("offerManage", ComponentFactory.loadComponent("fxml/employer/offer/offerManagePage.fxml", EmployerOfferManagePage.class)); + pages.put("applicationManage", ComponentFactory.loadComponent("fxml/employer/application/applicationManagePage.fxml", EmployerApplicationManagePage.class)); + } catch (Exception e) { + e.printStackTrace(); + DialogMaker.errorMessageDialog("出错啦,请联系lensfrex这家伙\n调试信息:\n" + e); + } + } + + private void fetchUserInfo() { + new OkhttpUtil() + .url(Constants.UserRequestUrls.USER_INFO_VIEW_URL) + .token(token) + .failCallback(e -> log.error("请求用户数据时发生网络错误")) + .successCallback(response -> new ResponseHandler(response) + .errorCallback(s -> threadSafeDialogMaker.createMessageDialog("啊哈", "获取用户信息不成功:" + s)) + .successCallback(this::updateUserInfoUI) + .process()) + .executeAsync(OkhttpUtil.RequestMethod.GET); + } + + private void updateUserInfoUI(String data) { + employerInfo = JSONUtil.toBean(data, EmployerInfoResponse.class); + Platform.runLater(() -> { + employerName.setText(employerInfo.getName()); + usernameText.setText("@" + employerInfo.getId()); + shortDescribeText.setText(employerInfo.getShortDescribe()); + }); + + this.updateAvatar(); + } + + private void updateAvatar() { + new OkhttpUtil() + .url(Constants.UserRequestUrls.USER_AVATAR_VIEW_URL) + .token(token) + .failCallback(e -> log.warn("获取头像时发生网络错误:\n" + e)) + .successCallback(avatarResponse -> Platform.runLater(() -> StudentProfilePage.setAvatar(avatarImageView, avatarResponse))) + .executeAsync(OkhttpUtil.RequestMethod.GET); + } + + @FXML + void showApplicationDealPage(ActionEvent event) { + EmployerApplicationManagePage page = (EmployerApplicationManagePage) this.switchPage("applicationManage"); + page.loadData(token, threadSafeDialogMaker); + } + + @FXML + void showOfferManagePage(ActionEvent event) { + EmployerOfferManagePage page = (EmployerOfferManagePage) this.switchPage("offerManage"); + page.loadData(token, threadSafeDialogMaker); + } + + @FXML + void showProfile(ActionEvent event) { + EmployerProfilePage page = (EmployerProfilePage) this.switchPage("profile"); + page.loadData(employerInfo, token, threadSafeDialogMaker, r -> fetchUserInfo()); + } + + private Node switchPage(String page) { + ObservableList list = containerPane.getChildren(); + + new FadeOut(list.get(0)).play(); + Pane pagePane = pages.get(page); + if (pagePane != null) { + list.clear(); + list.add(pagePane); + new FadeIn(pagePane).play(); + } + + return pagePane; + } +} \ No newline at end of file diff --git a/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/page/employer/EmployerOfferManagePage.java b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/page/employer/EmployerOfferManagePage.java new file mode 100644 index 0000000..f1c7a26 --- /dev/null +++ b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/page/employer/EmployerOfferManagePage.java @@ -0,0 +1,114 @@ +package net.lensfrex.disillusion.client.controller.page.employer; + +import cn.hutool.json.JSONUtil; +import com.jfoenix.effects.JFXDepthManager; +import javafx.application.Platform; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.scene.Node; +import javafx.scene.control.ScrollPane; +import javafx.scene.layout.FlowPane; +import javafx.scene.layout.VBox; +import lombok.extern.slf4j.Slf4j; +import net.lensfrex.disillusion.client.config.Constants; +import net.lensfrex.disillusion.client.controller.components.ComponentFactory; +import net.lensfrex.disillusion.client.controller.components.employer.offer.EmployerOfferCard; +import net.lensfrex.disillusion.client.controller.components.employer.offer.EmployerOfferDetailPane; +import net.lensfrex.disillusion.client.controller.components.student.resume.ResumeCard; +import net.lensfrex.disillusion.client.data.request.OfferPublishRequest; +import net.lensfrex.disillusion.client.data.response.OfferResponse; +import net.lensfrex.disillusion.client.util.Consumer; +import net.lensfrex.disillusion.client.util.OkhttpUtil; +import net.lensfrex.disillusion.client.util.ResponseHandler; +import net.lensfrex.disillusion.client.util.ThreadSafeDialogMaker; + +import java.io.IOException; +import java.util.List; + +@Slf4j +public class EmployerOfferManagePage extends VBox { + @FXML + private VBox rootPane; + + @FXML + private ScrollPane scrollPane; + + @FXML + private FlowPane offerContainer; + + private String token; + private ThreadSafeDialogMaker dialogMaker; + + public void loadData(String token, ThreadSafeDialogMaker dialogMaker) { + this.token = token; + this.dialogMaker = dialogMaker; + this.refreshOfferList(); + } + + public void initialize() { + offerContainer.getChildren().addAll(offerLost); + } + + private final ObservableList offerLost = FXCollections.observableArrayList(); + + @FXML + void addOffer(ActionEvent event) throws IOException, InstantiationException, IllegalAccessException { + EmployerOfferDetailPane offerDetailPane = + ComponentFactory.loadComponent("fxml/employer/offer/offerDetailPane.fxml", EmployerOfferDetailPane.class); + dialogMaker.createDialogWithOKAndCancel("发布新的offer信息", offerDetailPane, e -> this.publishOffer(offerDetailPane.getData())); + } + + private void publishOffer(OfferPublishRequest offerPublishRequest) { + new OkhttpUtil() + .url(Constants.OfferRequestUrls.OFFER_PUBLISH_URL) + .token(token) + .body(offerPublishRequest) + .failCallback(e -> dialogMaker.createMessageDialog("啊哈", "发布offer时发生了网络问题:\n" + e)) + .successCallback(response -> new ResponseHandler(response) + .errorCallback(s -> dialogMaker.createMessageDialog("啊哈", "发布offer不成功:\n" + s)) + .successCallback(s -> this.refreshOfferList()) + .process()) + .executeAsync(OkhttpUtil.RequestMethod.POST); + } + + private void refreshOfferList() { + new OkhttpUtil() + .url(Constants.OfferRequestUrls.OFFER_GET_BY_EMPLOYER_SELF_URL) + .token(token) + .failCallback(e -> dialogMaker.createMessageDialog("啊哈", "获取offer列表时发生了网络错误\n" + e)) + .successCallback(response -> new ResponseHandler(response) + .errorCallback(s -> dialogMaker.createMessageDialog("啊哈", "获取offer列表不成功\n" + s)) + .successCallback(data -> { + List result = JSONUtil.toList(data, OfferResponse.class); + Platform.runLater(() -> showOffers(result)); + }) + .process()) + .executeAsync(OkhttpUtil.RequestMethod.GET); + } + + private void showOffers(List offers) { + try { + ObservableList cards = offerContainer.getChildren(); + cards.clear(); + for (OfferResponse offer : offers) { + if (offer == null) { + continue; + } + EmployerOfferCard card = + ComponentFactory.loadComponent("fxml/employer/offer/offerCard.fxml", new EmployerOfferCard(offer)); + card.loadData(token, dialogMaker, this.deleteCallback, this.updateCallback); + JFXDepthManager.setDepth(card, 1); + cards.add(card); + } + } catch (Exception e) { + e.printStackTrace(); + log.warn("展示offer卡片时发生错误"); + } + } + + private final Consumer updateCallback = s -> refreshOfferList(); + + private final Consumer deleteCallback = s -> refreshOfferList(); +} diff --git a/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/page/employer/EmployerProfilePage.java b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/page/employer/EmployerProfilePage.java new file mode 100644 index 0000000..45d6417 --- /dev/null +++ b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/page/employer/EmployerProfilePage.java @@ -0,0 +1,206 @@ +package net.lensfrex.disillusion.client.controller.page.employer; + +import cn.hutool.core.codec.Base64; +import cn.hutool.core.io.FileUtil; +import com.jfoenix.controls.JFXTextArea; +import com.jfoenix.controls.JFXTextField; +import javafx.application.Platform; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.HBox; +import javafx.scene.text.Text; +import javafx.stage.FileChooser; +import lombok.extern.slf4j.Slf4j; +import net.lensfrex.disillusion.client.config.Constants; +import net.lensfrex.disillusion.client.data.response.EmployerInfoResponse; +import net.lensfrex.disillusion.client.util.ImageUtil; +import net.lensfrex.disillusion.client.util.OkhttpUtil; +import net.lensfrex.disillusion.client.util.ResponseHandler; +import net.lensfrex.disillusion.client.util.ThreadSafeDialogMaker; +import okhttp3.Response; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.function.Consumer; + +@Slf4j +public class EmployerProfilePage extends HBox { + @FXML + private HBox rootPane; + + @FXML + private ImageView avatarImageView; + + @FXML + private Text usernameText; + + @FXML + private Text shortDescribeText; + + @FXML + private JFXTextField employerNameTextField; + + @FXML + private JFXTextField industryTextField; + + @FXML + private JFXTextField contactTextField; + + @FXML + private JFXTextField addressTextField; + + @FXML + private JFXTextField shortDescribeTextField; + + @FXML + private JFXTextArea describeTextArea; + + private File newAvatarFile; + + private String token; + + private ThreadSafeDialogMaker dialogMaker; + + @FXML + void save(ActionEvent event) { + employerInfo.setName(employerNameTextField.getText()); + employerInfo.setPlace(addressTextField.getText()); + employerInfo.setContract(contactTextField.getText()); + employerInfo.setShortDescribe(shortDescribeTextField.getText()); + employerInfo.setDescribe(describeTextArea.getText()); + employerInfo.setIndustry(industryTextField.getText()); + + new OkhttpUtil() + .url(Constants.UserRequestUrls.USER_INFO_EDIT_URL) + .token(token) + .body(employerInfo) + .failCallback(e -> dialogMaker.createMessageDialog("啊哈", "更新上传用户信息的时候出错啦:\n" + e)) + .successCallback(response -> new ResponseHandler(response) + .errorCallback(s -> dialogMaker.createMessageDialog("啊哈", "更新上传用户信息不成功:" + s)) + .successCallback(s -> { + updateCallback.accept(employerInfo); + dialogMaker.createMessageDialog("好耶", "更新成功"); + Platform.runLater(this::updateDisplay); + }) + .process()) + .executeAsync(OkhttpUtil.RequestMethod.POST); + } + + @FXML + void replaceAvatar(MouseEvent event) { + FileChooser fileChooser = new FileChooser(); + fileChooser.setTitle("选择图片"); + File chooseFile = fileChooser.showOpenDialog(rootPane.getScene().getWindow()); + + if (chooseFile != null) { + String fileType = FileUtil.getType(chooseFile); + switch (fileType) { + case "png": + case "jpg": + case "jpeg": + case "gif": + case "webm": + this.uploadAvatar(chooseFile); + break; + default: + dialogMaker.createMessageDialog( + "(;´д`)ゞ", + "头像文件不支持:" + fileType + "\n仅支持png, jpg, gif, webm" + ); + } + } + } + + private void uploadAvatar(File avatarFile) { + try { + byte[] avatarData; + // 大于1m的图片就进行压缩 + long fileLength = avatarFile.length(); + if (fileLength > 1048576) { + // 压缩比例,文件越大压缩程度越大 + float percent = (float) Math.pow(0.96, fileLength / 1048576f); + avatarData = ImageUtil.processImage(avatarFile, 0.8f, percent, "jpg"); + } else { + avatarData = Files.readAllBytes(Paths.get(avatarFile.toURI())); + } + + new OkhttpUtil() + .url(Constants.UserRequestUrls.USER_AVATAR_EDIT_URL) + .token(token) + .body(Base64.encode(avatarData), "plain/text") + .failCallback(e -> dialogMaker.createMessageDialog("啊哈", "上传头像文件时发生网络错误:" + e)) + .successCallback(response -> new ResponseHandler(response) + .errorCallback(s -> dialogMaker.createMessageDialog("啊哈", "获取头像时出现问题:" + s)) + .successCallback(s -> { + Platform.runLater(() -> setAvatar(avatarImageView, avatarData)); + updateCallback.accept(employerInfo); + }) + .process()) + .executeAsync(OkhttpUtil.RequestMethod.POST); + } catch (IOException e) { + dialogMaker.createMessageDialog("啊哈", "读取头像文件时发生错误:" + e); + } + } + + private EmployerInfoResponse employerInfo; + + private Consumer updateCallback; + + public void loadData(EmployerInfoResponse employerInfo, + String token, ThreadSafeDialogMaker dialogMaker, + Consumer updateCallback) { + this.token = token; + this.employerInfo = employerInfo; + this.dialogMaker = dialogMaker; + this.updateCallback = updateCallback; + + this.updateDisplay(); + + new OkhttpUtil() + .url(Constants.UserRequestUrls.USER_AVATAR_VIEW_URL) + .token(token) + .successCallback(response -> Platform.runLater(() -> setAvatar(avatarImageView, response))) + .executeAsync(OkhttpUtil.RequestMethod.GET); + } + + private void updateDisplay() { + String name = employerInfo.getName(); + String place = employerInfo.getPlace(); + String contract = employerInfo.getContract(); + String shortDescribe = employerInfo.getShortDescribe(); + String describe = employerInfo.getDescribe(); + String industry = employerInfo.getIndustry(); + + usernameText.setText(name); + shortDescribeText.setText(shortDescribe); + + employerNameTextField.setText(name); + industryTextField.setText(industry); + contactTextField.setText(contract); + addressTextField.setText(place); + shortDescribeTextField.setText(shortDescribe); + describeTextArea.setText(describe); + } + + public static void setAvatar(ImageView avatarImageView, Response response) { + try { + assert response.body() != null; + byte[] data = response.body().bytes(); + + setAvatar(avatarImageView, data); + } catch (Exception e) { + log.warn("获取头像数据时出现异常", e); + } + } + + public static void setAvatar(ImageView avatarImageView, byte[] data) { + Image avatarImage = new Image(new ByteArrayInputStream(data)); + avatarImageView.setImage(avatarImage); + } +} diff --git a/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/page/student/ApplicationManagePage.java b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/page/student/ApplicationManagePage.java new file mode 100644 index 0000000..fa35a43 --- /dev/null +++ b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/page/student/ApplicationManagePage.java @@ -0,0 +1,106 @@ +package net.lensfrex.disillusion.client.controller.page.student; + +import cn.hutool.json.JSONUtil; +import com.jfoenix.effects.JFXDepthManager; +import javafx.application.Platform; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.Node; +import javafx.scene.control.ScrollPane; +import javafx.scene.layout.FlowPane; +import javafx.scene.layout.VBox; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.extern.slf4j.Slf4j; +import net.lensfrex.disillusion.client.config.Constants; +import net.lensfrex.disillusion.client.controller.CommunicativeController; +import net.lensfrex.disillusion.client.controller.components.ComponentFactory; +import net.lensfrex.disillusion.client.controller.components.student.application.ApplicationCard; +import net.lensfrex.disillusion.client.data.response.ApplicationResponse; +import net.lensfrex.disillusion.client.util.OkhttpUtil; +import net.lensfrex.disillusion.client.util.ResponseHandler; +import net.lensfrex.disillusion.client.util.ThreadSafeDialogMaker; +import okhttp3.*; + +import java.util.List; + +@Slf4j +public class ApplicationManagePage extends VBox implements CommunicativeController { + @FXML + private VBox rootPane; + + @FXML + private ScrollPane scrollPane; + + @FXML + private FlowPane applicationContainer; + + private String token; + private ThreadSafeDialogMaker dialogMaker; + + private static final OkHttpClient okhttpClient = Constants.globalClient; + + @Data + @Builder + @EqualsAndHashCode(callSuper = false) + public static class InitData extends CommunicativeController.InitData { + private String token; + private ThreadSafeDialogMaker dialogMaker; + } + + @Override + public void loadData(ApplicationManagePage.InitData initData) { + this.token = initData.token; + this.dialogMaker = initData.dialogMaker; + + this.refreshApplicationList(); + } + + public void initialize() { + applicationContainer.getChildren().addAll(applicationList); + } + + private final ObservableList applicationList = FXCollections.observableArrayList(); + + private void refreshApplicationList() { + new OkhttpUtil() + .url(Constants.ApplicationRequestUrls.APPLICATION_STUDENT_VIEW_URL) + .token(token) + .failCallback(e -> dialogMaker.createMessageDialog("啊哈", "获取简历时发生了网络错误\n" + e)) + .successCallback(response -> { + ResponseHandler.Result responseData = ResponseHandler.parse(response, + s -> dialogMaker.createMessageDialog("啊哈", "获取简历时出错了...\n" + s)); + if (responseData.data == null || !responseData.success) { + return; + } + + List result = JSONUtil.toList(responseData.data, ApplicationResponse.class); + Platform.runLater(() -> listApplications(result)); + }) + .executeAsync(OkhttpUtil.RequestMethod.GET); + } + + private void listApplications(List applications) { + try { + ObservableList items = applicationContainer.getChildren(); + items.clear(); + for (ApplicationResponse application : applications) { + if (application == null) { + continue; + } + + ApplicationCard card = + ComponentFactory.loadComponent("fxml/student/application/applicationCard.fxml", + new ApplicationCard(token, dialogMaker, application, s -> this.refreshApplicationList())); + JFXDepthManager.setDepth(card, 1); + card.setUserData(applications); + items.add(card); + } + } catch (Exception e) { + e.printStackTrace(); + log.warn("展示求职申请卡片时发生错误"); + } + } +} diff --git a/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/page/student/OfferSquarePage.java b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/page/student/OfferSquarePage.java new file mode 100644 index 0000000..9a0d6be --- /dev/null +++ b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/page/student/OfferSquarePage.java @@ -0,0 +1,204 @@ +package net.lensfrex.disillusion.client.controller.page.student; + +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import com.jfoenix.controls.JFXComboBox; +import com.jfoenix.controls.JFXTextField; +import com.jfoenix.effects.JFXDepthManager; +import javafx.application.Platform; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.control.ScrollPane; +import javafx.scene.input.ScrollEvent; +import javafx.scene.layout.FlowPane; +import javafx.scene.layout.Pane; +import javafx.scene.layout.VBox; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.extern.slf4j.Slf4j; +import net.lensfrex.disillusion.client.config.Constants; +import net.lensfrex.disillusion.client.controller.CommunicativeController; +import net.lensfrex.disillusion.client.controller.components.ComponentFactory; +import net.lensfrex.disillusion.client.controller.components.student.offer.StudentOfferCard; +import net.lensfrex.disillusion.client.data.ResponseCode; +import net.lensfrex.disillusion.client.data.response.OfferResponse; +import net.lensfrex.disillusion.client.util.Consumer; +import net.lensfrex.disillusion.client.util.OkhttpUtil; +import net.lensfrex.disillusion.client.util.ResponseHandler; +import net.lensfrex.disillusion.client.util.ThreadSafeDialogMaker; +import okhttp3.OkHttpClient; +import okhttp3.Response; + +import java.util.List; + +@Slf4j +public class OfferSquarePage + extends VBox + implements CommunicativeController { + + private static final OkHttpClient okhttpClient = Constants.globalClient; + + @FXML + private VBox rootPane; + + @FXML + private JFXComboBox filterType; + + @FXML + private JFXTextField filterString; + + @FXML + private FlowPane offersPane; + + @FXML + private ScrollPane scrollPane; + + @FXML + private Label pageLabel; + + private double totalDrag = 0; + + private String token; + private Pane parentRootPane; + + private ThreadSafeDialogMaker dialogMaker; + + @Override + public void loadData(InitData data) { + this.token = data.token; + this.dialogMaker = data.dialogMaker; + + this.refresh(null); + } + + @Data + @Builder + @EqualsAndHashCode(callSuper = true) + public static class InitData extends CommunicativeController.InitData { + private String token; + private ThreadSafeDialogMaker dialogMaker; + } + + public void initialize() { + ObservableList filterList = FXCollections.observableArrayList(); + filterList.add("用人单位"); + filterList.add("职位名称"); + filterList.add("最低薪资"); + filterType.setItems(filterList); + filterType.getSelectionModel().select(0); + + scrollPane.addEventHandler(ScrollEvent.SCROLL, e -> { + double downScrollLength = -e.getDeltaY(); + if (downScrollLength < 0) { + return; + } + + totalDrag += downScrollLength; + if (totalDrag > offersPane.getPrefHeight()) { + refresh(null); + } + }); + } + + private int currentPage = 0; + + private static final int DEFAULT_PAGE_SIZE = 5; + + @FXML + void refresh(ActionEvent event) { + this.getOffers(currentPage + 1, DEFAULT_PAGE_SIZE); + } + + private void getOffers(int page, int size) { + new OkhttpUtil() + .addQueryParam("page", String.valueOf(page)) + .addQueryParam("size", String.valueOf(size)) + .url(Constants.OfferRequestUrls.GET_ALL_OFFER_URL) + .failCallback(e -> dialogMaker.createMessageDialog("啊哈", "请求Offer列表时发生了一下错误\n" + e)) + .successCallback(getOfferSuccessCallback) + .executeAsync(OkhttpUtil.RequestMethod.GET); + } + + private static final OfferResponse[] EMPTY_OFFERS = new OfferResponse[]{}; + + private final Consumer getOfferSuccessCallback = response -> { + assert response.body() != null; + String responseString = response.body().string(); + JSONObject responseJson = JSONUtil.parseObj(responseString); + + Integer code = responseJson.getInt("code"); + if (code == null || !code.equals(ResponseCode.SUCCESS.getCode())) { + dialogMaker.createMessageDialog("啊哈", "获取offer列表时出错了...\n" + responseString); + } + + JSONObject data = responseJson.getJSONObject("data"); + + List offers = JSONUtil.toList(data.getJSONArray("result"), OfferResponse.class); + + Platform.runLater(() -> { + this.showOfferCards(offers); + currentPage = offers.size() < DEFAULT_PAGE_SIZE ? 0 : data.getInt("page"); + }); + }; + + private void showOfferCards(List offers) { + try { + ObservableList items = offersPane.getChildren(); + if (currentPage == 0) { + items.clear(); + } + + for (OfferResponse offer : offers) { + StudentOfferCard studentOfferCard = new StudentOfferCard(offer); + Node item = ComponentFactory.loadComponent("fxml/student/offer/offerCard.fxml", studentOfferCard); + + studentOfferCard.loadData(StudentOfferCard.InitData.builder().token(token).dialogMaker(dialogMaker).build()); + + studentOfferCard.setUserData("offer"); + JFXDepthManager.setDepth(item, 1); + + items.add(new Pane(item)); + } + } catch (Exception e) { + log.error("解析Offer对象数据时出错:\n{}", e.toString()); + e.printStackTrace(); + } + } + + @FXML + void search(ActionEvent event) { + String selected = filterType.getSelectionModel().getSelectedItem(); + String filterType = ""; + switch (selected) { + case "用人单位": + filterType = Constants.OfferRequestUrls.OFFER_FILTER_EMPLOYER; + break; + case "职位名称": + filterType = Constants.OfferRequestUrls.OFFER_FILTER_JOB_NAME; + break; + case "最低薪资": + filterType = Constants.OfferRequestUrls.OFFER_FILTER_SALARY; + break; + } + + new OkhttpUtil() + .url(Constants.OfferRequestUrls.OFFER_SEARCH_URL) + .addQueryParam("filter", filterType) + .addQueryParam("value", filterString.getText()) + .failCallback(e -> dialogMaker.createMessageDialog("啊哈", "搜索offer时发生了错误:\n" + e)) + .successCallback(response -> new ResponseHandler(response) + .errorCallback(s -> dialogMaker.createMessageDialog("啊哈", "搜索offer不成功:\n" + s)) + .successCallback(s -> Platform.runLater(() -> { + offersPane.getChildren().clear(); + List offers = JSONUtil.toList(s, OfferResponse.class); + this.showOfferCards(offers); + })) + .process()) + .executeAsync(OkhttpUtil.RequestMethod.GET); + } +} diff --git a/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/page/student/ResumeManagePage.java b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/page/student/ResumeManagePage.java new file mode 100644 index 0000000..2f94692 --- /dev/null +++ b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/page/student/ResumeManagePage.java @@ -0,0 +1,194 @@ +package net.lensfrex.disillusion.client.controller.page.student; + +import cn.hutool.core.codec.Base64; +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import com.jfoenix.effects.JFXDepthManager; +import javafx.application.Platform; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.scene.Node; +import javafx.scene.layout.FlowPane; +import javafx.scene.layout.VBox; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.extern.slf4j.Slf4j; +import net.lensfrex.disillusion.client.config.Constants; +import net.lensfrex.disillusion.client.controller.CommunicativeController; +import net.lensfrex.disillusion.client.controller.components.ComponentFactory; +import net.lensfrex.disillusion.client.controller.components.student.resume.ResumeCard; +import net.lensfrex.disillusion.client.controller.components.student.resume.ResumeUploadPane; +import net.lensfrex.disillusion.client.data.ResponseCode; +import net.lensfrex.disillusion.client.data.request.ResumeRequest; +import net.lensfrex.disillusion.client.data.response.ResumeResponse; +import net.lensfrex.disillusion.client.util.ThreadSafeDialogMaker; +import okhttp3.*; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.function.Consumer; + +@Slf4j +public class ResumeManagePage extends VBox + implements CommunicativeController { + + private static final OkHttpClient okhttpClient = Constants.globalClient; + + private String token; + private ThreadSafeDialogMaker dialogMaker; + + @Override + public void loadData(InitData initData) { + this.token = initData.token; + this.dialogMaker = initData.dialogMaker; + this.refreshResumeList(); + } + + @Data + @EqualsAndHashCode(callSuper = false) + public static class InitData extends CommunicativeController.InitData { + private String token; + private ThreadSafeDialogMaker dialogMaker; + } + + public void initialize() { + resumeContainer.getChildren().addAll(resumeList); + } + + @FXML + private VBox rootPane; + + @FXML + private FlowPane resumeContainer; + + private final ObservableList resumeList = FXCollections.observableArrayList(); + + @FXML + void addResume(ActionEvent event) { + try { + ResumeUploadPane resumeUploadPane = + ComponentFactory.loadComponent("fxml/student/resume/resumeUpload.fxml", new ResumeUploadPane(dialogMaker)); + + dialogMaker.createDialogWithOKAndCancel("上传新的简历", resumeUploadPane, e -> { + try { + ResumeRequest request = new ResumeRequest(); + request.setTitle(resumeUploadPane.getResumeName()); + request.setRemark(resumeUploadPane.getRemark()); + + File resumeFile = resumeUploadPane.getResumeFile(); + if (resumeFile == null) { + dialogMaker.createMessageDialog("啊哈", "请选择上传一个文件"); + return; + } + request.setResumeFileBase64(Base64.encode(resumeFile)); + + this.uploadResume(request); + } catch (Exception ex) { + ex.printStackTrace(); + dialogMaker.createMessageDialog("啊哈", "读取简历文件时发生错误...\n" + ex); + } + }); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private void uploadResume(ResumeRequest resumeRequest) { + String json = JSONUtil.toJsonStr(resumeRequest); + RequestBody requestBody = RequestBody.create(json, MediaType.get("application/json")); + + Request uploadRequest = new Request.Builder() + .url(Constants.ResumeRequestUrls.RESUME_UPLOAD_URL) + .addHeader("token", token) + .post(requestBody) + .build(); + + okhttpClient.newCall(uploadRequest).enqueue(resumeUploadCallback); + } + + private final Callback resumeUploadCallback = new Callback() { + @Override + public void onFailure(@NotNull Call call, @NotNull IOException e) { + dialogMaker.createMessageDialog("啊哈", "上传简历时发生了网络错误\n" + e); + } + + @Override + public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException { + assert response.body() != null; + String responseString = response.body().string(); + JSONObject responseJson = JSONUtil.parseObj(responseString); + + Integer code = responseJson.getInt("code"); + if (code == null || !code.equals(ResponseCode.SUCCESS.getCode())) { + String message = responseJson.getStr("message"); + dialogMaker.createMessageDialog("啊哈", "获取用户信息时出错了...\n" + message); + } else { + dialogMaker.createMessageDialog("好耶", "上传成功"); + Platform.runLater(ResumeManagePage.this::refreshResumeList); + } + } + }; + + private void refreshResumeList() { + Request uploadRequest = new Request.Builder() + .url(Constants.ResumeRequestUrls.RESUME_GET_ALL_URL) + .addHeader("token", token) + .get() + .build(); + + okhttpClient.newCall(uploadRequest).enqueue(resumeFetchCallback); + } + + private final Callback resumeFetchCallback = new Callback() { + @Override + public void onFailure(@NotNull Call call, @NotNull IOException e) { + dialogMaker.createMessageDialog("啊哈", "获取简历时发生了网络错误\n" + e); + } + + @Override + public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException { + assert response.body() != null; + String responseString = response.body().string(); + JSONObject responseJson = JSONUtil.parseObj(responseString); + + Integer code = responseJson.getInt("code"); + if (code == null || !code.equals(ResponseCode.SUCCESS.getCode())) { + String message = responseJson.getStr("message"); + dialogMaker.createMessageDialog("啊哈", "获取简历时出错了...\n" + message); + } + + List result = JSONUtil.toList(responseJson.getJSONArray("data"), ResumeResponse.class); + Platform.runLater(() -> listResumes(result)); + } + }; + + private void listResumes(List resumes) { + try { + ObservableList items = resumeContainer.getChildren(); + items.clear(); + for (ResumeResponse resume : resumes) { + if (resume == null) { + continue; + } + + ResumeCard itemController = new ResumeCard(resume, token, dialogMaker, updateCallback, deleteCallback); + ResumeCard item = + ComponentFactory.loadComponent("fxml/student/resume/resumeCard.fxml", itemController); + JFXDepthManager.setDepth(item, 1); + item.setUserData(resume); + items.add(item); + } + } catch (Exception e) { + e.printStackTrace(); + log.warn("展示简历卡片时发生错误"); + } + } + + private final Consumer updateCallback = response -> refreshResumeList(); + + private final Consumer deleteCallback = response -> refreshResumeList(); +} diff --git a/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/page/student/StudentMainController.java b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/page/student/StudentMainController.java new file mode 100644 index 0000000..40c5342 --- /dev/null +++ b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/page/student/StudentMainController.java @@ -0,0 +1,174 @@ +package net.lensfrex.disillusion.client.controller.page.student; + +import animatefx.animation.FadeIn; +import animatefx.animation.FadeOut; +import cn.hutool.json.JSONUtil; +import javafx.application.Platform; +import javafx.collections.ObservableList; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.scene.Node; +import javafx.scene.image.ImageView; +import javafx.scene.layout.Pane; +import javafx.scene.layout.StackPane; +import javafx.scene.text.Text; +import lombok.Builder; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import net.lensfrex.disillusion.client.config.Constants; +import net.lensfrex.disillusion.client.controller.components.ComponentFactory; +import net.lensfrex.disillusion.client.data.response.StudentInfoResponse; +import net.lensfrex.disillusion.client.util.DialogMaker; +import net.lensfrex.disillusion.client.util.OkhttpUtil; +import net.lensfrex.disillusion.client.util.ResponseHandler; +import net.lensfrex.disillusion.client.util.ThreadSafeDialogMaker; +import okhttp3.*; + +import java.util.HashMap; +import java.util.Map; + +@Slf4j +public class StudentMainController { + private static final OkHttpClient okhttpClient = Constants.globalClient; + + @FXML + private StackPane rootPane; + + @FXML + private ImageView avatarImageView; + + @FXML + private Text usernameText; + + @FXML + private Text realNameText; + + @FXML + private StackPane containerPane; + + @FXML + private Text signText; + + private ThreadSafeDialogMaker threadSafeDialogMaker; + private DialogMaker dialogMaker; + + private String token; + private String username; + + private StudentInfoResponse studentInfo; + + private final Map pages = new HashMap<>(4); + + public void initialize() { + threadSafeDialogMaker = new ThreadSafeDialogMaker(rootPane); + dialogMaker = new DialogMaker(rootPane); + try { + pages.put("offerSquare", ComponentFactory.loadComponent("fxml/student/offer/offerSquare.fxml", OfferSquarePage.class)); + pages.put("profile", ComponentFactory.loadComponent("fxml/student/profile.fxml", StudentProfilePage.class)); + pages.put("applicationManage", ComponentFactory.loadComponent("fxml/student/application/applicationManagePage.fxml", ApplicationManagePage.class)); + pages.put("resumeManage", ComponentFactory.loadComponent("fxml/student/resume/resumeManagePage.fxml", ResumeManagePage.class)); + } catch (Exception e) { + e.printStackTrace(); + DialogMaker.errorMessageDialog("出错啦,请联系lensfrex这家伙\n调试信息:\n" + e); + } + } + + public void setData(InitData data) { + this.token = data.token; + this.username = data.username; + + this.updateUserInfo(); + } + + private void updateUserInfo() { + new OkhttpUtil() + .url(Constants.UserRequestUrls.USER_INFO_VIEW_URL) + .token(token) + .failCallback(e -> log.error("请求用户数据时发生网络错误")) + .successCallback(response -> { + ResponseHandler.Result result = ResponseHandler.parse(response, + s -> threadSafeDialogMaker.createMessageDialog("啊哈", "获取用户信息时出错:" + s)); + if (!result.success) { + return; + } + + studentInfo = JSONUtil.toBean(result.data, StudentInfoResponse.class); + Platform.runLater(() -> { + usernameText.setText(username); + realNameText.textProperty().set("@" + studentInfo.getRealName()); + signText.setText(studentInfo.getShortDescribe()); + }); + + new OkhttpUtil() + .url(Constants.UserRequestUrls.USER_AVATAR_VIEW_URL) + .token(token) + .successCallback(avatarResponse -> Platform.runLater(() -> StudentProfilePage.setAvatar(avatarImageView, avatarResponse))) + .executeAsync(OkhttpUtil.RequestMethod.GET); + }) + .executeAsync(OkhttpUtil.RequestMethod.GET); + } + + @FXML + void showApplications(ActionEvent event) { + ApplicationManagePage controller = (ApplicationManagePage) this.switchPage("applicationManage"); + controller.loadData(ApplicationManagePage.InitData.builder() + .token(token) + .dialogMaker(threadSafeDialogMaker) + .build() + ); + } + + @FXML + void showOfferSquare(ActionEvent event) { + OfferSquarePage controller = (OfferSquarePage) this.switchPage("offerSquare"); + controller.loadData(OfferSquarePage.InitData.builder() + .token(token) + .dialogMaker(threadSafeDialogMaker) + .build() + ); + } + + @FXML + void showProfile(ActionEvent event) { + StudentProfilePage controller = (StudentProfilePage) this.switchPage("profile"); + controller.setData(studentInfo, token, threadSafeDialogMaker, info -> this.updateUserInfo()); + } + + @FXML + void showResumeManage(ActionEvent event) { + ResumeManagePage controller = (ResumeManagePage) this.switchPage("resumeManage"); + ResumeManagePage.InitData data = new ResumeManagePage.InitData(); + data.setToken(token); + data.setDialogMaker(threadSafeDialogMaker); + + controller.loadData(data); + } + + private String currentWindow = ""; + + private Node switchPage(String page) { + if (currentWindow.equals(page)) { + return pages.get(page); + } + + ObservableList list = containerPane.getChildren(); + + new FadeOut(list.get(0)).play(); + Pane pagePane = pages.get(page); + if (pagePane != null) { + list.clear(); + list.add(pagePane); + new FadeIn(pagePane).play(); + currentWindow = page; + } + + return pagePane; + } + + @Data + @Builder + public static class InitData { + private String token; + private String username; + } +} diff --git a/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/page/student/StudentProfilePage.java b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/page/student/StudentProfilePage.java new file mode 100644 index 0000000..c6d1b43 --- /dev/null +++ b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/controller/page/student/StudentProfilePage.java @@ -0,0 +1,235 @@ +package net.lensfrex.disillusion.client.controller.page.student; + +import cn.hutool.core.codec.Base64; +import cn.hutool.core.io.FileUtil; +import com.jfoenix.controls.JFXRadioButton; +import com.jfoenix.controls.JFXTextArea; +import com.jfoenix.controls.JFXTextField; +import javafx.application.Platform; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.scene.control.ToggleGroup; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.input.MouseEvent; +import javafx.scene.layout.HBox; +import javafx.scene.text.Text; +import javafx.stage.FileChooser; +import lombok.extern.slf4j.Slf4j; +import net.lensfrex.disillusion.client.config.Constants; +import net.lensfrex.disillusion.client.data.response.StudentInfoResponse; +import net.lensfrex.disillusion.client.util.ImageUtil; +import net.lensfrex.disillusion.client.util.OkhttpUtil; +import net.lensfrex.disillusion.client.util.ResponseHandler; +import net.lensfrex.disillusion.client.util.ThreadSafeDialogMaker; +import okhttp3.Response; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.function.Consumer; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +@Slf4j +public class StudentProfilePage extends HBox { + @FXML + private HBox rootPane; + + @FXML + private ImageView avatarImageView; + + @FXML + private Text usernameText; + + @FXML + private Text signText; + + @FXML + private JFXTextField signEditText; + + @FXML + private JFXTextArea describeTextArea; + + @FXML + private JFXTextField realNameTextField; + + @FXML + private JFXTextField ageTextField; + + @FXML + private JFXRadioButton maleRadioButton; + + @FXML + private JFXRadioButton femaleRadioButton; + + @FXML + private JFXTextField contactTextField; + + @FXML + private JFXTextField idNumberTextField; + + private File newAvatarFile; + + private String token; + + private static final Pattern agePattern = Pattern.compile("^\\d{2}$"); + + private ThreadSafeDialogMaker dialogMaker; + + @FXML + void save(ActionEvent event) { + studentInfo.setRealName(realNameTextField.getText()); + studentInfo.setShortDescribe(signEditText.getText()); + studentInfo.setDescribe(describeTextArea.getText()); + studentInfo.setPhoneNumber(contactTextField.getText()); + studentInfo.setIdNumber(idNumberTextField.getText()); + + String inputAge = ageTextField.getText(); + Matcher ageMatcher = agePattern.matcher(inputAge); + if (!ageMatcher.matches()) { + dialogMaker.createMessageDialog("啊哈", "年龄格式不正确(2位数字)"); + } + + studentInfo.setAge(Integer.parseInt(inputAge)); + + studentInfo.setSex(sexGroup.getSelectedToggle().getUserData().equals("0") ? 0 : 1); + + new OkhttpUtil() + .url(Constants.UserRequestUrls.USER_INFO_EDIT_URL) + .token(token) + .body(studentInfo) + .failCallback(e -> dialogMaker.createMessageDialog("啊哈", "更新上传用户信息的时候出错啦:\n" + e)) + .successCallback(response -> new ResponseHandler(response) + .errorCallback(s -> dialogMaker.createMessageDialog("啊哈", "更新上传用户信息不成功:" + s)) + .successCallback(s -> { + updateCallback.accept(studentInfo); + dialogMaker.createMessageDialog("好耶", "更新成功"); + }) + .process()) + .executeAsync(OkhttpUtil.RequestMethod.POST); + } + + @FXML + void replaceAvatar(MouseEvent event) { + FileChooser fileChooser = new FileChooser(); + fileChooser.setTitle("选择图片"); + File chooseFile = fileChooser.showOpenDialog(rootPane.getScene().getWindow()); + + if (chooseFile != null) { + String fileType = FileUtil.getType(chooseFile); + switch (fileType) { + case "png": + case "jpg": + case "jpeg": + case "gif": + case "webm": + this.uploadAvatar(chooseFile); + break; + default: + dialogMaker.createMessageDialog( + "(;´д`)ゞ", + "头像文件不支持:" + fileType + "\n仅支持png, jpg, gif, webm" + ); + } + } + } + + private void uploadAvatar(File avatarFile) { + try { + byte[] avatarData; + // 大于1m的图片就进行压缩 + long fileLength = avatarFile.length(); + if (fileLength > 1048576) { + // 压缩比例,文件越大压缩程度越大 + float percent = (float) Math.pow(0.96, fileLength / 1048576f); + avatarData = ImageUtil.processImage(avatarFile, 0.8f, percent, "jpg"); + } else { + avatarData = Files.readAllBytes(Paths.get(avatarFile.toURI())); + } + + new OkhttpUtil() + .url(Constants.UserRequestUrls.USER_AVATAR_EDIT_URL) + .token(token) + .body(Base64.encode(avatarData), "plain/text") + .failCallback(e -> dialogMaker.createMessageDialog("啊哈", "上传头像文件时发生网络错误:" + e)) + .successCallback(response -> new ResponseHandler(response) + .errorCallback(s -> dialogMaker.createMessageDialog("啊哈", "获取头像时出现问题:" + s)) + .successCallback(s -> { + updateCallback.accept(studentInfo); + Platform.runLater(() -> setAvatar(avatarImageView, avatarData)); + }) + .process()) + .executeAsync(OkhttpUtil.RequestMethod.POST); + } catch (IOException e) { + dialogMaker.createMessageDialog("啊哈", "读取头像文件时发生错误:" + e); + } + } + + private final ToggleGroup sexGroup = new ToggleGroup(); + + @FXML + public void initialize() { + maleRadioButton.setUserData("0"); + femaleRadioButton.setUserData("1"); + + maleRadioButton.setToggleGroup(sexGroup); + femaleRadioButton.setToggleGroup(sexGroup); + } + + private StudentInfoResponse studentInfo; + private Consumer updateCallback; + + public void setData(StudentInfoResponse studentInfo, + String token, ThreadSafeDialogMaker dialogMaker, + Consumer updateCallback) { + + this.studentInfo = studentInfo; + this.token = token; + this.dialogMaker = dialogMaker; + this.updateCallback = updateCallback; + + usernameText.setText(studentInfo.getId()); + realNameTextField.setText(studentInfo.getRealName()); + signText.setText(studentInfo.getShortDescribe()); + signEditText.setText(studentInfo.getShortDescribe()); + describeTextArea.setText(studentInfo.getDescribe()); + contactTextField.setText(studentInfo.getPhoneNumber()); + idNumberTextField.setText(studentInfo.getIdNumber()); + ageTextField.setText(studentInfo.getAge() + ""); + + switch (studentInfo.getSex()) { + case 0: + sexGroup.selectToggle(maleRadioButton); + break; + case 1: + sexGroup.selectToggle(femaleRadioButton); + break; + } + + new OkhttpUtil() + .url(Constants.UserRequestUrls.USER_AVATAR_VIEW_URL) + .token(token) + .successCallback(response -> Platform.runLater(() -> setAvatar(avatarImageView, response))) + .executeAsync(OkhttpUtil.RequestMethod.GET); + } + + public static void setAvatar(ImageView avatarImageView, Response response) { + try { + assert response.body() != null; + byte[] data = response.body().bytes(); + + setAvatar(avatarImageView, data); + } catch (Exception e) { + log.warn("获取头像数据时出现异常", e); + } + } + + public static void setAvatar(ImageView avatarImageView, byte[] data) { + Image avatarImage = new Image(new ByteArrayInputStream(data)); + avatarImageView.setImage(avatarImage); + } +} \ No newline at end of file diff --git a/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/data/ResponseCode.java b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/data/ResponseCode.java new file mode 100644 index 0000000..4ee65e2 --- /dev/null +++ b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/data/ResponseCode.java @@ -0,0 +1,45 @@ +/* + * Class created by lensfrex. + */ + +package net.lensfrex.disillusion.client.data; + +public enum ResponseCode { + SUCCESS(20000, "成功"), + REQUEST_TOO_FAST(20001, "技能冷却中..."), + + INVALID_REQUEST(30000, "非法请求"), + PARAM_WRONG(30001, "参数错误"), + + PERMISSION_DENIED(40000, "权限不足"), + TOKEN_EXPIRED(40001, "token过期"), + TOKEN_INVALID(40002, "token无效"), + + IDENTIFY_ERROR(40100, "用户不存在或密码错误"), + USER_ALREADY_EXISTS(40101, "用户名已经被注册"), + + USER_NOT_EXISTS(40103, "用户不存在"), + + SERVER_INTERNAL_ERROR(50000, "服务器内部错误"), + + API_NOT_IMPLEMENT(0, "接口未实现"), + + REQUEST_FILE_DOES_NOT_EXIST(60701, "请求的文件不存在"); + + private final int code; + + private final String message; + + ResponseCode(int code, String message) { + this.code = code; + this.message = message; + } + + public int getCode() { + return code; + } + + public String getMessage() { + return message; + } +} diff --git a/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/data/request/ApplicationRequest.java b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/data/request/ApplicationRequest.java new file mode 100644 index 0000000..2b86690 --- /dev/null +++ b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/data/request/ApplicationRequest.java @@ -0,0 +1,20 @@ +package net.lensfrex.disillusion.client.data.request; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class ApplicationRequest { + /** + * 申请的offerId + */ + private String offerId; + + /** + * 简历id + */ + private String resumeId; +} diff --git a/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/data/request/OfferPublishRequest.java b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/data/request/OfferPublishRequest.java new file mode 100644 index 0000000..bc6fe75 --- /dev/null +++ b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/data/request/OfferPublishRequest.java @@ -0,0 +1,36 @@ +package net.lensfrex.disillusion.client.data.request; + +import lombok.Data; + +@Data +public class OfferPublishRequest { + /** + * 岗位名称 + */ + private String jobName; + + /** + * 工作地点 + */ + private String place; + + /** + * 要求 + */ + private String requirement; + + /** + * 预期薪资 + */ + private int salary; + + /** + * 目标人数 + */ + private int targetNumber; + + /** + * 详细信息 + */ + private String detail; +} diff --git a/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/data/request/ResumeRequest.java b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/data/request/ResumeRequest.java new file mode 100644 index 0000000..4193441 --- /dev/null +++ b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/data/request/ResumeRequest.java @@ -0,0 +1,21 @@ +package net.lensfrex.disillusion.client.data.request; + +import lombok.Data; + +@Data +public class ResumeRequest { + /** + * 简历标题(名称) + */ + private String title; + + /** + * 简历备注信息 + */ + private String remark; + + /** + * 简历文件数据base64 + */ + private String resumeFileBase64; +} diff --git a/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/data/response/ApplicationResponse.java b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/data/response/ApplicationResponse.java new file mode 100644 index 0000000..ac39d92 --- /dev/null +++ b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/data/response/ApplicationResponse.java @@ -0,0 +1,37 @@ +package net.lensfrex.disillusion.client.data.response; + +import com.jfoenix.controls.datamodels.treetable.RecursiveTreeObject; +import lombok.Data; + +/** + * 求职申请 + */ +@Data +public class ApplicationResponse { + private String id; + + /** + * 发布者id + */ + private String publisherId; + + /** + * 目标公司id + */ + private String targetId; + + /** + * 申请的offerId + */ + private String offerId; + + /** + * 简历id + */ + private String resumeId; + + /** + * 申请状态 + */ + private String status; +} \ No newline at end of file diff --git a/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/data/response/EmployerInfoResponse.java b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/data/response/EmployerInfoResponse.java new file mode 100644 index 0000000..959590a --- /dev/null +++ b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/data/response/EmployerInfoResponse.java @@ -0,0 +1,41 @@ +package net.lensfrex.disillusion.client.data.response; + +import lombok.Data; + +/** + * 用人单位信息 + */ +@Data +public class EmployerInfoResponse { + private String id; + + /** + * 公司名称 + */ + private String name; + + /** + * 总部地址 + */ + private String place; + + /** + * 联系方式 + */ + private String contract; + + /** + * 公司简要介绍 + */ + private String shortDescribe; + + /** + * 公司介绍 + */ + private String describe; + + /** + * 所属行业 + */ + private String industry; +} diff --git a/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/data/response/OfferResponse.java b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/data/response/OfferResponse.java new file mode 100644 index 0000000..4587f33 --- /dev/null +++ b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/data/response/OfferResponse.java @@ -0,0 +1,61 @@ +package net.lensfrex.disillusion.client.data.response; + +import lombok.Data; + +import java.util.Objects; + +/** + * 招聘信息 + */ +@Data +public class OfferResponse { + private String id; + + /** + * 发布公司 + */ + private String publisherId; + + /** + * 岗位名称 + */ + private String jobName; + + /** + * 工作地点 + */ + private String place; + + /** + * 要求 + */ + private String requirement; + + /** + * 预期薪资 + */ + private Integer salary; + + /** + * 目标人数 + */ + private Integer targetNumber; + + /** + * 详细信息 + */ + private String detail; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + OfferResponse offer = (OfferResponse) o; + return id.equals(offer.id); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } +} diff --git a/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/data/response/PublicEmployerInfoResponse.java b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/data/response/PublicEmployerInfoResponse.java new file mode 100644 index 0000000..c634920 --- /dev/null +++ b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/data/response/PublicEmployerInfoResponse.java @@ -0,0 +1,40 @@ +package net.lensfrex.disillusion.client.data.response; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = false) +public class PublicEmployerInfoResponse { + private String id; + + /** + * 公司名称 + */ + private String name; + + /** + * 总部地址 + */ + private String place; + + /** + * 联系方式 + */ + private String contract; + + /** + * 公司简要介绍 + */ + private String shortDescribe; + + /** + * 公司介绍 + */ + private String describe; + + /** + * 所属行业 + */ + private String industry; +} diff --git a/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/data/response/PublicStudentInfoResponse.java b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/data/response/PublicStudentInfoResponse.java new file mode 100644 index 0000000..409a9ee --- /dev/null +++ b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/data/response/PublicStudentInfoResponse.java @@ -0,0 +1,38 @@ +package net.lensfrex.disillusion.client.data.response; + +import lombok.Data; + +@Data +public class PublicStudentInfoResponse { + private String id; + + /** + * 真名 + */ + private String realName; + + /** + * 简介 + */ + private String shortDescribe; + + /** + * 介绍 + */ + private String describe; + + /** + * 电话号 + */ + private String phoneNumber; + + /** + * 年龄 + */ + private int age; + + /** + * 性别,0:男;1:女 + */ + private int sex; +} diff --git a/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/data/response/ResumeResponse.java b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/data/response/ResumeResponse.java new file mode 100644 index 0000000..e0ec324 --- /dev/null +++ b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/data/response/ResumeResponse.java @@ -0,0 +1,31 @@ +package net.lensfrex.disillusion.client.data.response; + +import lombok.Data; + +/** + * 简历 + */ +@Data +public class ResumeResponse { + private String id; + + /** + * 学生id + */ + private String ownerId; + + /** + * 简历标题(名称) + */ + private String title; + + /** + * 简历备注信息 + */ + private String remark; + + /** + * 简历文件id,一般来说等同于简历id + */ + private String resumeFileId; +} diff --git a/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/data/response/StudentInfoResponse.java b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/data/response/StudentInfoResponse.java new file mode 100644 index 0000000..a556a57 --- /dev/null +++ b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/data/response/StudentInfoResponse.java @@ -0,0 +1,43 @@ +package net.lensfrex.disillusion.client.data.response; + +import lombok.Data; + +@Data +public class StudentInfoResponse { + private String id; + + /** + * 真名 + */ + private String realName; + + /** + * 简介 + */ + private String shortDescribe; + + /** + * 介绍 + */ + private String describe; + + /** + * 电话号 + */ + private String phoneNumber; + + /** + * 身份证号 + */ + private String idNumber; + + /** + * 年龄 + */ + private int age; + + /** + * 性别,0:男;1:女 + */ + private int sex; +} diff --git a/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/util/Consumer.java b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/util/Consumer.java new file mode 100644 index 0000000..8818421 --- /dev/null +++ b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/util/Consumer.java @@ -0,0 +1,8 @@ +package net.lensfrex.disillusion.client.util; + +import java.io.IOException; + +public interface Consumer { + + void accept(T t) throws IOException; +} \ No newline at end of file diff --git a/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/util/DialogMaker.java b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/util/DialogMaker.java new file mode 100644 index 0000000..d3f689f --- /dev/null +++ b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/util/DialogMaker.java @@ -0,0 +1,130 @@ +package net.lensfrex.disillusion.client.util; + +import com.jfoenix.controls.JFXButton; +import com.jfoenix.controls.JFXDialog; +import com.jfoenix.controls.JFXDialogLayout; +import javafx.beans.NamedArg; +import javafx.event.ActionEvent; +import javafx.event.EventHandler; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.Scene; +import javafx.scene.control.Label; +import javafx.scene.control.TextArea; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Pane; +import javafx.scene.layout.StackPane; +import javafx.scene.paint.Paint; +import javafx.scene.text.Font; +import javafx.scene.text.FontWeight; +import javafx.scene.text.Text; +import javafx.stage.Stage; + +public class DialogMaker { + Pane rootPane; + + public DialogMaker(@NamedArg("rootPane") Pane rootPane) { + this.rootPane = rootPane; + } + + public void createMessageDialog(@NamedArg("title") String title, @NamedArg("message") String message) { + JFXButton OKButton = new JFXButton("了解!"); + OKButton.setFont(Font.font("Microsoft YaHei", FontWeight.BOLD, 12)); + OKButton.setPrefWidth(60); + OKButton.setPrefHeight(30); + + Text messageText = new Text(message); + messageText.setFont(Font.font("Microsoft YaHei", 14)); + + createDialog(title, messageText, OKButton); + } + + //创建只有一个按钮的dialog + public void createDialogWithOneBtn(@NamedArg("title") String title, @NamedArg("theBody") Node body, @NamedArg("OKEvent") EventHandler event) { + //dialog.setPrefHeight(rootPane.getPrefHeight()); + //dialog.setPrefWidth(rootPane.getPrefWidth()); + + JFXButton OKButton = new JFXButton("好的!"); + OKButton.setFont(Font.font("Microsoft YaHei", FontWeight.BOLD, 12)); + OKButton.setPrefWidth(60); + OKButton.setPrefHeight(30); + + createDialog(title, body, OKButton); + + if (event != null) { + OKButton.addEventHandler(ActionEvent.ACTION, event); + } + } + + public void createDialogWithOneBtn(@NamedArg("title") String title, @NamedArg("theBody") Node body) { + createDialogWithOneBtn(title, body, null); + } + + //创建有OK和cancel按钮的dialog + public void createDialogWithOKAndCancel(@NamedArg("title") String title, @NamedArg("message") String message, @NamedArg("OKEvent") EventHandler OKEvent) { + Text messageText = new Text(message); + messageText.setFont(Font.font("Microsoft YaHei", 14)); + + createDialogWithOKAndCancel(title, messageText, OKEvent); + } + + //创建有OK和cancel按钮的dialog + public void createDialogWithOKAndCancel(@NamedArg("title") String title, @NamedArg("body") Node body, @NamedArg("OKEvent") EventHandler OKEvent) { + JFXButton CancelButton = new JFXButton("手滑了"); + CancelButton.setFont(Font.font("Microsoft YaHei", FontWeight.BOLD, 12)); + CancelButton.setPrefWidth(60); + CancelButton.setPrefHeight(30); + + JFXButton OKButton = new JFXButton("是!"); + OKButton.setFont(Font.font("Microsoft YaHei", FontWeight.BOLD, 12)); + OKButton.setPrefWidth(60); + OKButton.setPrefHeight(30); + OKButton.setTextFill(Paint.valueOf("red")); + OKButton.addEventHandler(ActionEvent.ACTION, OKEvent); + + createDialog(title, body, CancelButton, OKButton); + } + + public void createDialog(@NamedArg("title") String title, @NamedArg("theBody") Node body, @NamedArg("buttons") JFXButton... buttons) { + + JFXDialogLayout content = new JFXDialogLayout(); + + Label titleLabel = new Label(title); + titleLabel.setFont(Font.font("Microsoft YaHei", FontWeight.BOLD, 22)); + content.setHeading(titleLabel); + + content.setBody(body); + content.setAlignment(Pos.CENTER); + + StackPane tempPane = new StackPane(); + tempPane.setPrefHeight(rootPane.getPrefHeight()); + tempPane.setPrefWidth(rootPane.getPrefWidth()); + rootPane.getChildren().add(tempPane); + + JFXDialog dialog = new JFXDialog(tempPane, content, JFXDialog.DialogTransition.TOP); + + dialog.setOnDialogClosed(event -> rootPane.getChildren().remove(tempPane)); + + for (JFXButton button : buttons) { + button.addEventHandler(ActionEvent.ACTION, e -> dialog.close()); + } + + content.setActions(buttons); + + dialog.show(); + } + + + public static void errorMessageDialog(String message) { + HBox box = new HBox(new TextArea(message)); + box.setAlignment(Pos.CENTER); + + Scene scene = new Scene(box, 500, 350); + + Stage stage = new Stage(); + stage.setTitle("出事了"); + stage.setScene(scene); + stage.show(); + } + +} diff --git a/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/util/ImageUtil.java b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/util/ImageUtil.java new file mode 100644 index 0000000..4f94c9f --- /dev/null +++ b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/util/ImageUtil.java @@ -0,0 +1,20 @@ +package net.lensfrex.disillusion.client.util; + +import net.coobird.thumbnailator.Thumbnails; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; + +public class ImageUtil { + public static byte[] processImage(File inputFile, float scale, float level, String format) throws IOException { + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + Thumbnails.of(inputFile) + .scale(scale) + .outputQuality(level) + .outputFormat(format) + .toOutputStream(byteArrayOutputStream); + + return byteArrayOutputStream.toByteArray(); + } +} diff --git a/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/util/OkhttpUtil.java b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/util/OkhttpUtil.java new file mode 100644 index 0000000..edd9cbb --- /dev/null +++ b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/util/OkhttpUtil.java @@ -0,0 +1,173 @@ +package net.lensfrex.disillusion.client.util; + +import cn.hutool.core.util.URLUtil; +import cn.hutool.json.JSONUtil; +import net.lensfrex.disillusion.client.config.Constants; +import okhttp3.*; +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + +public class OkhttpUtil { + private static final OkHttpClient client = Constants.globalClient; + + private Request request; + private RequestBody requestBody; + + private final Request.Builder requestBuilder; + private FormBody.Builder formBuilder; + + private Consumer successCallback; + private java.util.function.Consumer failCallback; + + private String url; + + private final Callback callbackWrapper = new Callback() { + @Override + public void onFailure(@NotNull Call call, @NotNull IOException e) { + try { + failCallback.accept(e); + } catch (Exception ex) { + e.printStackTrace(); + } + } + + @Override + public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException { + try { + successCallback.accept(response); + } catch (Exception e) { + e.printStackTrace(); + } + } + }; + + public OkhttpUtil() { + requestBuilder = new Request.Builder(); + } + + public OkhttpUtil url(String url) { + this.url = url; + return this; + } + + private Map queryParams; + + public OkhttpUtil addQueryParam(String key, String value) { + if (queryParams == null) { + queryParams = new HashMap<>(10); + } + + queryParams.put(key, value); + + return this; + } + + public OkhttpUtil token(String token) { + return this.addHeader("token", token); + } + + public OkhttpUtil addHeader(String key, String value) { + requestBuilder.addHeader(key, value); + + return this; + } + + public OkhttpUtil successCallback(Consumer successCallback) { + this.successCallback = successCallback; + + return this; + } + + public OkhttpUtil failCallback(java.util.function.Consumer failCallbackCallback) { + this.failCallback = failCallbackCallback; + + return this; + } + + public OkhttpUtil callback(Consumer successCallback, java.util.function.Consumer failCallbackCallback) { + this.successCallback = successCallback; + this.failCallback = failCallbackCallback; + + return this; + } + + public OkhttpUtil form(String key, String value) { + if (formBuilder == null) { + formBuilder = new FormBody.Builder(); + } + + formBuilder.add(key, value); + + return this; + } + + public OkhttpUtil body(String data, String contentType) { + requestBody = RequestBody.create(data, MediaType.parse(contentType)); + + return this; + } + + public OkhttpUtil body(Object data) { + String jsonData = JSONUtil.toJsonStr(data); + return body(jsonData, "application/json"); + } + + public OkhttpUtil body(byte[] data, String contentType) { + requestBody = RequestBody.create(data, MediaType.parse(contentType)); + + return this; + } + + private static final byte[] EMPTY_DATA = "".getBytes(StandardCharsets.UTF_8); + + public void executeAsync(RequestMethod method) { + this.execute(method).enqueue(callbackWrapper); + } + + public Response executeSync(RequestMethod method) { + try { + return execute(method).execute(); + } catch (IOException e) { + if (failCallback != null) { + failCallback.accept(e); + } + } + + return null; + } + + private Call execute(RequestMethod method) { + if (queryParams == null) { + requestBuilder.url(URLUtil.url(url)); + } else { + String queryParamString = URLUtil.buildQuery(queryParams, StandardCharsets.UTF_8); + requestBuilder.url(URLUtil.url(url + "?" + queryParamString)); + } + + switch (method) { + case GET: + request = requestBuilder.get().build(); + return client.newCall(request); + case POST: + if (requestBody == null) { + if (formBuilder == null) { + requestBody = RequestBody.create(EMPTY_DATA); + } else { + requestBody = formBuilder.build(); + } + } + request = requestBuilder.post(requestBody).build(); + return client.newCall(request); + default: + throw new IllegalArgumentException(); + } + } + + public static enum RequestMethod { + GET, POST + } +} \ No newline at end of file diff --git a/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/util/ResponseHandler.java b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/util/ResponseHandler.java new file mode 100644 index 0000000..cb56630 --- /dev/null +++ b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/util/ResponseHandler.java @@ -0,0 +1,72 @@ +package net.lensfrex.disillusion.client.util; + +import cn.hutool.json.JSONObject; +import cn.hutool.json.JSONUtil; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import net.lensfrex.disillusion.client.data.ResponseCode; +import okhttp3.Response; + +import java.io.IOException; + +@Slf4j +public class ResponseHandler { + @SafeVarargs + public static Result parse(Response response, Consumer... errorCallbacks) throws IOException { + assert response.body() != null; + String responseString = response.body().string(); + JSONObject responseJson = JSONUtil.parseObj(responseString); + + boolean success = true; + Integer code = responseJson.getInt("code"); + String message = responseJson.getStr("message"); + if (code == null || !code.equals(ResponseCode.SUCCESS.getCode())) { + if (message == null) { + message = responseString; + } + if (errorCallbacks != null) { + for (Consumer errorCallback : errorCallbacks) { + errorCallback.accept(message); + } + } + + success = false; + log.warn("请求错误:" + message); + } + + return new Result(success, message, responseJson.getStr("data")); + } + + private final Response response; + + private Consumer errorCallback; + private Consumer successCallback; + + public ResponseHandler(Response response) { + this.response = response; + } + + public ResponseHandler errorCallback(Consumer errorCallback) { + this.errorCallback = errorCallback; + return this; + } + + public ResponseHandler successCallback(Consumer successCallback) { + this.successCallback = successCallback; + return this; + } + + public void process() throws IOException { + Result result = parse(response, errorCallback); + if (result.success) { + successCallback.accept(result.data); + } + } + + @AllArgsConstructor + public static class Result { + public final boolean success; + public final String message; + public final String data; + } +} diff --git a/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/util/ThreadSafeDialogMaker.java b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/util/ThreadSafeDialogMaker.java new file mode 100644 index 0000000..8f1f9c4 --- /dev/null +++ b/disillusion-client-jfx17/src/main/java/net/lensfrex/disillusion/client/util/ThreadSafeDialogMaker.java @@ -0,0 +1,43 @@ +package net.lensfrex.disillusion.client.util; + +import com.jfoenix.controls.JFXButton; +import com.jfoenix.controls.events.JFXDialogEvent; +import javafx.application.Platform; +import javafx.beans.NamedArg; +import javafx.event.ActionEvent; +import javafx.event.EventHandler; +import javafx.scene.Node; +import javafx.scene.layout.Pane; + +public class ThreadSafeDialogMaker extends DialogMaker { + public ThreadSafeDialogMaker(@NamedArg("rootPane") Pane rootPane) { + super(rootPane); + } + + public void createMessageDialog(@NamedArg("title") String title, @NamedArg("message") String message) { + Platform.runLater(() -> super.createMessageDialog(title, message)); + } + + //创建只有一个按钮的dialog + public void createDialogWithOneBtn(@NamedArg("title") String title, @NamedArg("theBody") Node body, @NamedArg("OKEvent") EventHandler event) { + Platform.runLater(() -> super.createDialogWithOneBtn(title, body, event)); + } + + public void createDialogWithOneBtn(@NamedArg("title") String title, @NamedArg("theBody") Node body) { + createDialogWithOneBtn(title, body, null); + } + + //创建有OK和cancel按钮的dialog + public void createDialogWithOKAndCancel(@NamedArg("title") String title, @NamedArg("message") String message, @NamedArg("OKEvent") EventHandler OKEvent) { + Platform.runLater(() -> super.createDialogWithOKAndCancel(title, message, OKEvent)); + } + + //创建有OK和cancel按钮的dialog + public void createDialogWithOKAndCancel(@NamedArg("title") String title, @NamedArg("body") Node body, @NamedArg("OKEvent") EventHandler OKEvent) { + Platform.runLater(() -> super.createDialogWithOKAndCancel(title, body, OKEvent)); + } + + public void createDialog(@NamedArg("title") String title, @NamedArg("theBody") Node body, @NamedArg("buttons") JFXButton... buttons) { + Platform.runLater(() -> super.createDialog(title, body, buttons)); + } +} diff --git a/disillusion-client-jfx17/src/main/resources/META-INF/MANIFEST.MF b/disillusion-client-jfx17/src/main/resources/META-INF/MANIFEST.MF new file mode 100644 index 0000000..2bae2da --- /dev/null +++ b/disillusion-client-jfx17/src/main/resources/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: net.lensfrex.disillusion.client.ClientMain + diff --git a/disillusion-client-jfx17/src/main/resources/exampleFile.txt b/disillusion-client-jfx17/src/main/resources/exampleFile.txt new file mode 100644 index 0000000..0e9ca85 --- /dev/null +++ b/disillusion-client-jfx17/src/main/resources/exampleFile.txt @@ -0,0 +1,13 @@ + 前期经过自愿报名并审核成功的学生,根据所报微专业的培养方案,自行选择相应课程。 + + 9.认真对照培养方案,选课时务必仔细核对课程信息,确保“课程代码”、“课程名称”、“学分”这三项重要信息与培养方案要求相符。 + + 10.禁止同一时间修读两门及以上课程,系统会提示时间冲突并禁止选课。提前了解专业教学进程(实习、实践、课程设计等)情况,不能选择与本专业其它教学任务周次有冲突的课堂。 + + 11.因培养方案变动、转专业等原因,计划修读的课程确定不再开课的,请先查询本科生课程学分认定对照表并咨询开课学院,再进行选课。待课程修读完获得学分后,再按照本科生院发布的相关要求办理学分认定。 + + 12.学生选课、听课、考核必须一致。学生不能参加未选择课程的学习和考核,即使参加了学习和考核,如果没有选课,也是无效的,将不记录成绩。 + + 13.学生凭学号、密码选课,认真对待,并对自己的选课行为负责。密码必须妥善保管,不得代替他人选课,不得借用、盗用他人学号及密码选课。 + + 14.学生登录本科教学综合管理系统需通过学校统一身份认证平台登录,登录方式、密码重置方式详见以下两个通知:https://jwc.wust.edu.cn/2022/0226/c1925a255654/page.htm、https://jwc.wust.edu.cn/2022/0302/c1925a255786/page.htm。 \ No newline at end of file diff --git a/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/css/style.css b/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/css/style.css new file mode 100644 index 0000000..5811ea0 --- /dev/null +++ b/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/css/style.css @@ -0,0 +1,60 @@ +/******************************************************************************* + * * + * Scroll Bar * + * * + ******************************************************************************/ + +.scroll-bar:vertical > .track-background, .scroll-bar:horizontal > .track-background { + -fx-background-color: #F1F1F1; + -fx-background-insets: 0.0; +} + +.scroll-bar:vertical > .thumb, .scroll-bar:horizontal > .thumb { + -fx-background-color: #BCBCBC; + -fx-background-insets: 0.0; + -fx-background-radius: 1.0; +} + +.scroll-bar > .increment-button, .scroll-bar > .decrement-button { + -fx-padding: 5 2 5 2; +} + +.scroll-bar > .increment-button, .scroll-bar > .decrement-button, .scroll-bar:hover > .increment-button, .scroll-bar:hover > .decrement-button { + -fx-background-color: transparent; +} + +.scroll-bar > .increment-button > .increment-arrow, .scroll-bar > .decrement-button > .decrement-arrow { + -fx-background-color: rgb(150.0, 150.0, 150.0); +} + +.scroll-bar > .increment-button > .increment-arrow { + -fx-shape: "M298 426h428l-214 214z"; +} + +.scroll-bar > .decrement-button > .decrement-arrow { + -fx-shape: "M298 598l214-214 214 214h-428z"; +} + +/******************************************************************************* + * * + * Scroll Pane * + * * + ******************************************************************************/ + +.scroll-pane { + -fx-background: white; + -fx-background-insets: 0; + -fx-padding: 0; +} + +.scroll-pane:focused { + -fx-background-insets: 0; +} + +.scroll-pane .corner { + -fx-background-insets: 0; +} + +.jfx-list-cell:odd:selected .jfx-rippler .jfx-list-cell:even:selected { + -fx-background-color: rgba(255, 0, 0, 0.2); +} \ No newline at end of file diff --git a/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/employer/application/applicationDealPane.fxml b/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/employer/application/applicationDealPane.fxml new file mode 100644 index 0000000..c73d8c6 --- /dev/null +++ b/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/employer/application/applicationDealPane.fxml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/employer/application/applicationListItem.fxml b/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/employer/application/applicationListItem.fxml new file mode 100644 index 0000000..968d155 --- /dev/null +++ b/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/employer/application/applicationListItem.fxml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/employer/application/applicationListPage.fxml b/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/employer/application/applicationListPage.fxml new file mode 100644 index 0000000..9b986e9 --- /dev/null +++ b/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/employer/application/applicationListPage.fxml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/employer/application/applicationManagePage.fxml b/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/employer/application/applicationManagePage.fxml new file mode 100644 index 0000000..1ef2996 --- /dev/null +++ b/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/employer/application/applicationManagePage.fxml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/employer/application/applicationOfferCard.fxml b/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/employer/application/applicationOfferCard.fxml new file mode 100644 index 0000000..3b3cb7d --- /dev/null +++ b/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/employer/application/applicationOfferCard.fxml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/employer/application/resumeDownloadPane.fxml b/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/employer/application/resumeDownloadPane.fxml new file mode 100644 index 0000000..21cdd17 --- /dev/null +++ b/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/employer/application/resumeDownloadPane.fxml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + diff --git a/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/employer/application/studentProfile.fxml b/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/employer/application/studentProfile.fxml new file mode 100644 index 0000000..3847b55 --- /dev/null +++ b/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/employer/application/studentProfile.fxml @@ -0,0 +1,174 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 100 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/employer/employerMain.fxml b/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/employer/employerMain.fxml new file mode 100644 index 0000000..f6a51f9 --- /dev/null +++ b/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/employer/employerMain.fxml @@ -0,0 +1,113 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 32 + + + + + 5 + 2.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/employer/offer/offerCard.fxml b/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/employer/offer/offerCard.fxml new file mode 100644 index 0000000..d008d80 --- /dev/null +++ b/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/employer/offer/offerCard.fxml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/employer/offer/offerDetailPane.fxml b/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/employer/offer/offerDetailPane.fxml new file mode 100644 index 0000000..a0ff90b --- /dev/null +++ b/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/employer/offer/offerDetailPane.fxml @@ -0,0 +1,110 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/employer/offer/offerManagePage.fxml b/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/employer/offer/offerManagePage.fxml new file mode 100644 index 0000000..47bec6e --- /dev/null +++ b/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/employer/offer/offerManagePage.fxml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/employer/profile.fxml b/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/employer/profile.fxml new file mode 100644 index 0000000..9eec6be --- /dev/null +++ b/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/employer/profile.fxml @@ -0,0 +1,183 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 100 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/loginMain.fxml b/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/loginMain.fxml new file mode 100644 index 0000000..c968a87 --- /dev/null +++ b/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/loginMain.fxml @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/student/application/applicationCard.fxml b/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/student/application/applicationCard.fxml new file mode 100644 index 0000000..a19e81b --- /dev/null +++ b/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/student/application/applicationCard.fxml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/student/application/applicationManagePage.fxml b/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/student/application/applicationManagePage.fxml new file mode 100644 index 0000000..bb51bee --- /dev/null +++ b/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/student/application/applicationManagePage.fxml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/student/application/applicationPost.fxml b/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/student/application/applicationPost.fxml new file mode 100644 index 0000000..f4e846c --- /dev/null +++ b/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/student/application/applicationPost.fxml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + diff --git a/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/student/application/applicationPostPage.fxml b/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/student/application/applicationPostPage.fxml new file mode 100644 index 0000000..4f511dc --- /dev/null +++ b/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/student/application/applicationPostPage.fxml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + diff --git a/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/student/offer/employerProfile.fxml b/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/student/offer/employerProfile.fxml new file mode 100644 index 0000000..46288a9 --- /dev/null +++ b/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/student/offer/employerProfile.fxml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 100 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/student/offer/offerCard.fxml b/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/student/offer/offerCard.fxml new file mode 100644 index 0000000..a152354 --- /dev/null +++ b/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/student/offer/offerCard.fxml @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/student/offer/offerDetail.fxml b/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/student/offer/offerDetail.fxml new file mode 100644 index 0000000..7d7b01f --- /dev/null +++ b/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/student/offer/offerDetail.fxml @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/student/offer/offerSquare.fxml b/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/student/offer/offerSquare.fxml new file mode 100644 index 0000000..0a37abc --- /dev/null +++ b/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/student/offer/offerSquare.fxml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/student/profile.fxml b/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/student/profile.fxml new file mode 100644 index 0000000..f09fd8f --- /dev/null +++ b/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/student/profile.fxml @@ -0,0 +1,218 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 100 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/student/resume/resumeCard.fxml b/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/student/resume/resumeCard.fxml new file mode 100644 index 0000000..8bb220d --- /dev/null +++ b/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/student/resume/resumeCard.fxml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/student/resume/resumeManagePage.fxml b/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/student/resume/resumeManagePage.fxml new file mode 100644 index 0000000..f0ecb7a --- /dev/null +++ b/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/student/resume/resumeManagePage.fxml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/student/resume/resumeUpload.fxml b/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/student/resume/resumeUpload.fxml new file mode 100644 index 0000000..f8856e5 --- /dev/null +++ b/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/student/resume/resumeUpload.fxml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/student/studentMain.fxml b/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/student/studentMain.fxml new file mode 100644 index 0000000..19ea114 --- /dev/null +++ b/disillusion-client-jfx17/src/main/resources/net/lensfrex/disillusion/client/fxml/student/studentMain.fxml @@ -0,0 +1,121 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 32 + + + + + 5 + 2.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/disillusion/.gitignore b/disillusion/.gitignore new file mode 100644 index 0000000..603d7e1 --- /dev/null +++ b/disillusion/.gitignore @@ -0,0 +1,67 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +/pubspec.lock +**/doc/api/ +.dart_tool/ +.packages +build/ + +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +*.log +*.class +mvnw +mvnw.cmd + +/database/ +/delombok/ +*/.mvn diff --git a/disillusion/backend/.flattened-pom.xml b/disillusion/backend/.flattened-pom.xml new file mode 100644 index 0000000..bae2fd0 --- /dev/null +++ b/disillusion/backend/.flattened-pom.xml @@ -0,0 +1,101 @@ + + + 4.0.0 + net.lensfrex + backend + 0.0.1-SNAPSHOT + + + org.springframework.boot + spring-boot-starter-web + 3.0.2 + compile + + + org.springframework.boot + spring-boot-configuration-processor + 3.0.2 + compile + true + + + org.slf4j + slf4j-api + 2.0.6 + compile + + + org.mindrot + jbcrypt + 0.4 + compile + + + com.auth0 + java-jwt + 4.3.0 + compile + + + cn.hutool + hutool-core + 5.8.12 + compile + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + 2.0.2 + compile + + + org.webjars + swagger-ui + 4.15.5 + compile + + + commons-lang + commons-lang + 2.6 + compile + + + net.lensfrex + common + 0.0.1-SNAPSHOT + compile + + + net.lensfrex + virtual-database + 0.0.1-SNAPSHOT + compile + + + jakarta.annotation + jakarta.annotation-api + 2.1.1 + compile + + + javax.annotation + javax.annotation-api + 1.3.2 + compile + + + org.projectlombok + lombok + 1.18.22 + compile + + + org.mapstruct + mapstruct + 1.5.3.Final + compile + + + diff --git a/disillusion/backend/pom.xml b/disillusion/backend/pom.xml new file mode 100644 index 0000000..3ae0fed --- /dev/null +++ b/disillusion/backend/pom.xml @@ -0,0 +1,170 @@ + + + 4.0.0 + + net.lensfrex + disillusion + ${revision} + + + backend + 0.0.1-SNAPSHOT + backend + backend + + 17 + + 3.0.2 + + 3.0.0 + 1.9.6 + + + + org.springframework.boot + spring-boot-starter-web + ${springboot.version} + + + org.springframework.boot + spring-boot-configuration-processor + true + ${springboot.version} + + + + org.slf4j + slf4j-api + 2.0.6 + + + + org.springframework.boot + ${springboot.version} + spring-boot-starter-test + test + + + + + org.mindrot + jbcrypt + 0.4 + + + + + com.auth0 + java-jwt + 4.3.0 + + + + + + + + + + + + + cn.hutool + hutool-core + 5.8.12 + + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + 2.0.2 + + + + org.webjars + swagger-ui + 4.15.5 + + + + commons-lang + commons-lang + 2.6 + + + + net.lensfrex + common + ${revision} + + + net.lensfrex + virtual-database + ${revision} + + + + + + + org.graalvm.buildtools + native-maven-plugin + 0.9.19 + + + org.springframework.boot + spring-boot-maven-plugin + ${springboot.version} + + + + repackage + + + + + + + org.projectlombok + lombok + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 17 + 17 + + + + + + diff --git a/disillusion/backend/src/main/java/net/lensfrex/disillusion/BackendMain.java b/disillusion/backend/src/main/java/net/lensfrex/disillusion/BackendMain.java new file mode 100644 index 0000000..ea2c3c1 --- /dev/null +++ b/disillusion/backend/src/main/java/net/lensfrex/disillusion/BackendMain.java @@ -0,0 +1,13 @@ +package net.lensfrex.disillusion; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class BackendMain { + + public static void main(String[] args) { + SpringApplication.run(BackendMain.class, args); + } + +} diff --git a/disillusion/backend/src/main/java/net/lensfrex/disillusion/DocumentMain.java b/disillusion/backend/src/main/java/net/lensfrex/disillusion/DocumentMain.java new file mode 100644 index 0000000..a5419b3 --- /dev/null +++ b/disillusion/backend/src/main/java/net/lensfrex/disillusion/DocumentMain.java @@ -0,0 +1,24 @@ +//package net.lensfrex.disillusion; +// +//import io.github.yedaxia.apidocs.Docs; +//import io.github.yedaxia.apidocs.DocsConfig; +// +//public class DocumentMain { +// +// public static void main(String[] args) { +// DocsConfig config = new DocsConfig(); +// // 项目根目录 +// config.setProjectPath("./"); +// // 项目名称 +// config.setProjectName("Assembly"); +// // 声明该API的版本 +// config.setApiVersion("V2.0"); +// // 生成API 文档所在目录 +// config.setDocsPath("./doc"); +// // 配置自动生成 +// config.setAutoGenerate(Boolean.TRUE); +// // 执行生成文档 +// Docs.buildHtmlDocs(config); +// } +// +//} diff --git a/disillusion/backend/src/main/java/net/lensfrex/disillusion/annotation/CheckAuth.java b/disillusion/backend/src/main/java/net/lensfrex/disillusion/annotation/CheckAuth.java new file mode 100644 index 0000000..3ccdf6f --- /dev/null +++ b/disillusion/backend/src/main/java/net/lensfrex/disillusion/annotation/CheckAuth.java @@ -0,0 +1,10 @@ +package net.lensfrex.disillusion.annotation; + +import java.lang.annotation.*; + +@Documented +@Inherited +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface CheckAuth { +} diff --git a/disillusion/backend/src/main/java/net/lensfrex/disillusion/configure/ApplicationContextHelper.java b/disillusion/backend/src/main/java/net/lensfrex/disillusion/configure/ApplicationContextHelper.java new file mode 100644 index 0000000..a3795f8 --- /dev/null +++ b/disillusion/backend/src/main/java/net/lensfrex/disillusion/configure/ApplicationContextHelper.java @@ -0,0 +1,24 @@ +package net.lensfrex.disillusion.configure; + +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class ApplicationContextHelper implements ApplicationContextAware { + public static ApplicationContext applicationContext; + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + ApplicationContextHelper.applicationContext = applicationContext; + } + + public static T get(Class clazz) { + return applicationContext.getBean(clazz); + } + + public static Object get(String name) { + return applicationContext.getBean(name); + } +} diff --git a/disillusion/backend/src/main/java/net/lensfrex/disillusion/configure/GlobalConfigure.java b/disillusion/backend/src/main/java/net/lensfrex/disillusion/configure/GlobalConfigure.java new file mode 100644 index 0000000..4ab18f1 --- /dev/null +++ b/disillusion/backend/src/main/java/net/lensfrex/disillusion/configure/GlobalConfigure.java @@ -0,0 +1,24 @@ +package net.lensfrex.disillusion.configure; + +import lombok.Data; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Data +@Configuration +@ConfigurationProperties("disillusion") +public class GlobalConfigure { + private String databaseDir; + + private static String jwtSecret; + + public static String getJwtSecret() { + return jwtSecret; + } + + @Value("${disillusion.tokenSecretKey}") + public void setJwtSecret(String jwtSecret) { + GlobalConfigure.jwtSecret = jwtSecret; + } +} diff --git a/disillusion/backend/src/main/java/net/lensfrex/disillusion/configure/InterceptorConfigure.java b/disillusion/backend/src/main/java/net/lensfrex/disillusion/configure/InterceptorConfigure.java new file mode 100644 index 0000000..5adeb42 --- /dev/null +++ b/disillusion/backend/src/main/java/net/lensfrex/disillusion/configure/InterceptorConfigure.java @@ -0,0 +1,38 @@ +package net.lensfrex.disillusion.configure; + +import net.lensfrex.disillusion.web.interceptor.TokenInterceptor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; + +@Configuration +public class InterceptorConfigure extends WebMvcConfigurationSupport { + @Override + protected void addInterceptors(InterceptorRegistry registry) { + super.addInterceptors(registry); + registry.addInterceptor(this.getTokenInterceptor()).addPathPatterns("/**") + .excludePathPatterns("/user/login") + .excludePathPatterns("/user/register") + .excludePathPatterns("/swagger-ui") + .order(0) + ; + + super.addInterceptors(registry); + } + + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + super.addResourceHandlers(registry); + registry. + addResourceHandler("/swagger-ui/**") + .addResourceLocations("classpath:/META-INF/resources/webjars/swagger-ui/") + .resourceChain(false); + } + + @Bean + public TokenInterceptor getTokenInterceptor() { + return new TokenInterceptor(); + } +} diff --git a/disillusion/backend/src/main/java/net/lensfrex/disillusion/configure/SwaggerConfigure.java b/disillusion/backend/src/main/java/net/lensfrex/disillusion/configure/SwaggerConfigure.java new file mode 100644 index 0000000..5cf2797 --- /dev/null +++ b/disillusion/backend/src/main/java/net/lensfrex/disillusion/configure/SwaggerConfigure.java @@ -0,0 +1,35 @@ +//package net.lensfrex.disillusion.configure; +// +//import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j; +//import org.springframework.context.annotation.Bean; +//import org.springframework.context.annotation.Configuration; +//import springfox.documentation.builders.ApiInfoBuilder; +//import springfox.documentation.builders.PathSelectors; +//import springfox.documentation.builders.RequestHandlerSelectors; +//import springfox.documentation.service.ApiInfo; +//import springfox.documentation.spi.DocumentationType; +//import springfox.documentation.spring.web.plugins.Docket; +//import springfox.documentation.swagger2.annotations.EnableSwagger2; +// +//@Configuration +//@EnableSwagger2 +//@EnableKnife4j +//public class SwaggerConfigure { +// @Bean +// public Docket createRestBmbsApi() { +// return new Docket(DocumentationType.SWAGGER_2) +// .groupName("users") +// .apiInfo(apiInfo()) +// .select() +// .apis(RequestHandlerSelectors.basePackage("net.lensfrex.disillusion.web.controller")) +// .paths(PathSelectors.any()) +// .build(); +// } +// +// private ApiInfo apiInfo() { +// return new ApiInfoBuilder() +// .title("disillusion API") +// .version("0.0.1") +// .build(); +// } +//} diff --git a/disillusion/backend/src/main/java/net/lensfrex/disillusion/dao/DaoBase.java b/disillusion/backend/src/main/java/net/lensfrex/disillusion/dao/DaoBase.java new file mode 100644 index 0000000..4a00163 --- /dev/null +++ b/disillusion/backend/src/main/java/net/lensfrex/disillusion/dao/DaoBase.java @@ -0,0 +1,83 @@ +package net.lensfrex.disillusion.dao; + +import net.lensfrex.disillusion.configure.GlobalConfigure; +import net.lensfrex.disillusion.dao.database.Table; +import net.lensfrex.disillusion.dao.database.VirtualDatabase; +import net.lensfrex.disillusion.data.Hash; +import net.lensfrex.disillusion.data.List; +import net.lensfrex.disillusion.model.StoreData; + +import java.io.IOException; + +public abstract class DaoBase implements Table { + // 表名,如"resume", "offer", "user_info"等 + private final String tableName; + + public DaoBase(GlobalConfigure globalConfigure, String tableName) + throws IOException, ClassNotFoundException { + + this.tableName = tableName; + VirtualDatabase.init(globalConfigure.getDatabaseDir()); + updateData(); + } + + // 存放实际数据Hash表,泛型T需要符合StoreData接口,即具有id字段 + // 此时id作为hash表的key进行存取 + private Hash table; + + @Override + public T select(String id) throws IOException, ClassNotFoundException { + updateData(); + return table.get(id); + } + + /** + * 获取所有数据 + */ + public List getAll() throws IOException, ClassNotFoundException { + updateData(); + return table.allValues(); + } + + @Override + public void delete(T record) throws IOException, ClassNotFoundException { + updateData(); + delete(record.getId()); + } + + /** + * 按照id删除一条数据 + */ + @Override + public void delete(String id) throws IOException, ClassNotFoundException { + updateData(); + table.delete(id); + VirtualDatabase.saveTable(tableName, table); + } + + /** + * 插入或者更新一条数据,当数据记录不存在时则新增,否则更新该条数据 + */ + @Override + public void insertOrUpdate(T record) throws IOException, ClassNotFoundException { + updateData(); + table.put(record.getId(), record); + VirtualDatabase.saveTable(tableName, table); + } + + @Override + public void update(T record) throws IOException, ClassNotFoundException { + updateData(); + table.put(record.getId(), record); + VirtualDatabase.saveTable(tableName, table); + } + + @SuppressWarnings("unchecked") + private void updateData() throws IOException, ClassNotFoundException { + table = (Hash) VirtualDatabase.getTable(tableName, Hash.class); + if (table == null) { + table = new Hash<>(); + VirtualDatabase.saveTable(tableName, table); + } + } +} diff --git a/disillusion/backend/src/main/java/net/lensfrex/disillusion/dao/OfferApplicationTable.java b/disillusion/backend/src/main/java/net/lensfrex/disillusion/dao/OfferApplicationTable.java new file mode 100644 index 0000000..cd89903 --- /dev/null +++ b/disillusion/backend/src/main/java/net/lensfrex/disillusion/dao/OfferApplicationTable.java @@ -0,0 +1,14 @@ +package net.lensfrex.disillusion.dao; + +import net.lensfrex.disillusion.configure.GlobalConfigure; +import net.lensfrex.disillusion.model.OfferApplication; +import org.springframework.stereotype.Component; + +import java.io.IOException; + +@Component +public class OfferApplicationTable extends DaoBase { + public OfferApplicationTable(GlobalConfigure globalConfigure) throws IOException, ClassNotFoundException { + super(globalConfigure, "offer_application"); + } +} diff --git a/disillusion/backend/src/main/java/net/lensfrex/disillusion/dao/OfferStatusTable.java b/disillusion/backend/src/main/java/net/lensfrex/disillusion/dao/OfferStatusTable.java new file mode 100644 index 0000000..a96f7d4 --- /dev/null +++ b/disillusion/backend/src/main/java/net/lensfrex/disillusion/dao/OfferStatusTable.java @@ -0,0 +1,12 @@ +package net.lensfrex.disillusion.dao; + +import net.lensfrex.disillusion.configure.GlobalConfigure; +import net.lensfrex.disillusion.model.OfferStatus; + +import java.io.IOException; + +public class OfferStatusTable extends DaoBase { + public OfferStatusTable(GlobalConfigure globalConfigure) throws IOException, ClassNotFoundException { + super(globalConfigure, "offer_status"); + } +} diff --git a/disillusion/backend/src/main/java/net/lensfrex/disillusion/dao/OfferTable.java b/disillusion/backend/src/main/java/net/lensfrex/disillusion/dao/OfferTable.java new file mode 100644 index 0000000..7574fb5 --- /dev/null +++ b/disillusion/backend/src/main/java/net/lensfrex/disillusion/dao/OfferTable.java @@ -0,0 +1,14 @@ +package net.lensfrex.disillusion.dao; + +import net.lensfrex.disillusion.configure.GlobalConfigure; +import net.lensfrex.disillusion.model.employer.Offer; +import org.springframework.stereotype.Component; + +import java.io.IOException; + +@Component +public class OfferTable extends DaoBase { + public OfferTable(GlobalConfigure globalConfigure) throws IOException, ClassNotFoundException { + super(globalConfigure, "offer"); + } +} diff --git a/disillusion/backend/src/main/java/net/lensfrex/disillusion/dao/ResumeTable.java b/disillusion/backend/src/main/java/net/lensfrex/disillusion/dao/ResumeTable.java new file mode 100644 index 0000000..7b60340 --- /dev/null +++ b/disillusion/backend/src/main/java/net/lensfrex/disillusion/dao/ResumeTable.java @@ -0,0 +1,14 @@ +package net.lensfrex.disillusion.dao; + +import net.lensfrex.disillusion.configure.GlobalConfigure; +import net.lensfrex.disillusion.model.student.Resume; +import org.springframework.stereotype.Component; + +import java.io.IOException; + +@Component +public class ResumeTable extends DaoBase { + public ResumeTable(GlobalConfigure globalConfigure) throws IOException, ClassNotFoundException { + super(globalConfigure, "resume"); + } +} diff --git a/disillusion/backend/src/main/java/net/lensfrex/disillusion/dao/UserInfoTable.java b/disillusion/backend/src/main/java/net/lensfrex/disillusion/dao/UserInfoTable.java new file mode 100644 index 0000000..21b0b76 --- /dev/null +++ b/disillusion/backend/src/main/java/net/lensfrex/disillusion/dao/UserInfoTable.java @@ -0,0 +1,14 @@ +package net.lensfrex.disillusion.dao; + +import net.lensfrex.disillusion.configure.GlobalConfigure; +import net.lensfrex.disillusion.model.UserInfo; +import org.springframework.stereotype.Component; + +import java.io.IOException; + +@Component +public class UserInfoTable extends DaoBase { + public UserInfoTable(GlobalConfigure globalConfigure) throws IOException, ClassNotFoundException { + super(globalConfigure, "user_info"); + } +} diff --git a/disillusion/backend/src/main/java/net/lensfrex/disillusion/dao/UserTable.java b/disillusion/backend/src/main/java/net/lensfrex/disillusion/dao/UserTable.java new file mode 100644 index 0000000..aae4822 --- /dev/null +++ b/disillusion/backend/src/main/java/net/lensfrex/disillusion/dao/UserTable.java @@ -0,0 +1,14 @@ +package net.lensfrex.disillusion.dao; + +import net.lensfrex.disillusion.configure.GlobalConfigure; +import net.lensfrex.disillusion.model.User; +import org.springframework.stereotype.Component; + +import java.io.IOException; + +@Component +public class UserTable extends DaoBase { + public UserTable(GlobalConfigure globalConfigure) throws IOException, ClassNotFoundException { + super(globalConfigure, "user"); + } +} diff --git a/disillusion/backend/src/main/java/net/lensfrex/disillusion/dao/index/EmployerOfferIndex.java b/disillusion/backend/src/main/java/net/lensfrex/disillusion/dao/index/EmployerOfferIndex.java new file mode 100644 index 0000000..665ccda --- /dev/null +++ b/disillusion/backend/src/main/java/net/lensfrex/disillusion/dao/index/EmployerOfferIndex.java @@ -0,0 +1,16 @@ +package net.lensfrex.disillusion.dao.index; + +import net.lensfrex.disillusion.configure.GlobalConfigure; +import net.lensfrex.disillusion.dao.DaoBase; +import net.lensfrex.disillusion.model.MultiIndex; +import org.springframework.stereotype.Component; + +import java.io.IOException; + +@Component +public class EmployerOfferIndex extends DaoBase { + public EmployerOfferIndex(GlobalConfigure globalConfigure) throws IOException, ClassNotFoundException { + super(globalConfigure, "employer_offer_index"); + } +} + diff --git a/disillusion/backend/src/main/java/net/lensfrex/disillusion/dao/index/StudentResumeIndex.java b/disillusion/backend/src/main/java/net/lensfrex/disillusion/dao/index/StudentResumeIndex.java new file mode 100644 index 0000000..3fc0b2d --- /dev/null +++ b/disillusion/backend/src/main/java/net/lensfrex/disillusion/dao/index/StudentResumeIndex.java @@ -0,0 +1,15 @@ +package net.lensfrex.disillusion.dao.index; + +import net.lensfrex.disillusion.configure.GlobalConfigure; +import net.lensfrex.disillusion.dao.DaoBase; +import net.lensfrex.disillusion.model.MultiIndex; +import org.springframework.stereotype.Component; + +import java.io.IOException; + +@Component +public class StudentResumeIndex extends DaoBase { + public StudentResumeIndex(GlobalConfigure globalConfigure) throws IOException, ClassNotFoundException { + super(globalConfigure, "student_resume_index"); + } +} diff --git a/disillusion/backend/src/main/java/net/lensfrex/disillusion/dao/index/application/EmployerApplicationIndex.java b/disillusion/backend/src/main/java/net/lensfrex/disillusion/dao/index/application/EmployerApplicationIndex.java new file mode 100644 index 0000000..d2ffe56 --- /dev/null +++ b/disillusion/backend/src/main/java/net/lensfrex/disillusion/dao/index/application/EmployerApplicationIndex.java @@ -0,0 +1,15 @@ +package net.lensfrex.disillusion.dao.index.application; + +import net.lensfrex.disillusion.configure.GlobalConfigure; +import net.lensfrex.disillusion.dao.DaoBase; +import net.lensfrex.disillusion.model.MultiIndex; +import org.springframework.stereotype.Component; + +import java.io.IOException; + +@Component +public class EmployerApplicationIndex extends DaoBase { + public EmployerApplicationIndex(GlobalConfigure globalConfigure) throws IOException, ClassNotFoundException { + super(globalConfigure, "employer_application_index"); + } +} diff --git a/disillusion/backend/src/main/java/net/lensfrex/disillusion/dao/index/application/OfferApplicationIndex.java b/disillusion/backend/src/main/java/net/lensfrex/disillusion/dao/index/application/OfferApplicationIndex.java new file mode 100644 index 0000000..f2a4be7 --- /dev/null +++ b/disillusion/backend/src/main/java/net/lensfrex/disillusion/dao/index/application/OfferApplicationIndex.java @@ -0,0 +1,15 @@ +package net.lensfrex.disillusion.dao.index.application; + +import net.lensfrex.disillusion.configure.GlobalConfigure; +import net.lensfrex.disillusion.dao.DaoBase; +import net.lensfrex.disillusion.model.MultiIndex; +import org.springframework.stereotype.Component; + +import java.io.IOException; + +@Component +public class OfferApplicationIndex extends DaoBase { + public OfferApplicationIndex(GlobalConfigure globalConfigure) throws IOException, ClassNotFoundException { + super(globalConfigure, "offer_application_index"); + } +} diff --git a/disillusion/backend/src/main/java/net/lensfrex/disillusion/dao/index/application/StudentApplicationIndex.java b/disillusion/backend/src/main/java/net/lensfrex/disillusion/dao/index/application/StudentApplicationIndex.java new file mode 100644 index 0000000..610f2d4 --- /dev/null +++ b/disillusion/backend/src/main/java/net/lensfrex/disillusion/dao/index/application/StudentApplicationIndex.java @@ -0,0 +1,15 @@ +package net.lensfrex.disillusion.dao.index.application; + +import net.lensfrex.disillusion.configure.GlobalConfigure; +import net.lensfrex.disillusion.dao.DaoBase; +import net.lensfrex.disillusion.model.MultiIndex; +import org.springframework.stereotype.Component; + +import java.io.IOException; + +@Component +public class StudentApplicationIndex extends DaoBase { + public StudentApplicationIndex(GlobalConfigure globalConfigure) throws IOException, ClassNotFoundException { + super(globalConfigure, "student_application_index"); + } +} diff --git a/disillusion/backend/src/main/java/net/lensfrex/disillusion/util/TokenUtil.java b/disillusion/backend/src/main/java/net/lensfrex/disillusion/util/TokenUtil.java new file mode 100644 index 0000000..726d9a8 --- /dev/null +++ b/disillusion/backend/src/main/java/net/lensfrex/disillusion/util/TokenUtil.java @@ -0,0 +1,46 @@ +package net.lensfrex.disillusion.util; + +import com.auth0.jwt.JWT; +import com.auth0.jwt.JWTVerifier; +import com.auth0.jwt.algorithms.Algorithm; +import com.auth0.jwt.exceptions.JWTVerificationException; +import net.lensfrex.disillusion.configure.GlobalConfigure; +import org.springframework.stereotype.Component; + +import java.time.Instant; +import java.time.temporal.ChronoUnit; + +@Component +public class TokenUtil { + + public TokenUtil(GlobalConfigure globalConfigure) { + } + + public static String signNewToken(String user) { + String secret = GlobalConfigure.getJwtSecret(); + return JWT.create() + .withClaim("user", user) + .withClaim("issue", Instant.now().getNano()) + .withExpiresAt(Instant.now().plus(30, ChronoUnit.DAYS)) + .sign(Algorithm.HMAC256(secret)); + } + + public static boolean verify(String token) { + String secret = GlobalConfigure.getJwtSecret(); + JWTVerifier verifier = JWT.require(Algorithm.HMAC256(secret)).build(); + try { + verifier.verify(token); + } catch (JWTVerificationException e) { + return false; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + + return true; + } + + public static String getUsernameFromToken(String token) { + return JWT.decode(token).getClaim("user").asString(); + } +} diff --git a/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/controller/OfferController.java b/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/controller/OfferController.java new file mode 100644 index 0000000..0a566f6 --- /dev/null +++ b/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/controller/OfferController.java @@ -0,0 +1,158 @@ +package net.lensfrex.disillusion.web.controller; + +import net.lensfrex.disillusion.annotation.CheckAuth; +import net.lensfrex.disillusion.model.OfferApplication; +import net.lensfrex.disillusion.model.User; +import net.lensfrex.disillusion.model.employer.Offer; +import net.lensfrex.disillusion.response.general.Response; +import net.lensfrex.disillusion.response.general.ResponseCode; +import net.lensfrex.disillusion.util.TokenUtil; +import net.lensfrex.disillusion.web.response.OfferListResponse; +import net.lensfrex.disillusion.web.service.employer.OfferService; +import net.lensfrex.disillusion.web.service.student.ApplicationService; +import net.lensfrex.disillusion.web.service.user.UserService; +import org.springframework.beans.BeanUtils; +import org.springframework.web.bind.annotation.*; + +import java.io.IOException; +import java.util.Arrays; + +@RestController +@RequestMapping("/offer") +public class OfferController { + + private final OfferService offerService; + private final UserService userService; + private final ApplicationService applicationService; + + public OfferController(OfferService offerService, UserService userService, + ApplicationService applicationService) { + + this.offerService = offerService; + this.userService = userService; + this.applicationService = applicationService; + } + + private static final String OFFER_FILTER_EMPLOYER = "by_employer"; + private static final String OFFER_FILTER_JOB_NAME = "by_job_name"; + private static final String OFFER_FILTER_SALARY = "by_salary"; + + @GetMapping("/search") + public Response searchOffer(@RequestParam("filter") String filter, @RequestParam("value") String filterValue) + throws IOException, ClassNotFoundException { + + Offer[] result = switch (filter) { + case OFFER_FILTER_EMPLOYER -> offerService.searchOfferByUser(filterValue); + case OFFER_FILTER_JOB_NAME -> offerService.searchOfferByName(filterValue); + case OFFER_FILTER_SALARY -> offerService.getAllOfferBySalary(filterValue); + default -> throw new IllegalStateException("Unexpected value: " + filter); + }; + + return Response.success(result); + } + + private static final OfferListResponse.OfferResponse[] EMPTY_OFFER = new OfferListResponse.OfferResponse[]{}; + + @GetMapping("/getAll") + public Response getOffer(@RequestParam("page") Integer page, @RequestParam("size") Integer size) + throws IOException, ClassNotFoundException { + Offer[] allOffer = offerService.getAllOffer(); + + int start = (page - 1) * size; + if (start > allOffer.length) { + OfferListResponse response = new OfferListResponse(); + response.setTotal(0); + response.setPage(0); + response.setSize(0); + response.setResult(EMPTY_OFFER); + + return Response.success(response); + } + + Offer[] offers = Arrays.copyOfRange(allOffer, start, Math.min(start + size, allOffer.length)); + OfferListResponse response = new OfferListResponse(); + OfferListResponse.OfferResponse[] offerList = new OfferListResponse.OfferResponse[offers.length]; + for (int i = 0; i < offers.length; i++) { + Offer offer = offers[i]; + offerList[i] = new OfferListResponse.OfferResponse(); + BeanUtils.copyProperties(offer, offerList[i]); + } + + response.setTotal(allOffer.length); + response.setPage(page); + response.setSize(size); + response.setResult(offerList); + + return Response.success(response); + } + + @GetMapping("/get") + public Response getOffer(@RequestParam("id") String id) + throws IOException, ClassNotFoundException { + + Offer offer = offerService.getOffer(id); + if (offer == null) { + return Response.success(); + } + + OfferListResponse.OfferResponse response = new OfferListResponse.OfferResponse(); + User publisher = userService.getAccount(offer.getPublisherId()); + if (publisher == null) { + return Response.success(); + } + + BeanUtils.copyProperties(offer, response); + + return Response.success(response); + } + + @GetMapping("/progress") + public Response getStatus(@RequestParam("id") String id) + throws IOException, ClassNotFoundException { + Offer offer = offerService.getOffer(id); + if (offer == null) { + return Response.success("unknown"); + } + + int target = offer.getTargetNumber(); + + int count = 0; + + OfferApplication[] applications = applicationService.getAllApplicationByOfferId(id); + for (OfferApplication application : applications) { + if (application == null) { + continue; + } + if (application.getStatus() == OfferApplication.Status.ACCEPTED) { + count++; + } + } + + return Response.success(count + "/" + target); + } + + private static final String samples = """ + 8.微专业课程选课要求: + + 前期经过自愿报名并审核成功的学生,根据所报微专业的培养方案,自行选择相应课程。 + + 9.认真对照培养方案,选课时务必仔细核对课程信息,确保“课程代码”、“课程名称”、“学分”这三项重要信息与培养方案要求相符。 + + 10.禁止同一时间修读两门及以上课程,系统会提示时间冲突并禁止选课。提前了解专业教学进程(实习、实践、课程设计等)情况,不能选择与本专业其它教学任务周次有冲突的课堂。 + + 11.因培养方案变动、转专业等原因,计划修读的课程确定不再开课的,请先查询本科生课程学分认定对照表并咨询开课学院,再进行选课。待课程修读完获得学分后,再按照本科生院发布的相关要求办理学分认定。 + + 12.学生选课、听课、考核必须一致。学生不能参加未选择课程的学习和考核,即使参加了学习和考核,如果没有选课,也是无效的,将不记录成绩。 + + 13.学生凭学号、密码选课,认真对待,并对自己的选课行为负责。密码必须妥善保管,不得代替他人选课,不得借用、盗用他人学号及密码选课。 + + 14.学生登录本科教学综合管理系统需通过学校统一身份认证平台登录,登录方式、密码重置方式详见以下两个通知:https://jwc.wust.edu.cn/2022/0226/c1925a255654/page.htm、https://jwc.wust.edu.cn/2022/0302/c1925a255786/page.htm。 + + 对账号、密码有疑问的,请按照登录页面上“常见问题”、“忘记密码”等提示进行操作。 + + 15.本次选课为2022-2023学年第二学期课程的最后一次选课,请同学们严格按照时间安排进行退改选,尤其要在规定的时间内查看课表,以免出现课堂因人数不足取消而本人又没有及时参加补选,再次造成某些课程漏选的情况。无故不参加选课或错过选课机会,后果自负。\s + + 本科生院教务管理办公室 + + 二〇二三年二月二十三日"""; +} diff --git a/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/controller/ResumeController.java b/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/controller/ResumeController.java new file mode 100644 index 0000000..e9485b8 --- /dev/null +++ b/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/controller/ResumeController.java @@ -0,0 +1,47 @@ +package net.lensfrex.disillusion.web.controller; + +import net.lensfrex.disillusion.annotation.CheckAuth; +import net.lensfrex.disillusion.model.User; +import net.lensfrex.disillusion.model.student.Resume; +import net.lensfrex.disillusion.response.general.Response; +import net.lensfrex.disillusion.response.general.ResponseCode; +import net.lensfrex.disillusion.util.TokenUtil; +import net.lensfrex.disillusion.web.service.student.ResumeService; +import net.lensfrex.disillusion.web.service.user.UserService; +import org.springframework.web.bind.annotation.*; + +import java.io.IOException; + +@RestController +@RequestMapping("/resume") +public class ResumeController { + private final ResumeService resumeService; + + private final UserService userService; + + public ResumeController(ResumeService resumeService, + UserService userService) { + this.resumeService = resumeService; + this.userService = userService; + } + + @CheckAuth + @GetMapping("/view") + public Response view(@RequestHeader("token") String token, @RequestParam("id") String id) + throws IOException, ClassNotFoundException { + + Resume result = resumeService.getResume(id); + + String username = TokenUtil.getUsernameFromToken(token); + User user = userService.getAccount(username); + if (result == null) { + return Response.success(); + } + + if (result.getOwnerId().equals(username) || user.getUserType() == User.UserType.EMPLOYER) { + return Response.success(result); + } else { + return Response.error(ResponseCode.PERMISSION_DENIED); + } + } +} diff --git a/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/controller/TestController.java b/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/controller/TestController.java new file mode 100644 index 0000000..b233ceb --- /dev/null +++ b/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/controller/TestController.java @@ -0,0 +1,19 @@ +package net.lensfrex.disillusion.web.controller; + +import net.lensfrex.disillusion.annotation.CheckAuth; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class TestController { + @RequestMapping("/ping") + public String ping() { + return "pong"; + } + + @CheckAuth + @RequestMapping("/ping-token") + public String pingWithToken() { + return "pong"; + } +} diff --git a/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/controller/employer/EmolpyerOfferController.java b/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/controller/employer/EmolpyerOfferController.java new file mode 100644 index 0000000..98d62df --- /dev/null +++ b/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/controller/employer/EmolpyerOfferController.java @@ -0,0 +1,99 @@ +package net.lensfrex.disillusion.web.controller.employer; + +import net.lensfrex.disillusion.annotation.CheckAuth; +import net.lensfrex.disillusion.model.OfferApplication; +import net.lensfrex.disillusion.model.User; +import net.lensfrex.disillusion.model.employer.Offer; +import net.lensfrex.disillusion.response.general.Response; +import net.lensfrex.disillusion.response.general.ResponseCode; +import net.lensfrex.disillusion.web.request.OfferPublishRequest; +import net.lensfrex.disillusion.web.service.employer.OfferService; +import net.lensfrex.disillusion.web.service.student.ApplicationService; +import net.lensfrex.disillusion.web.service.user.UserService; +import net.lensfrex.disillusion.util.TokenUtil; +import org.springframework.web.bind.annotation.*; + +import java.io.IOException; +import java.util.Arrays; + +@RestController +@RequestMapping("/employer/offer") +public class EmolpyerOfferController { + + private final UserService userService; + + private final OfferService offerService; + + private final ApplicationService applicationService; + + public EmolpyerOfferController(UserService userService, + OfferService offerService, + ApplicationService applicationService) { + + this.userService = userService; + this.offerService = offerService; + this.applicationService = applicationService; + } + + @CheckAuth + @PostMapping("/publish") + public Response publish(@RequestBody OfferPublishRequest offerRequest, @RequestHeader("token") String token) + throws IOException, ClassNotFoundException { + + String username = TokenUtil.getUsernameFromToken(token); + User user = userService.getAccount(username); + if (user.getUserType() != User.UserType.EMPLOYER) { + return Response.error(ResponseCode.PERMISSION_DENIED); + } + + offerService.addOffer(username, offerRequest); + return Response.success(); + } + + @CheckAuth + @PostMapping("/delete") + public Response delete(@RequestHeader("token") String token, @RequestParam("id") String id) + throws IOException, ClassNotFoundException { + + String username = TokenUtil.getUsernameFromToken(token); + if (!check(username, id)) { + return Response.error(ResponseCode.PERMISSION_DENIED); + } + + offerService.deleteOffer(username, id); + + return Response.success(); + } + + @CheckAuth + @GetMapping("/view") + public Response getAllOffer(@RequestHeader("token") String token) + throws IOException, ClassNotFoundException { + + String username = TokenUtil.getUsernameFromToken(token); + Offer[] offers = offerService.getAllOfferByUser(username); + + return Response.success(offers); + } + + @CheckAuth + @PostMapping("/update") + public Response update(@RequestHeader("token") String token, @RequestParam("id") String id, @RequestBody OfferPublishRequest offerRequest) + throws IOException, ClassNotFoundException { + + String user = TokenUtil.getUsernameFromToken(token); + if (!check(user, id)) { + return Response.error(ResponseCode.PERMISSION_DENIED); + } + + offerService.updateOffer(user, id, offerRequest); + + return Response.success(); + } + + private boolean check(String user, String id) throws IOException, ClassNotFoundException { + Offer offer = offerService.getOffer(id); + + return offer != null && offer.getPublisherId().equals(user); + } +} diff --git a/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/controller/employer/EmployerOfferApplicationController.java b/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/controller/employer/EmployerOfferApplicationController.java new file mode 100644 index 0000000..f47f8c3 --- /dev/null +++ b/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/controller/employer/EmployerOfferApplicationController.java @@ -0,0 +1,75 @@ +package net.lensfrex.disillusion.web.controller.employer; + +import net.lensfrex.disillusion.annotation.CheckAuth; +import net.lensfrex.disillusion.model.OfferApplication; +import net.lensfrex.disillusion.model.User; +import net.lensfrex.disillusion.response.general.Response; +import net.lensfrex.disillusion.response.general.ResponseCode; +import net.lensfrex.disillusion.util.TokenUtil; +import net.lensfrex.disillusion.web.service.student.ApplicationService; +import net.lensfrex.disillusion.web.service.user.UserService; +import org.springframework.beans.BeanUtils; +import org.springframework.web.bind.annotation.*; + +import java.io.IOException; +import java.util.Arrays; + +@RestController +@RequestMapping("/employer/application") +public class EmployerOfferApplicationController { + private final UserService userService; + + private final ApplicationService applicationService; + + public EmployerOfferApplicationController(UserService userService, + ApplicationService applicationService) { + + this.userService = userService; + this.applicationService = applicationService; + } + + @CheckAuth + @PostMapping("/deal") + public Response deal(@RequestHeader("token") String token, @RequestParam("id") String id, @RequestParam("opinion") String opinion) throws IOException, ClassNotFoundException { + String username = TokenUtil.getUsernameFromToken(token); + User user = userService.getAccount(username); + if (user.getUserType() != User.UserType.EMPLOYER) { + return Response.error(ResponseCode.PERMISSION_DENIED); + } + + OfferApplication application = applicationService.getOfferApplication(id); + + if (!application.getTargetId().equals(username)) { + return Response.error(ResponseCode.PERMISSION_DENIED); + } + + OfferApplication.Status status = switch (opinion) { + case "accept" -> OfferApplication.Status.ACCEPTED; + case "reject" -> OfferApplication.Status.REJECTED; + default -> throw new IllegalStateException(); + }; + + applicationService.changeApplicationStatus(id, status); + + return Response.success(); + } + + @CheckAuth + @GetMapping("/view") + public Response view(@RequestHeader("token") String token, @RequestParam("offer_id") String offerId) + throws IOException, ClassNotFoundException { + + OfferApplication[] applications = applicationService.getAllApplicationByOfferId(offerId); + OfferApplication[] result = new OfferApplication[applications.length]; + int count = 0; + for (OfferApplication application : applications) { + if (application != null && application.getStatus() != OfferApplication.Status.ACCEPTED) { + result[count] = new OfferApplication(); + BeanUtils.copyProperties(application, result[count]); + count++; + } + } + + return Response.success(Arrays.copyOf(result, count)); + } +} diff --git a/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/controller/student/StudentOfferApplicationController.java b/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/controller/student/StudentOfferApplicationController.java new file mode 100644 index 0000000..2f66e21 --- /dev/null +++ b/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/controller/student/StudentOfferApplicationController.java @@ -0,0 +1,109 @@ +package net.lensfrex.disillusion.web.controller.student; +import cn.hutool.core.util.RandomUtil; +import net.lensfrex.disillusion.model.OfferApplication.Status; + +import net.lensfrex.disillusion.annotation.CheckAuth; +import net.lensfrex.disillusion.model.OfferApplication; +import net.lensfrex.disillusion.response.general.Response; +import net.lensfrex.disillusion.response.general.ResponseCode; +import net.lensfrex.disillusion.util.TokenUtil; +import net.lensfrex.disillusion.web.request.OfferApplicationRequest; +import net.lensfrex.disillusion.web.response.ApplicationResponse; +import net.lensfrex.disillusion.web.service.student.ApplicationService; +import net.lensfrex.disillusion.web.service.user.UserService; +import org.springframework.beans.BeanUtils; +import org.springframework.web.bind.annotation.*; + +import java.io.IOException; + +@RestController +@RequestMapping("/student/application") +public class StudentOfferApplicationController { + private final UserService userService; + + private final ApplicationService applicationService; + + public StudentOfferApplicationController(UserService userService, + ApplicationService applicationService) { + + this.userService = userService; + this.applicationService = applicationService; + } + + private boolean check(String user, String id) throws IOException, ClassNotFoundException { + OfferApplication offerApplication = applicationService.getOfferApplication(id); + + return offerApplication != null && offerApplication.getPublisherId().equals(user); + } + + @CheckAuth + @PostMapping("/publish") + public Response publish(@RequestBody OfferApplicationRequest offerRequest, @RequestHeader String token) + throws IOException, ClassNotFoundException { + + String username = TokenUtil.getUsernameFromToken(token); + applicationService.addApplication(username, offerRequest); + + return Response.success(); + } + + @CheckAuth + @PostMapping("/delete") + public Response delete(@RequestHeader String token, @RequestParam("id") String id) + throws IOException, ClassNotFoundException { + + String user = TokenUtil.getUsernameFromToken(token); + if (!check(user, id)) { + return Response.error(ResponseCode.PERMISSION_DENIED); + } + + applicationService.deleteOfferApplication(user, id); + + return Response.success(); + } + + @CheckAuth + @GetMapping("/view") + public Response view(@RequestHeader("token") String token) throws IOException, ClassNotFoundException { + String username = TokenUtil.getUsernameFromToken(token); + + OfferApplication[] applications = applicationService.getAllApplicationByUser(username); + ApplicationResponse[] response = new ApplicationResponse[applications.length]; + int i = 0; + for (OfferApplication application : applications) { + if (application == null) { + continue; + } + response[i] = new ApplicationResponse(); + BeanUtils.copyProperties(application, response[i]); + i++; + } + + return Response.success(response); + } + + private static final String samples = """ + 8.微专业课程选课要求: + + 前期经过自愿报名并审核成功的学生,根据所报微专业的培养方案,自行选择相应课程。 + + 9.认真对照培养方案,选课时务必仔细核对课程信息,确保“课程代码”、“课程名称”、“学分”这三项重要信息与培养方案要求相符。 + + 10.禁止同一时间修读两门及以上课程,系统会提示时间冲突并禁止选课。提前了解专业教学进程(实习、实践、课程设计等)情况,不能选择与本专业其它教学任务周次有冲突的课堂。 + + 11.因培养方案变动、转专业等原因,计划修读的课程确定不再开课的,请先查询本科生课程学分认定对照表并咨询开课学院,再进行选课。待课程修读完获得学分后,再按照本科生院发布的相关要求办理学分认定。 + + 12.学生选课、听课、考核必须一致。学生不能参加未选择课程的学习和考核,即使参加了学习和考核,如果没有选课,也是无效的,将不记录成绩。 + + 13.学生凭学号、密码选课,认真对待,并对自己的选课行为负责。密码必须妥善保管,不得代替他人选课,不得借用、盗用他人学号及密码选课。 + + 14.学生登录本科教学综合管理系统需通过学校统一身份认证平台登录,登录方式、密码重置方式详见以下两个通知:https://jwc.wust.edu.cn/2022/0226/c1925a255654/page.htm、https://jwc.wust.edu.cn/2022/0302/c1925a255786/page.htm。 + + 对账号、密码有疑问的,请按照登录页面上“常见问题”、“忘记密码”等提示进行操作。 + + 15.本次选课为2022-2023学年第二学期课程的最后一次选课,请同学们严格按照时间安排进行退改选,尤其要在规定的时间内查看课表,以免出现课堂因人数不足取消而本人又没有及时参加补选,再次造成某些课程漏选的情况。无故不参加选课或错过选课机会,后果自负。\s + + 本科生院教务管理办公室 + + 二〇二三年二月二十三日"""; +} diff --git a/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/controller/student/StudentResumeController.java b/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/controller/student/StudentResumeController.java new file mode 100644 index 0000000..57f6085 --- /dev/null +++ b/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/controller/student/StudentResumeController.java @@ -0,0 +1,101 @@ +package net.lensfrex.disillusion.web.controller.student; + +import net.lensfrex.disillusion.annotation.CheckAuth; +import net.lensfrex.disillusion.model.User; +import net.lensfrex.disillusion.model.student.Resume; +import net.lensfrex.disillusion.response.general.Response; +import net.lensfrex.disillusion.response.general.ResponseCode; +import net.lensfrex.disillusion.util.TokenUtil; +import net.lensfrex.disillusion.web.request.ResumeRequest; +import net.lensfrex.disillusion.web.service.student.ResumeService; +import net.lensfrex.disillusion.web.service.user.UserService; +import org.springframework.web.bind.annotation.*; + +import java.io.IOException; + +@RestController +@RequestMapping("/student/resume") +public class StudentResumeController { + private final ResumeService resumeService; + private final UserService userService; + + public StudentResumeController(ResumeService resumeService, UserService userService) { + this.resumeService = resumeService; + this.userService = userService; + } + + @CheckAuth + @PostMapping(value = "/add") + public Response add(@RequestBody ResumeRequest request, @RequestHeader("token") String token) + throws IOException, ClassNotFoundException { + + String user = TokenUtil.getUsernameFromToken(token); + resumeService.addResume(user, request); + + return Response.success(); + } + + @CheckAuth + @PostMapping(value = "/update") + public Response update(@RequestBody ResumeRequest resumeRequest, + @RequestParam("id") String id, @RequestHeader("token") String token) + throws IOException, ClassNotFoundException { + + String user = TokenUtil.getUsernameFromToken(token); + if (!check(user, id)) { + return Response.error(ResponseCode.PERMISSION_DENIED); + } + resumeService.updateResume(user, id, resumeRequest); + + return Response.success(); + } + + @CheckAuth + @PostMapping("/delete") + public Response deleteResume(@RequestParam("id") String id, @RequestHeader("token") String token) + throws IOException, ClassNotFoundException { + + String user = TokenUtil.getUsernameFromToken(token); + if (!check(user, id)) { + return Response.error(ResponseCode.PERMISSION_DENIED); + } + + resumeService.deleteResume(user, id); + + return Response.success(); + } + + @CheckAuth + @GetMapping("/all") + public Response getAllResume(@RequestHeader("token") String token) + throws IOException, ClassNotFoundException { + + String username = TokenUtil.getUsernameFromToken(token); + Resume[] result = resumeService.getAllResume(username); + + return Response.success(result); + } + + @CheckAuth + @GetMapping("/getFile") + public Response getFile(@RequestParam("id") String id, @RequestHeader("token") String token) + throws IOException, ClassNotFoundException { + + String username = TokenUtil.getUsernameFromToken(token); + if (!check(username, id)) { + User user = userService.getAccount(username); + if (user == null || user.getUserType() != User.UserType.EMPLOYER) { + return Response.error(ResponseCode.PERMISSION_DENIED); + } + } + + String fileData = resumeService.getResumeFileData(id); + + return Response.success(fileData); + } + + private boolean check(String user, String id) throws IOException, ClassNotFoundException { + Resume resume = resumeService.getResume(id); + return resume != null && resume.getOwnerId().equals(user); + } +} diff --git a/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/controller/user/UserController.java b/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/controller/user/UserController.java new file mode 100644 index 0000000..043284a --- /dev/null +++ b/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/controller/user/UserController.java @@ -0,0 +1,127 @@ +package net.lensfrex.disillusion.web.controller.user; + +import lombok.extern.slf4j.Slf4j; +import net.lensfrex.disillusion.annotation.CheckAuth; +import net.lensfrex.disillusion.model.User; +import net.lensfrex.disillusion.model.UserInfo; +import net.lensfrex.disillusion.model.employer.Employer; +import net.lensfrex.disillusion.model.student.Student; +import net.lensfrex.disillusion.response.general.Response; +import net.lensfrex.disillusion.response.general.ResponseCode; +import net.lensfrex.disillusion.util.TokenUtil; +import net.lensfrex.disillusion.web.service.user.UserInfoService; +import net.lensfrex.disillusion.web.service.user.UserService; +import org.mindrot.jbcrypt.BCrypt; +import org.springframework.web.bind.annotation.*; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.HashMap; +import java.util.Map; + +@Slf4j +@RestController +@RequestMapping("/user") +public class UserController { + private final UserService userService; + private final UserInfoService userInfoService; + + public UserController(UserService userService, + UserInfoService userInfoService) { + + this.userService = userService; + this.userInfoService = userInfoService; + } + + @PostMapping(value = "/login", produces = "application/json;charset=utf-8") + public Response> login(@RequestParam("username") String username, + @RequestParam("password") String password) + throws IOException, ClassNotFoundException { + + User user = userService.getAccount(username); + if (user == null || !BCrypt.checkpw(password, user.getPassword())) { + return Response.error(ResponseCode.IDENTIFY_ERROR); + } + + String token = TokenUtil.signNewToken(username); + + Map responseData = new HashMap<>(2); + responseData.put("token", token); + responseData.put("user_type", user.getUserType().code + ""); + + return Response.success(responseData); + } + + @CheckAuth + @PostMapping(value = "/refresh", produces = "application/json;charset=utf-8") + public Response refreshToken(@RequestHeader("token") String token) throws IOException, ClassNotFoundException { + + String username = TokenUtil.getUsernameFromToken(token); + User user = userService.getAccount(username); + if (user == null) { + return Response.error(ResponseCode.USER_NOT_EXISTS); + } + + String newToken = TokenUtil.signNewToken(username); + + return Response.success(newToken); + } + + @PostMapping(value = "/register", produces = "application/json;charset=utf-8") + public Response register(@RequestParam("username") String username, + @RequestParam("password") String password, + @RequestParam("userType") String userType) + throws IOException, ClassNotFoundException { + + // 检查用户是否已经存在 + User user = userService.getAccount(username); + if (user != null) { + return Response.error(ResponseCode.USER_ALREADY_EXISTS); + } + + // 注册用户类型 + User.UserType enumUserType = getUserType(userType); + if (enumUserType == null) { + return Response.error(ResponseCode.PARAM_WRONG); + } + + // 注册用户 + userService.registerNewUser(username, password, enumUserType); + + // 生成默认的用户信息 + userInfoService.saveUserInfoById(getDefaultUserInfo(username, enumUserType)); + + return Response.success(); + } + + private User.UserType getUserType(String value) { + return switch (value) { + case "0" -> User.UserType.STUDENT; + case "1" -> User.UserType.EMPLOYER; + default -> null; + }; + } + + private UserInfo getDefaultUserInfo(String username, User.UserType userType) { + try { + File avatarBaseDir = new File(UserInfoController.AVATAR_BASE_DIR); + if (!avatarBaseDir.exists()) { + avatarBaseDir.mkdirs(); + } + + Files.write(Paths.get(UserInfoController.AVATAR_BASE_DIR, username), Student.DEFAULT_AVATAR, + StandardOpenOption.CREATE, StandardOpenOption.WRITE); + + } catch (IOException e) { + log.error("写入默认头像时发生异常", e); + } + + return switch (userType) { + case STUDENT -> Student.getDefaultInfo(username); + case EMPLOYER -> Employer.getDefaultInfo(username); + }; + } +} diff --git a/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/controller/user/UserInfoController.java b/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/controller/user/UserInfoController.java new file mode 100644 index 0000000..f3430cf --- /dev/null +++ b/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/controller/user/UserInfoController.java @@ -0,0 +1,168 @@ +package net.lensfrex.disillusion.web.controller.user; + +import cn.hutool.core.codec.Base64; +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.slf4j.Slf4j; +import net.lensfrex.disillusion.annotation.CheckAuth; +import net.lensfrex.disillusion.model.User; +import net.lensfrex.disillusion.model.UserInfo; +import net.lensfrex.disillusion.web.response.user.PublicEmployerInfoResponse; +import net.lensfrex.disillusion.web.response.user.PublicStudentInfoResponse; +import net.lensfrex.disillusion.web.response.user.UserInfoResponseBase; +import net.lensfrex.disillusion.model.employer.Employer; +import net.lensfrex.disillusion.model.student.Student; +import net.lensfrex.disillusion.response.general.Response; +import net.lensfrex.disillusion.response.general.ResponseCode; +import net.lensfrex.disillusion.util.IOUtil; +import net.lensfrex.disillusion.web.service.user.UserInfoService; +import net.lensfrex.disillusion.web.service.user.UserService; +import net.lensfrex.disillusion.util.TokenUtil; +import org.springframework.beans.BeanUtils; +import org.springframework.web.bind.annotation.*; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; + +@Slf4j +@RestController +@RequestMapping("/user/info") +public class UserInfoController { + public static final String AVATAR_BASE_DIR = "database/files/avatar"; + + private static final ObjectMapper objectMapper = new ObjectMapper(); + + private final UserService userService; + private final UserInfoService userInfoService; + + public UserInfoController(UserService userService, + UserInfoService userInfoService) { + + this.userService = userService; + this.userInfoService = userInfoService; + } + + // 获取自己的用户信息 + @CheckAuth + @GetMapping(value = "/view", produces = "application/json;charset=utf-8") + public Response getUserInfo(@RequestHeader("token") String token) + throws IOException, ClassNotFoundException { + + String username = TokenUtil.getUsernameFromToken(token); + User user = userService.getAccount(username); + if (user == null) { + return Response.error(ResponseCode.USER_NOT_EXISTS); + } + + UserInfo info = userInfoService.getUserInfoById(user.getId()); + return Response.success(info); + } + + @GetMapping(value = "/view/public", produces = "application/json") + public Response getUserInfoPublic(@RequestParam("user") String username) + throws IOException, ClassNotFoundException { + + User user = userService.getAccount(username); + if (user == null) { + return Response.error(ResponseCode.USER_NOT_EXISTS); + } + + UserInfo info = userInfoService.getUserInfoById(username); + + return switch (user.getUserType()) { + case STUDENT -> { + PublicStudentInfoResponse response = new PublicStudentInfoResponse(); + BeanUtils.copyProperties(info, response); + + yield Response.success(response); + } + case EMPLOYER -> { + PublicEmployerInfoResponse response = new PublicEmployerInfoResponse(); + BeanUtils.copyProperties(info, response); + + yield Response.success(response); + } + }; + } + + @CheckAuth + @PostMapping(value = "/edit", produces = "application/json;charset=utf-8") + public Response editUserInfo(@RequestHeader("token") String token, @RequestBody String infoJson) + throws IOException, ClassNotFoundException { + + String username = TokenUtil.getUsernameFromToken(token); + User user = userService.getAccount(username); + if (user == null) { + return Response.error(ResponseCode.USER_NOT_EXISTS); + } + + try { + UserInfo userInfo = switch (user.getUserType()) { + case STUDENT -> objectMapper.readValue(infoJson, Student.class); + case EMPLOYER -> objectMapper.readValue(infoJson, Employer.class); + }; + + userInfo.setId(user.getId()); + userInfoService.saveUserInfoById(userInfo); + + return Response.success(); + + } catch (JsonMappingException | JsonParseException e) { + return Response.error(ResponseCode.PARAM_WRONG); + + } catch (Exception e) { + return Response.error(ResponseCode.SERVER_INTERNAL_ERROR); + } + } + + @GetMapping(value = "/avatar", produces = "image/png") + public byte[] getAvatarImage(@RequestParam(value = "user", required = false) String user, + @RequestHeader(value = "token", required = false) String token) + throws IOException, ClassNotFoundException { + if (user == null && token == null) { + return Response.error(ResponseCode.PARAM_WRONG).toBytes(); + } + + if (user == null) { + String username = TokenUtil.getUsernameFromToken(token); + User userData = userService.getAccount(username); + if (userData == null) { + return Response.error(ResponseCode.USER_NOT_EXISTS).toBytes(); + } + + return Files.readAllBytes(Paths.get(AVATAR_BASE_DIR, username)); + } else { + return Files.readAllBytes(Paths.get(AVATAR_BASE_DIR, user)); + } + } + + @CheckAuth + @PostMapping(value = "/avatar/upload", produces = "application/json") + public Response uploadAvatar(@RequestHeader(value = "token") String token, @RequestBody String avatarBase64) + throws IOException, ClassNotFoundException { + + String username = TokenUtil.getUsernameFromToken(token); + User user = userService.getAccount(username); + if (user == null) { + return Response.error(ResponseCode.USER_NOT_EXISTS); + } + + try { + File avatarBaseDir = new File(UserInfoController.AVATAR_BASE_DIR); + if (!avatarBaseDir.exists()) { + avatarBaseDir.mkdirs(); + } + + Files.write(Paths.get(UserInfoController.AVATAR_BASE_DIR, username), Base64.decode(avatarBase64), + StandardOpenOption.CREATE, StandardOpenOption.WRITE); + } catch (Exception e) { + return Response.error(ResponseCode.SERVER_INTERNAL_ERROR); + } + + return Response.success(); + } +} diff --git a/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/interceptor/TokenInterceptor.java b/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/interceptor/TokenInterceptor.java new file mode 100644 index 0000000..fc5d314 --- /dev/null +++ b/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/interceptor/TokenInterceptor.java @@ -0,0 +1,52 @@ +package net.lensfrex.disillusion.web.interceptor; + +import com.fasterxml.jackson.databind.ObjectMapper; +import net.lensfrex.disillusion.annotation.CheckAuth; +import net.lensfrex.disillusion.response.general.Response; +import net.lensfrex.disillusion.response.general.ResponseCode; +import net.lensfrex.disillusion.util.TokenUtil; +import org.springframework.stereotype.Service; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.HandlerInterceptor; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.PrintWriter; + +/** + * token验证拦截器 + */ +@Service +public class TokenInterceptor implements HandlerInterceptor { + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + if (!needCheckAuth(handler)) { + return true; + } + + String token = request.getHeader("token"); + if (token == null || !TokenUtil.verify(token)) { + response.setCharacterEncoding("utf-8"); + response.setContentType("application/json;charset=utf-8"); + PrintWriter writer = response.getWriter(); + + String result = new ObjectMapper().writeValueAsString( + Response.error(ResponseCode.TOKEN_INVALID)); + writer.print(result); + writer.close(); + + return false; + } + + return true; + } + + private boolean needCheckAuth(Object handler) { + if (handler instanceof HandlerMethod handlerMethod) { + CheckAuth annotation = handlerMethod.getMethod().getAnnotation(CheckAuth.class); + return annotation != null; + } + + return false; + } +} \ No newline at end of file diff --git a/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/request/OfferApplicationRequest.java b/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/request/OfferApplicationRequest.java new file mode 100644 index 0000000..59f0906 --- /dev/null +++ b/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/request/OfferApplicationRequest.java @@ -0,0 +1,32 @@ +package net.lensfrex.disillusion.web.request; + +import lombok.Data; + +@Data +public class OfferApplicationRequest { + /** + * 申请的offerId + */ + private String offerId; + + /** + * 简历id + */ + private String resumeId; + + public String offerId() { + return offerId; + } + + public void setOfferId(String offerId) { + this.offerId = offerId; + } + + public String resumeId() { + return resumeId; + } + + public void setResumeId(String resumeId) { + this.resumeId = resumeId; + } +} diff --git a/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/request/OfferPublishRequest.java b/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/request/OfferPublishRequest.java new file mode 100644 index 0000000..d15a185 --- /dev/null +++ b/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/request/OfferPublishRequest.java @@ -0,0 +1,36 @@ +package net.lensfrex.disillusion.web.request; + +import lombok.Data; + +@Data +public class OfferPublishRequest { + /** + * 岗位名称 + */ + private String jobName; + + /** + * 工作地点 + */ + private String place; + + /** + * 要求 + */ + private String requirement; + + /** + * 预期薪资 + */ + private Integer salary; + + /** + * 目标人数 + */ + private Integer targetNumber; + + /** + * 详细信息 + */ + private String detail; +} diff --git a/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/request/ResumeRequest.java b/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/request/ResumeRequest.java new file mode 100644 index 0000000..2811cd9 --- /dev/null +++ b/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/request/ResumeRequest.java @@ -0,0 +1,27 @@ +package net.lensfrex.disillusion.web.request; + +import lombok.Data; +import net.lensfrex.disillusion.model.StoreData; + +import java.io.Serializable; + +/** + * 简历 + */ +@Data +public class ResumeRequest { + /** + * 简历标题(名称) + */ + private String title; + + /** + * 简历备注信息 + */ + private String remark; + + /** + * 简历文件数据,base64 + */ + private String resumeFileBase64; +} diff --git a/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/response/ApplicationResponse.java b/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/response/ApplicationResponse.java new file mode 100644 index 0000000..eaefab1 --- /dev/null +++ b/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/response/ApplicationResponse.java @@ -0,0 +1,37 @@ +package net.lensfrex.disillusion.web.response; + +import lombok.Data; +import net.lensfrex.disillusion.model.OfferApplication; + +/** + * 求职申请 + */ +@Data +public class ApplicationResponse { + private String id; + + /** + * 发布者id + */ + private String publisherId; + + /** + * 目标公司id + */ + private String targetId; + + /** + * 申请的offerId + */ + private String offerId; + + /** + * 简历id + */ + private String resumeId; + + /** + * 申请状态 + */ + private OfferApplication.Status status; +} \ No newline at end of file diff --git a/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/response/EmployerApplicationResponse.java b/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/response/EmployerApplicationResponse.java new file mode 100644 index 0000000..c196b28 --- /dev/null +++ b/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/response/EmployerApplicationResponse.java @@ -0,0 +1,28 @@ +package net.lensfrex.disillusion.web.response; + +import lombok.Data; +import net.lensfrex.disillusion.model.OfferApplication; + +/** + * 求职申请 + */ +@Data +public class EmployerApplicationResponse { + private String id; + + /** + * 发布者id + */ + private String publisherId; + private String publisherName; + + /** + * 简历id + */ + private String resumeId; + + /** + * 申请状态 + */ + private OfferApplication.Status status; +} \ No newline at end of file diff --git a/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/response/OfferListResponse.java b/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/response/OfferListResponse.java new file mode 100644 index 0000000..1c6cfa9 --- /dev/null +++ b/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/response/OfferListResponse.java @@ -0,0 +1,51 @@ +package net.lensfrex.disillusion.web.response; + +import lombok.Data; + +@Data +public class OfferListResponse { + private int total; + private int page; + private int size; + private OfferResponse[] result; + + @Data + public static class OfferResponse { + private String id; + + /** + * 发布公司 + */ + private String publisherId; + + /** + * 岗位名称 + */ + private String jobName; + + /** + * 工作地点 + */ + private String place; + + /** + * 要求 + */ + private String requirement; + + /** + * 预期薪资 + */ + private Integer salary; + + /** + * 目标人数 + */ + private Integer targetNumber; + + /** + * 详细信息 + */ + private String detail; + } +} diff --git a/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/response/user/PublicEmployerInfoResponse.java b/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/response/user/PublicEmployerInfoResponse.java new file mode 100644 index 0000000..3944978 --- /dev/null +++ b/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/response/user/PublicEmployerInfoResponse.java @@ -0,0 +1,40 @@ +package net.lensfrex.disillusion.web.response.user; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = false) +public class PublicEmployerInfoResponse extends UserInfoResponseBase { + private String id; + + /** + * 公司名称 + */ + private String name; + + /** + * 总部地址 + */ + private String place; + + /** + * 联系方式 + */ + private String contract; + + /** + * 公司简要介绍 + */ + private String shortDescribe; + + /** + * 公司介绍 + */ + private String describe; + + /** + * 所属行业 + */ + private String industry; +} diff --git a/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/response/user/PublicStudentInfoResponse.java b/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/response/user/PublicStudentInfoResponse.java new file mode 100644 index 0000000..d86f759 --- /dev/null +++ b/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/response/user/PublicStudentInfoResponse.java @@ -0,0 +1,40 @@ +package net.lensfrex.disillusion.web.response.user; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = false) +public class PublicStudentInfoResponse extends UserInfoResponseBase { + private String id; + + /** + * 真名 + */ + private String realName; + + /** + * 简介 + */ + private String shortDescribe; + + /** + * 介绍 + */ + private String describe; + + /** + * 电话号 + */ + private String phoneNumber; + + /** + * 年龄 + */ + private int age; + + /** + * 性别,0:男;1:女 + */ + private int sex; +} diff --git a/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/response/user/UserInfoResponseBase.java b/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/response/user/UserInfoResponseBase.java new file mode 100644 index 0000000..2881041 --- /dev/null +++ b/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/response/user/UserInfoResponseBase.java @@ -0,0 +1,4 @@ +package net.lensfrex.disillusion.web.response.user; + +public class UserInfoResponseBase { +} \ No newline at end of file diff --git a/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/service/employer/OfferService.java b/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/service/employer/OfferService.java new file mode 100644 index 0000000..a830bfd --- /dev/null +++ b/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/service/employer/OfferService.java @@ -0,0 +1,191 @@ +package net.lensfrex.disillusion.web.service.employer; + +import cn.hutool.core.lang.Validator; +import net.lensfrex.disillusion.dao.OfferApplicationTable; +import net.lensfrex.disillusion.dao.OfferTable; +import net.lensfrex.disillusion.dao.index.EmployerOfferIndex; +import net.lensfrex.disillusion.dao.index.application.OfferApplicationIndex; +import net.lensfrex.disillusion.data.ArrayList; +import net.lensfrex.disillusion.data.List; +import net.lensfrex.disillusion.model.MultiIndex; +import net.lensfrex.disillusion.model.employer.Employer; +import net.lensfrex.disillusion.model.employer.Offer; +import net.lensfrex.disillusion.web.request.OfferPublishRequest; +import net.lensfrex.disillusion.web.service.user.UserInfoService; +import org.apache.commons.lang.RandomStringUtils; +import org.springframework.beans.BeanUtils; +import org.springframework.stereotype.Service; + +import java.io.IOException; +import java.util.Arrays; + +@Service +public class OfferService { + private final EmployerOfferIndex employerOfferIndex; + private final OfferTable offerTable; + private final UserInfoService userInfoService; + + private final OfferApplicationIndex offerApplicationIndex; + private final OfferApplicationTable offerApplicationTable; + + public OfferService(EmployerOfferIndex EmployerOfferIndex, + OfferTable offerTable, UserInfoService userInfoService, + OfferApplicationIndex offerApplicationIndex, + OfferApplicationTable offerApplicationTable) { + + this.employerOfferIndex = EmployerOfferIndex; + this.offerTable = offerTable; + this.userInfoService = userInfoService; + this.offerApplicationIndex = offerApplicationIndex; + this.offerApplicationTable = offerApplicationTable; + } + + public void addOffer(String user, OfferPublishRequest request) throws IOException, ClassNotFoundException { + Offer offer = packageOffer(user, RandomStringUtils.randomAlphanumeric(8), request); + + MultiIndex multiIndex = employerOfferIndex.select(user); + if (multiIndex == null) { + multiIndex = new MultiIndex(user); + multiIndex.addValue(offer.getId()); + } else { + multiIndex.addValue(offer.getId()); + } + + offerTable.insertOrUpdate(offer); + employerOfferIndex.insertOrUpdate(multiIndex); + } + + public void updateOffer(String user, String offerId, OfferPublishRequest request) throws IOException, ClassNotFoundException { + MultiIndex multiIndex = employerOfferIndex.select(user); + if (multiIndex == null || !multiIndex.hasValue(offerId)) { + return; + } + + offerTable.insertOrUpdate(packageOffer(user, offerId, request)); + } + + private Offer packageOffer(String user, String id, OfferPublishRequest request) { + Offer offer = new Offer(); + offer.setPublisherId(user); + offer.setId(id); + + BeanUtils.copyProperties(request, offer); + return offer; + } + + public Offer[] getAllOfferByUser(String user) throws IOException, ClassNotFoundException { + MultiIndex index = employerOfferIndex.select(user); + if (index == null) { + return new Offer[0]; + } + + String[] ids = index.getAllValues(); + List offers = new ArrayList<>(ids.length); + for (String id : ids) { + if (id == null) { + continue; + } + + offers.add(offerTable.select(id)); + } + + return offers.toArray(new Offer[offers.size()]); + } + + public Offer[] searchOfferByUser(String keyWord) throws IOException, ClassNotFoundException { + List offers = new ArrayList<>(32); + List employers = userInfoService.getAllEmployerUserInfo(); + + for (int i = 0; i < employers.size(); i++) { + Employer employer = employers.get(i); + if (employer.getName().matches(".*?" + keyWord + ".*")) { + MultiIndex index = employerOfferIndex.select(employer.getId()); + if (index == null) { + return new Offer[0]; + } + + String[] ids = index.getAllValues(); + for (String id : ids) { + offers.add(offerTable.select(id)); + } + } + } + + return offers.toArray(new Offer[offers.size()]); + } + + public Offer[] getAllOfferBySalary(String minSalaryText) throws IOException, ClassNotFoundException { + if (!Validator.isNumber(minSalaryText)) { + return new Offer[0]; + } + + int minSalary = Integer.parseInt(minSalaryText); + List result = offerTable.getAll(); + List filterResult = new ArrayList<>(result.size()); + + for (int i = 0; i < result.size(); i++) { + Offer offer = result.get(i); + if (offer.getSalary() > minSalary) { + filterResult.add(offer); + } + } + + return filterResult.toArray(new Offer[0]); + } + + public Offer[] searchOfferByName(String keyWord) throws IOException, ClassNotFoundException { + List result = offerTable.getAll(); + List filterResult = new ArrayList<>(result.size()); + + for (int i = 0; i < result.size(); i++) { + Offer offer = result.get(i); + if (offer.getJobName().matches(".*?" + keyWord + ".*")) { + filterResult.add(offer); + } + } + + return filterResult.toArray(new Offer[0]); + } + + public void deleteOffer(String user, String id) throws IOException, ClassNotFoundException { + MultiIndex index = employerOfferIndex.select(user); + if (index == null || !index.hasValue(id)) { + return; + } + + index.removeValue(id); + employerOfferIndex.insertOrUpdate(index); + + MultiIndex offerApplication = offerApplicationIndex.select(id); + if (offerApplication == null) { + return; + } + + String[] applications = offerApplication.getAllValues(); + for (String application : applications) { + offerApplicationTable.delete(application); + } + + offerApplicationIndex.insertOrUpdate(offerApplication); + offerTable.delete(id); + } + + public Offer getOffer(String id) throws IOException, ClassNotFoundException { + return offerTable.select(id); + } + + public Offer[] getOffer(int page, int size) throws IOException, ClassNotFoundException { + List offerList = offerTable.getAll(); + Offer[] allOffer = new Offer[size]; + offerList.toArray(allOffer); + + return Arrays.copyOfRange(allOffer, page * size, size); + } + + public Offer[] getAllOffer() throws IOException, ClassNotFoundException { + List offerList = offerTable.getAll(); + Offer[] allOffer = new Offer[offerList.size()]; + + return offerList.toArray(allOffer); + } +} diff --git a/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/service/student/ApplicationService.java b/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/service/student/ApplicationService.java new file mode 100644 index 0000000..2e875da --- /dev/null +++ b/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/service/student/ApplicationService.java @@ -0,0 +1,169 @@ +package net.lensfrex.disillusion.web.service.student; + +import net.lensfrex.disillusion.dao.DaoBase; +import net.lensfrex.disillusion.dao.OfferApplicationTable; +import net.lensfrex.disillusion.dao.OfferTable; +import net.lensfrex.disillusion.dao.index.application.EmployerApplicationIndex; +import net.lensfrex.disillusion.dao.index.application.OfferApplicationIndex; +import net.lensfrex.disillusion.dao.index.application.StudentApplicationIndex; +import net.lensfrex.disillusion.data.ArrayList; +import net.lensfrex.disillusion.data.List; +import net.lensfrex.disillusion.model.OfferApplication; +import net.lensfrex.disillusion.model.MultiIndex; +import net.lensfrex.disillusion.model.User; +import net.lensfrex.disillusion.model.employer.Offer; +import net.lensfrex.disillusion.web.request.OfferApplicationRequest; +import net.lensfrex.disillusion.web.service.user.UserService; +import org.apache.commons.lang.RandomStringUtils; +import org.springframework.stereotype.Service; + +import java.io.IOException; + +@Service +public class ApplicationService { + private final OfferTable offerTable; + + private final OfferApplicationIndex offerApplicationIndex; + private final StudentApplicationIndex studentApplicationIndex; + private final EmployerApplicationIndex employerApplicationIndex; + + private final OfferApplicationTable offerApplicationTable; + + private final UserService userService; + + public ApplicationService(OfferTable offerTable, + OfferApplicationIndex offerApplicationIndex, + StudentApplicationIndex studentApplicationIndex, + EmployerApplicationIndex employerApplicationIndex, + OfferApplicationTable offerApplicationTable, + UserService userService) { + + this.offerTable = offerTable; + + this.offerApplicationIndex = offerApplicationIndex; + this.studentApplicationIndex = studentApplicationIndex; + this.employerApplicationIndex = employerApplicationIndex; + this.offerApplicationTable = offerApplicationTable; + this.userService = userService; + } + + public void addApplication(String user, OfferApplicationRequest request) throws IOException, ClassNotFoundException { + Offer offer = offerTable.select(request.getOfferId()); + if (offer == null) { + return; + } + + String employer = offer.getPublisherId(); + User employerUser = userService.getAccount(employer); + if (employerUser == null) { + return; + } + + OfferApplication application = packageApplication(user, RandomStringUtils.randomAlphanumeric(8), employer, request); + + offerApplicationTable.insertOrUpdate(application); + + addRelationIndex(offerApplicationIndex, offer.getId(), application.getId()); + addRelationIndex(studentApplicationIndex, user, application.getId()); + addRelationIndex(employerApplicationIndex, employer, offer.getId()); + } + + private OfferApplication packageApplication(String user, String applicationId, String target, + OfferApplicationRequest request) { + + OfferApplication application = new OfferApplication(); + + application.setStatus(OfferApplication.Status.WAITING); + + application.setOfferId(request.getOfferId()); + application.setResumeId(request.getResumeId()); + + application.setPublisherId(user); + application.setId(applicationId); + application.setTargetId(target); + + return application; + } + + private void addRelationIndex(DaoBase index, String id, String value) + throws IOException, ClassNotFoundException { + + MultiIndex multiIndex = index.select(id); + if (multiIndex == null) { + multiIndex = new MultiIndex(id); + multiIndex.addValue(value); + } else { + multiIndex.addValue(value); + } + + index.insertOrUpdate(multiIndex); + } + + private void removeRelationIndex(DaoBase index, String id, String value) + throws IOException, ClassNotFoundException { + + MultiIndex multiIndex = index.select(id); + if (multiIndex == null || !multiIndex.hasValue(id)) { + return; + } + + multiIndex.removeValue(value); + index.insertOrUpdate(multiIndex); + } + + public OfferApplication[] getAllApplicationByUser(String user) throws IOException, ClassNotFoundException { + return getFromIndex(studentApplicationIndex, user); + } + + public OfferApplication[] getAllApplicationByTarget(String employer) throws IOException, ClassNotFoundException { + return getFromIndex(employerApplicationIndex, employer); + } + + public OfferApplication[] getAllApplicationByOfferId(String offerId) throws IOException, ClassNotFoundException { + return getFromIndex(offerApplicationIndex, offerId); + } + + private OfferApplication[] getFromIndex(DaoBase indexDao, String queryKey) + throws IOException, ClassNotFoundException { + + MultiIndex index = indexDao.select(queryKey); + if (index == null) { + return new OfferApplication[0]; + } + + String[] ids = index.getAllValues(); + List applications = new ArrayList<>(ids.length); + for (String id : ids) { + applications.add(offerApplicationTable.select(id)); + } + + return applications.toArray(new OfferApplication[applications.size()]); + } + + public void deleteOfferApplication(String user, String id) + throws IOException, ClassNotFoundException { + + removeRelationIndex(offerApplicationIndex, user, id); + removeRelationIndex(employerApplicationIndex, user, id); + removeRelationIndex(studentApplicationIndex, user, id); + + offerApplicationTable.delete(id); + } + + public OfferApplication getOfferApplication(String id) throws IOException, ClassNotFoundException { + return offerApplicationTable.select(id); + } + + public void changeApplicationStatus(String id, OfferApplication.Status status) + throws IOException, ClassNotFoundException { + + OfferApplication application = getOfferApplication(id); + if (application == null) { + return; + } + + application.setStatus(status); + + offerApplicationTable.insertOrUpdate(application); + } +} diff --git a/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/service/student/ResumeService.java b/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/service/student/ResumeService.java new file mode 100644 index 0000000..3e80749 --- /dev/null +++ b/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/service/student/ResumeService.java @@ -0,0 +1,131 @@ +package net.lensfrex.disillusion.web.service.student; + +import cn.hutool.core.codec.Base64; +import cn.hutool.core.io.FileUtil; +import net.lensfrex.disillusion.dao.ResumeTable; +import net.lensfrex.disillusion.dao.index.StudentResumeIndex; +import net.lensfrex.disillusion.data.ArrayList; +import net.lensfrex.disillusion.data.List; +import net.lensfrex.disillusion.model.MultiIndex; +import net.lensfrex.disillusion.model.student.Resume; +import net.lensfrex.disillusion.web.request.ResumeRequest; +import org.apache.commons.lang.RandomStringUtils; +import org.springframework.stereotype.Service; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; + +@Service +public class ResumeService { + + private final StudentResumeIndex studentResumeIndex; + private final ResumeTable resumeTable; + + private static final String resumeFileLocationBase = "database/files/resume"; + private static final File baseDir = new File(resumeFileLocationBase); + + public ResumeService(StudentResumeIndex studentResumeIndex, + ResumeTable resumeTable) { + + this.studentResumeIndex = studentResumeIndex; + this.resumeTable = resumeTable; + } + + public void addResume(String user, ResumeRequest request) throws IOException, ClassNotFoundException { + String resumeId = RandomStringUtils.randomAlphanumeric(8); + + Resume resume = new Resume(); + resume.setOwnerId(user); + resume.setId(resumeId); + resume.setTitle(request.getTitle()); + resume.setRemark(request.getRemark()); + resume.setResumeFileId(resumeId); + + resumeTable.insertOrUpdate(resume); + + if (!baseDir.exists()) { + baseDir.mkdirs(); + } + + Files.writeString(Paths.get(resumeFileLocationBase, resumeId), + request.getResumeFileBase64(), + StandardOpenOption.CREATE, StandardOpenOption.WRITE); + + MultiIndex multiIndex = studentResumeIndex.select(user); + if (multiIndex == null) { + multiIndex = new MultiIndex(user); + multiIndex.addValue(resume.getId()); + } else { + multiIndex.addValue(resume.getId()); + } + + studentResumeIndex.insertOrUpdate(multiIndex); + } + + public void updateResume(String user, String resumeId, ResumeRequest request) throws IOException, ClassNotFoundException { + MultiIndex multiIndex = studentResumeIndex.select(user); + if (multiIndex == null || !multiIndex.hasValue(resumeId)) { + return; + } + + Resume resume = new Resume(); + resume.setOwnerId(user); + resume.setId(resumeId); + resume.setTitle(request.getTitle()); + resume.setRemark(request.getRemark()); + resume.setResumeFileId(resumeId); + + updateResume(resume, request.getResumeFileBase64()); + } + + public void updateResume(Resume resume, String resumeFileData) throws IOException, ClassNotFoundException { + if (resumeFileData != null) { + Files.writeString(Paths.get(resumeFileLocationBase, resume.getId()), resumeFileData, StandardOpenOption.WRITE); + } + + resumeTable.insertOrUpdate(resume); + } + + public Resume[] getAllResume(String user) throws IOException, ClassNotFoundException { + MultiIndex index = studentResumeIndex.select(user); + if (index == null) { + return new Resume[0]; + } + + String[] ids = index.getAllValues(); + List resumes = new ArrayList<>(ids.length); + for (String id : ids) { + resumes.add(resumeTable.select(id)); + } + + return resumes.toArray(new Resume[resumes.size()]); + } + + public void deleteResume(String user, String id) throws IOException, ClassNotFoundException { + MultiIndex index = studentResumeIndex.select(user); + if (index == null || !index.hasValue(id)) { + return; + } + + index.removeValue(id); + studentResumeIndex.insertOrUpdate(index); + resumeTable.delete(id); + } + + public Resume getResume(String id) throws IOException, ClassNotFoundException { + return resumeTable.select(id); + } + + public String getResumeFileData(String fileId) throws IOException { + Path file = Paths.get(resumeFileLocationBase, fileId); + if (FileUtil.exists(file, true)) { + return Files.readString(file); + } else { + return Base64.encode("文件不存在"); + } + } +} diff --git a/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/service/user/UserInfoService.java b/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/service/user/UserInfoService.java new file mode 100644 index 0000000..6d88f9e --- /dev/null +++ b/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/service/user/UserInfoService.java @@ -0,0 +1,41 @@ +package net.lensfrex.disillusion.web.service.user; + +import net.lensfrex.disillusion.dao.UserInfoTable; +import net.lensfrex.disillusion.data.ArrayList; +import net.lensfrex.disillusion.data.List; +import net.lensfrex.disillusion.model.UserInfo; +import net.lensfrex.disillusion.model.employer.Employer; +import org.springframework.stereotype.Service; + +import java.io.IOException; + +@Service +public class UserInfoService { + private final UserInfoTable userInfoTable; + + public UserInfoService(UserInfoTable userInfoTable) { + this.userInfoTable = userInfoTable; + } + + public UserInfo getUserInfoById(String id) throws IOException, ClassNotFoundException { + return userInfoTable.select(id); + } + + public List getAllEmployerUserInfo() throws IOException, ClassNotFoundException { + List allUserInfo = userInfoTable.getAll(); + List employers = new ArrayList<>(allUserInfo.size()); + + for (int i = 0; i < allUserInfo.size(); i++) { + UserInfo userInfo = allUserInfo.get(i); + if (userInfo instanceof Employer employer) { + employers.add(employer); + } + } + + return employers; + } + + public void saveUserInfoById(UserInfo userInfo) throws IOException, ClassNotFoundException { + userInfoTable.insertOrUpdate(userInfo); + } +} diff --git a/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/service/user/UserService.java b/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/service/user/UserService.java new file mode 100644 index 0000000..940d1da --- /dev/null +++ b/disillusion/backend/src/main/java/net/lensfrex/disillusion/web/service/user/UserService.java @@ -0,0 +1,36 @@ +package net.lensfrex.disillusion.web.service.user; + +import net.lensfrex.disillusion.dao.UserTable; +import net.lensfrex.disillusion.data.List; +import net.lensfrex.disillusion.model.User; +import org.mindrot.jbcrypt.BCrypt; +import org.springframework.stereotype.Service; + +import java.io.IOException; + +@Service +public class UserService { + private final UserTable userTable; + + public UserService(UserTable userTable) { + this.userTable = userTable; + } + + public User getAccount(String username) throws IOException, ClassNotFoundException { + return userTable.select(username); + } + + public List getAllUser() throws IOException, ClassNotFoundException { + return userTable.getAll(); + } + + public void registerNewUser(String username, String password, User.UserType userType) throws IOException, ClassNotFoundException { + String hashedPassword = BCrypt.hashpw(password, BCrypt.gensalt()); + User newUser = new User(userType, username, hashedPassword); + userTable.insertOrUpdate(newUser); + } + + public void updateUser(User user) throws IOException, ClassNotFoundException { + userTable.insertOrUpdate(user); + } +} diff --git a/disillusion/backend/src/main/resources/application.properties b/disillusion/backend/src/main/resources/application.properties new file mode 100644 index 0000000..c959e3c --- /dev/null +++ b/disillusion/backend/src/main/resources/application.properties @@ -0,0 +1,6 @@ +disillusion.database-dir=./database +disillusion.tokenSecretKey=&&xenoblade&&test&&disillusion&& +spring.jackson.default-property-inclusion=non_null +springdoc.swagger-ui.path=/api.html +server.servlet.context-path=/api +server.port=8888 \ No newline at end of file diff --git a/disillusion/backend/src/test/java/net/lensfrex/disillusion/BackendApplicationTests.java b/disillusion/backend/src/test/java/net/lensfrex/disillusion/BackendApplicationTests.java new file mode 100644 index 0000000..9370d1e --- /dev/null +++ b/disillusion/backend/src/test/java/net/lensfrex/disillusion/BackendApplicationTests.java @@ -0,0 +1,13 @@ +package net.lensfrex.disillusion; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class BackendApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/disillusion/common/.flattened-pom.xml b/disillusion/common/.flattened-pom.xml new file mode 100644 index 0000000..4a9a60f --- /dev/null +++ b/disillusion/common/.flattened-pom.xml @@ -0,0 +1,40 @@ + + + 4.0.0 + net.lensfrex + common + 0.0.1-SNAPSHOT + + + cn.hutool + hutool-json + 5.8.12 + compile + + + jakarta.annotation + jakarta.annotation-api + 2.1.1 + compile + + + javax.annotation + javax.annotation-api + 1.3.2 + compile + + + org.projectlombok + lombok + 1.18.22 + compile + + + org.mapstruct + mapstruct + 1.5.3.Final + compile + + + diff --git a/disillusion/common/pom.xml b/disillusion/common/pom.xml new file mode 100644 index 0000000..5568f1b --- /dev/null +++ b/disillusion/common/pom.xml @@ -0,0 +1,27 @@ + + + 4.0.0 + + net.lensfrex + disillusion + ${revision} + + + common + + + 17 + 17 + UTF-8 + + + + + cn.hutool + hutool-json + 5.8.12 + + + \ No newline at end of file diff --git a/disillusion/common/src/main/java/net/lensfrex/disillusion/data/ArrayList.java b/disillusion/common/src/main/java/net/lensfrex/disillusion/data/ArrayList.java new file mode 100644 index 0000000..67285e3 --- /dev/null +++ b/disillusion/common/src/main/java/net/lensfrex/disillusion/data/ArrayList.java @@ -0,0 +1,178 @@ +package net.lensfrex.disillusion.data; + +import java.io.Serial; +import java.util.Arrays; +import java.util.Objects; + +/** + * 简略版的顺序表(ArrayList),参考了Java内置的ArrayList实现 + * + * @param + */ +public class ArrayList implements List { + @Serial + private static final long serialVersionUID = 114514L; + + private static final int DEFAULT_MAX_SIZE = 10; + + private static final Object[] EMPTY_ARRAY = {}; + + private int size = 0; + + private Object[] data; + + public ArrayList(int initialSize) { + data = new Object[initialSize]; + } + + public ArrayList() { + data = EMPTY_ARRAY; + } + + @Override + @SuppressWarnings("unchecked") + public T get(int index) { + if (index >= size) { + throw new IndexOutOfBoundsException(String.format("index %s is out of size %s", index, size)); + } + + return (T) data[index]; + } + + @Override + public int size() { + return this.size; + } + + @Override + public boolean isEmpty() { + return size == 0; + } + + @Override + public void add(T value) { + if (value == null) { + return; + } + + if (size == data.length) { + this.resize(); + } + + this.data[size++] = value; + } + + @Override + public void add(T value, int index) { + if (value == null) { + return; + } + + checkIndex(index); + if (data.length == size) { + this.resize(); + } + + System.arraycopy(this.data, index, + this.data, index + 1, + size - index); + + this.data[index] = value; + size++; + } + + @Override + public void set(int index, T newValue) { + if (newValue == null) { + return; + } + + checkIndex(index); + + this.data[index] = newValue; + } + + @Override + public void remove(int index) { + checkIndex(index); + + int numMoved = size - index - 1; + if (numMoved > 0) { + System.arraycopy(this.data, index + 1, + this.data, index, numMoved); + } + this.data[--size] = null; + this.trim(); + } + + @Override + public void remove(Object value) { + int index = indexOf(value); + if (index >= 0) { + remove(index); + } + } + + public void trim() { + if (size < data.length) { + data = (size == 0) ? EMPTY_ARRAY : Arrays.copyOf(data, size); + } + } + + @Override + public int indexOf(Object value) { + for (int i = 0; i < size; i++) { + if (Objects.equals(value, data[i])) { + return i; + } + } + + return -1; + } + + @Override + public Object[] toArray() { + return Arrays.copyOf(data, data.length); + } + + @SuppressWarnings("unchecked") + public T[] toArray(T[] array) { + if (array.length < size) { + return (T[]) Arrays.copyOf(data, size, array.getClass()); + } + System.arraycopy(data, 0, array, 0, size); + + return array; + } + + @Override + public boolean contains(Object obj) { + for (int i = 0; i < size; i++) { + if (data[i].equals(obj)) { + return true; + } + } + + return false; + } + + private void checkIndex(int index) { + if (index < 0 || index > size) { + throw new IndexOutOfBoundsException(indexOutOfBoundsMessage(index)); + } + } + + private String indexOutOfBoundsMessage(int index) { + return String.format("Index %s out of %s", index, size); + } + + private void resize() { + int newSize = (this.size + 1) * 2; + + if (this.data != EMPTY_ARRAY) { + this.data = Arrays.copyOf(data, newSize); + } else { + this.data = new Object[Math.max(newSize, DEFAULT_MAX_SIZE)]; + } + } +} diff --git a/disillusion/common/src/main/java/net/lensfrex/disillusion/data/Hash.java b/disillusion/common/src/main/java/net/lensfrex/disillusion/data/Hash.java new file mode 100644 index 0000000..28bc65b --- /dev/null +++ b/disillusion/common/src/main/java/net/lensfrex/disillusion/data/Hash.java @@ -0,0 +1,120 @@ +package net.lensfrex.disillusion.data; + +import java.io.Serializable; + +// 极其简陋的hashMap实现 +public class Hash implements Serializable { + private static final int TABLE_SIZE = 32; + + private final Node[] table; + + private int size = 0; + + @SuppressWarnings({"unchecked"}) + public Hash() { + this.table = (Node[]) new Node[TABLE_SIZE]; + } + + public void put(K key, V value) { + int index = Math.abs(key.hashCode()) % TABLE_SIZE; + + if (table[index] == null) { + table[index] = new Node<>(key, value, null); + + } else { + Node node = table[index]; + // key中第一个节点 + if (node.key.equals(key)) { + node.value = value; + return; + } + + while (node.next != null) { + // key对应的值已存在,直接替换 + if (node.next.key.equals(key)) { + node.next.value = value; + size++; + + return; + } + node = node.next; + } + + // key对应值不存在,插入 + node.next = new Node<>(key, value, null); + } + } + + public V get(K key) { + int index = Math.abs(key.hashCode()) % TABLE_SIZE; + + Node node = table[index]; + while (node != null) { + if (node.key.equals(key)) { + return node.value; + } + node = node.next; + } + + return null; + } + + public void delete(K key) { + int index = Math.abs(key.hashCode()) % TABLE_SIZE; + + Node node = table[index]; + while (node != null) { + if (node.key.equals(key)) { + table[index] = node.next; + + node.value = null; + node.next = null; + + size--; + + return; + } + + node = node.next; + } + } + + public List allValues() { + List result = new ArrayList<>(); + + for (Node node : table) { + while (node != null) { + result.add(node.value); + node = node.next; + } + } + + return result; + } + + public List allKeys() { + List result = new ArrayList<>(); + + for (Node node : table) { + while (node != null && !result.contains(node.key)) { + result.add(node.key); + node = node.next; + } + } + + return result; + } + + private static class Node implements Serializable { + private final K key; + + private V value; + private Node next; + + public Node(K key, V value, Node next) { + this.key = key; + this.value = value; + this.next = next; + } + } +} diff --git a/disillusion/common/src/main/java/net/lensfrex/disillusion/data/LinkedList.java b/disillusion/common/src/main/java/net/lensfrex/disillusion/data/LinkedList.java new file mode 100644 index 0000000..00180d7 --- /dev/null +++ b/disillusion/common/src/main/java/net/lensfrex/disillusion/data/LinkedList.java @@ -0,0 +1,217 @@ +package net.lensfrex.disillusion.data; + +import java.util.Arrays; +import java.util.Objects; + +public class LinkedList implements List { + private int size = 0; + + private Node first; + private Node last; + + public LinkedList() { + } + + @Override + public T get(int index) { + return node(index).data; + } + + private Node node(int index) { + if (index < size / 2) { + return getNodeForward(index); + } else { + return getNodeBackward(index); + } + } + + private Node getNodeForward(int index) { + Node node = first; + for (int i = 0; i < index; i++) { + node = node.next; + } + + return node; + } + + private Node getNodeBackward(int index) { + Node node = last; + for (int i = size - 1; i > index; i--) { + node = node.prev; + } + + return node; + } + + @Override + public int size() { + return this.size; + } + + @Override + public boolean isEmpty() { + return this.size == 0; + } + + @Override + public void add(T value) { + Node newNode = new Node<>(this.last, value, null); + // 空表 + if (this.first == null) { + this.first = newNode; + } else { + this.last.next = newNode; + } + + this.last = newNode; + size++; + } + + @Override + public void add(T value, int index) { + checkPositionIndex(index); + if (index == size) { + add(value); + } + + Node nativeNode = node(index); + Node prev = nativeNode.prev; + + Node newNode = new Node<>(prev, value, nativeNode); + nativeNode.prev = newNode; + + // 表头位置 + if (prev == null) { + first = newNode; + } else { + prev.next = newNode; + } + + size++; + } + + @Override + public void set(int index, T newValue) { + Node node = node(index); + node.data = newValue; + } + + @Override + public void remove(int index) { + checkElementIndex(index); + + unlink(node(index)); + size--; + } + + @Override + public void remove(Object value) { + Node node = first; + while (node.next != null) { + if (Objects.equals(value, node.data)) { + unlink(node); + return; + } + + node = node.next; + } + } + + private void unlink(Node node) { + Node prev = node.prev; + Node next = node.next; + + // 表头位置 + if (prev == null) { + this.first = next; + } else { + prev.next = next; + node.next = null; + } + + // 表尾位置 + if (next == null) { + this.last = prev; + } else { + next.prev = prev; + node.prev = null; + } + + node.data = null; + } + + @Override + public int indexOf(Object value) { + Node node = first; + if (node == null) { + return -1; + } + + for (int i = 0; node.next != null; i++) { + if (Objects.equals(value, node.data)) { + return i; + } + + node = node.next; + } + + return -1; + } + + @Override + @SuppressWarnings("unchecked") + public T[] toArray() { + Object[] data = new Object[size]; + + Node node = first; + for (int i = 0; i < size; i++) { + data[i] = node; + node = node.next; + } + + return (T[]) Arrays.copyOf(data, data.length); + } + + @Override + public T[] toArray(T[] array) { + return null; + } + + @Override + public boolean contains(Object obj) { + Node node = first; + for (int i = 0; i < size; i++) { + if (obj.equals(node.data)){ + return true; + } + + node = node.next; + } + + return false; + } + + private void checkPositionIndex(int index) { + if (index > size) { + throw new IndexOutOfBoundsException(String.format("Index %s out of %s", index, size)); + } + } + + private void checkElementIndex(int index) { + if (index >= size) { + throw new IndexOutOfBoundsException(String.format("Index %s out of %s", index, size)); + } + } + + private static class Node { + Node prev; + T data; + Node next; + + public Node(Node prev, T data, Node next) { + this.prev = prev; + this.data = data; + this.next = next; + } + } +} diff --git a/disillusion/common/src/main/java/net/lensfrex/disillusion/data/List.java b/disillusion/common/src/main/java/net/lensfrex/disillusion/data/List.java new file mode 100644 index 0000000..c2ae77c --- /dev/null +++ b/disillusion/common/src/main/java/net/lensfrex/disillusion/data/List.java @@ -0,0 +1,29 @@ +package net.lensfrex.disillusion.data; + +import java.io.Serializable; + +public interface List extends Serializable { + T get(int index); + + int size(); + + boolean isEmpty(); + + void add(T value); + + void add(T value, int index); + + void set(int index, T newValue); + + void remove(int index); + + void remove(Object value); + + int indexOf(Object value); + + boolean contains(Object obj); + + Object[] toArray(); + + T[] toArray(T[] array); +} diff --git a/disillusion/common/src/main/java/net/lensfrex/disillusion/data/Map.java b/disillusion/common/src/main/java/net/lensfrex/disillusion/data/Map.java new file mode 100644 index 0000000..caa4e0e --- /dev/null +++ b/disillusion/common/src/main/java/net/lensfrex/disillusion/data/Map.java @@ -0,0 +1,6 @@ +package net.lensfrex.disillusion.data; + +import java.io.Serializable; + +public interface Map extends Serializable { +} diff --git a/disillusion/common/src/main/java/net/lensfrex/disillusion/data/db/Table.java b/disillusion/common/src/main/java/net/lensfrex/disillusion/data/db/Table.java new file mode 100644 index 0000000..f0e0a02 --- /dev/null +++ b/disillusion/common/src/main/java/net/lensfrex/disillusion/data/db/Table.java @@ -0,0 +1,40 @@ +package net.lensfrex.disillusion.data.db; + +import net.lensfrex.disillusion.data.ArrayList; +import net.lensfrex.disillusion.data.Hash; + +public class Table { + private final ArrayList> page; + private final ArrayList keys; + + private final Hash primaryKeyIndex = new Hash<>(); + + private final Hash index = new Hash<>(); + private final ArrayList indexKeys; + + public Table(String[] fields, String... indexFields) { + this.page = new ArrayList<>(); + this.keys = new ArrayList<>(); + for (String field : fields) { + keys.add(field); + } + + indexKeys= new ArrayList<>(indexFields.length); + for (String indexField : indexFields) { + indexKeys.add(indexField); + } + } + + public void insert(String[] fields, String[] value) { + Hash record = new Hash<>(); + for (int i = 0; i < fields.length; i++) { + if (!keys.contains(fields[i])) { + throw new IllegalArgumentException("Field '%s' doesn't exist."); + } + + record.put(fields[i], value[i]); + } + + primaryKeyIndex.put(fields[1], page.size()); + } +} diff --git a/disillusion/common/src/main/java/net/lensfrex/disillusion/model/MultiIndex.java b/disillusion/common/src/main/java/net/lensfrex/disillusion/model/MultiIndex.java new file mode 100644 index 0000000..1e3d325 --- /dev/null +++ b/disillusion/common/src/main/java/net/lensfrex/disillusion/model/MultiIndex.java @@ -0,0 +1,44 @@ +package net.lensfrex.disillusion.model; + +import lombok.Data; +import net.lensfrex.disillusion.data.ArrayList; + +/** + * 哈希一对多索引,用于对取值不唯一的字段的查询 + */ +@Data +public class MultiIndex implements StoreData { + private final String key; + private final ArrayList values; + + public MultiIndex(String key) { + this.key = key; + this.values = new ArrayList<>(8); + } + + public void addValue(String value) { + values.add(value); + } + + public String[] getAllValues() { + return values.toArray(new String[values.size()]); + } + + public void removeValue(Object target) { + values.remove(target); + } + + public boolean hasValue(Object obj) { + return values.contains(obj); + } + + @Override + public String getId() { + return key; + } + + @Override + public void setId(String id) { + + } +} \ No newline at end of file diff --git a/disillusion/common/src/main/java/net/lensfrex/disillusion/model/OfferApplication.java b/disillusion/common/src/main/java/net/lensfrex/disillusion/model/OfferApplication.java new file mode 100644 index 0000000..53c9f26 --- /dev/null +++ b/disillusion/common/src/main/java/net/lensfrex/disillusion/model/OfferApplication.java @@ -0,0 +1,42 @@ +package net.lensfrex.disillusion.model; + +import lombok.Data; + +import java.io.Serializable; + +/** + * 求职申请 + */ +@Data +public class OfferApplication implements Serializable, StoreData { + private String id; + + /** + * 目标公司id + */ + private String targetId; + + /** + * 求职发起者id + */ + private String publisherId; + + /** + * 申请的offerId + */ + private String offerId; + + /** + * 简历id + */ + private String resumeId; + + /** + * 申请状态 + */ + private Status status; + + public enum Status { + ACCEPTED, WAITING, REJECTED + } +} \ No newline at end of file diff --git a/disillusion/common/src/main/java/net/lensfrex/disillusion/model/OfferStatus.java b/disillusion/common/src/main/java/net/lensfrex/disillusion/model/OfferStatus.java new file mode 100644 index 0000000..2c7e2d8 --- /dev/null +++ b/disillusion/common/src/main/java/net/lensfrex/disillusion/model/OfferStatus.java @@ -0,0 +1,8 @@ +package net.lensfrex.disillusion.model; + +import lombok.Data; + +@Data +public class OfferStatus implements StoreData { + private String id; +} diff --git a/disillusion/common/src/main/java/net/lensfrex/disillusion/model/StoreData.java b/disillusion/common/src/main/java/net/lensfrex/disillusion/model/StoreData.java new file mode 100644 index 0000000..4facfbd --- /dev/null +++ b/disillusion/common/src/main/java/net/lensfrex/disillusion/model/StoreData.java @@ -0,0 +1,14 @@ +package net.lensfrex.disillusion.model; + +import java.io.Serializable; + +public interface StoreData extends Serializable { + String id = ""; + + default String getId() { + return id; + } + + void setId(String id); + boolean equals(Object o); +} diff --git a/disillusion/common/src/main/java/net/lensfrex/disillusion/model/User.java b/disillusion/common/src/main/java/net/lensfrex/disillusion/model/User.java new file mode 100644 index 0000000..24d6270 --- /dev/null +++ b/disillusion/common/src/main/java/net/lensfrex/disillusion/model/User.java @@ -0,0 +1,61 @@ +package net.lensfrex.disillusion.model; + +import lombok.Data; + +import java.io.Serializable; +import java.util.Objects; + +@Data +public class User implements Serializable, StoreData { + private UserType userType; + + private final String username; + private final String password; + + public enum UserType { + STUDENT(0, "学生"), + EMPLOYER(1, "用人单位"), + ; + + public final int code; + public final String name; + + UserType(int code, String name) { + this.code = code; + this.name = name; + } + } + + public User(UserType userType, String username, String password) { + if (username == null) { + throw new NullPointerException("Field 'username' can't be null"); + } + + this.userType = userType; + this.username = username; + this.password = password; + } + + @Override + public String getId() { + return username; + } + + @Override + public void setId(String id) { + + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + User user = (User) o; + return username.equals(user.username); + } + + @Override + public int hashCode() { + return Objects.hash(username); + } +} \ No newline at end of file diff --git a/disillusion/common/src/main/java/net/lensfrex/disillusion/model/UserInfo.java b/disillusion/common/src/main/java/net/lensfrex/disillusion/model/UserInfo.java new file mode 100644 index 0000000..ba8e2b6 --- /dev/null +++ b/disillusion/common/src/main/java/net/lensfrex/disillusion/model/UserInfo.java @@ -0,0 +1,5 @@ +package net.lensfrex.disillusion.model; + +public interface UserInfo extends StoreData { + String getId(); +} diff --git a/disillusion/common/src/main/java/net/lensfrex/disillusion/model/employer/Employer.java b/disillusion/common/src/main/java/net/lensfrex/disillusion/model/employer/Employer.java new file mode 100644 index 0000000..d5ababa --- /dev/null +++ b/disillusion/common/src/main/java/net/lensfrex/disillusion/model/employer/Employer.java @@ -0,0 +1,60 @@ +package net.lensfrex.disillusion.model.employer; + +import lombok.Data; +import net.lensfrex.disillusion.model.UserInfo; +import net.lensfrex.disillusion.model.StoreData; +import net.lensfrex.disillusion.model.student.Student; + +import java.io.Serializable; +import java.util.Objects; + +/** + * 用人单位信息 + */ +@Data +public class Employer implements UserInfo, Serializable, StoreData { + private String id; + + /** + * 公司名称 + */ + private String name; + + /** + * 总部地址 + */ + private String place; + + /** + * 联系方式 + */ + private String contract; + + /** + * 公司简要介绍 + */ + private String shortDescribe; + + /** + * 公司介绍 + */ + private String describe; + + /** + * 所属行业 + */ + private String industry; + + public static Employer getDefaultInfo(String id) { + Employer employer = new Employer(); + employer.setId(id); + employer.setName("不知道是什么公司"); + employer.setPlace("不知道公司在哪里"); + employer.setShortDescribe("简短的介绍"); + employer.setDescribe("还没有什么描述呢"); + employer.setContract("400-1010101010"); + employer.setIndustry("不知道是什么行业"); + + return employer; + } +} diff --git a/disillusion/common/src/main/java/net/lensfrex/disillusion/model/employer/Offer.java b/disillusion/common/src/main/java/net/lensfrex/disillusion/model/employer/Offer.java new file mode 100644 index 0000000..80d4f27 --- /dev/null +++ b/disillusion/common/src/main/java/net/lensfrex/disillusion/model/employer/Offer.java @@ -0,0 +1,63 @@ +package net.lensfrex.disillusion.model.employer; + +import lombok.Data; +import net.lensfrex.disillusion.model.StoreData; + +import java.io.Serializable; +import java.util.Objects; + +/** + * 招聘信息 + */ +@Data +public class Offer implements Serializable, StoreData { + private String id; + + /** + * 发布公司 + */ + private String publisherId; + + /** + * 岗位名称 + */ + private String jobName; + + /** + * 工作地点 + */ + private String place; + + /** + * 要求 + */ + private String requirement; + + /** + * 预期薪资 + */ + private Integer salary; + + /** + * 目标人数 + */ + private Integer targetNumber; + + /** + * 详细信息 + */ + private String detail; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Offer offer = (Offer) o; + return id.equals(offer.id); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } +} diff --git a/disillusion/common/src/main/java/net/lensfrex/disillusion/model/student/Resume.java b/disillusion/common/src/main/java/net/lensfrex/disillusion/model/student/Resume.java new file mode 100644 index 0000000..dece53c --- /dev/null +++ b/disillusion/common/src/main/java/net/lensfrex/disillusion/model/student/Resume.java @@ -0,0 +1,34 @@ +package net.lensfrex.disillusion.model.student; + +import lombok.Data; +import net.lensfrex.disillusion.model.StoreData; + +import java.io.Serializable; + +/** + * 简历 + */ +@Data +public class Resume implements Serializable, StoreData { + private String id; + + /** + * 学生id + */ + private String ownerId; + + /** + * 简历标题(名称) + */ + private String title; + + /** + * 简历备注信息 + */ + private String remark; + + /** + * 简历文件id,一般来说等同于简历id + */ + private String resumeFileId; +} diff --git a/disillusion/common/src/main/java/net/lensfrex/disillusion/model/student/Student.java b/disillusion/common/src/main/java/net/lensfrex/disillusion/model/student/Student.java new file mode 100644 index 0000000..4d7a640 --- /dev/null +++ b/disillusion/common/src/main/java/net/lensfrex/disillusion/model/student/Student.java @@ -0,0 +1,66 @@ +package net.lensfrex.disillusion.model.student; + +import lombok.Data; +import net.lensfrex.disillusion.model.UserInfo; +import net.lensfrex.disillusion.model.StoreData; +import net.lensfrex.disillusion.util.IOUtil; + +import java.io.Serializable; +import java.nio.charset.StandardCharsets; + +@Data +public class Student implements UserInfo, Serializable, StoreData { + private String id; + + /** + * 真名 + */ + private String realName; + + /** + * 简介 + */ + private String shortDescribe; + + /** + * 介绍 + */ + private String describe; + + /** + * 电话号 + */ + private String phoneNumber; + + /** + * 身份证号 + */ + private String idNumber; + + /** + * 年龄 + */ + private int age; + + /** + * 性别,0:男;1:女 + */ + private int sex; + + public static final byte[] DEFAULT_AVATAR = IOUtil.readDataFromInputStream( + Student.class.getResourceAsStream("/61212844_p0_compress.png")); + + public static Student getDefaultInfo(String username) { + Student student = new Student(); + student.setId(username); + student.setRealName(username); + student.setShortDescribe("这个人很懒什么都没写"); + student.setDescribe("没有简介..."); + student.setPhoneNumber("没有填写"); + student.setIdNumber("没有提供"); + student.setAge(22); + student.setSex(0); + + return student; + } +} diff --git a/disillusion/common/src/main/java/net/lensfrex/disillusion/response/general/Response.java b/disillusion/common/src/main/java/net/lensfrex/disillusion/response/general/Response.java new file mode 100644 index 0000000..b0b2ffe --- /dev/null +++ b/disillusion/common/src/main/java/net/lensfrex/disillusion/response/general/Response.java @@ -0,0 +1,32 @@ +/* + * Class created by lensfrex. + */ + +package net.lensfrex.disillusion.response.general; + +import cn.hutool.json.JSONUtil; + +import java.nio.charset.StandardCharsets; + +public record Response(int code, String message, T data) { + + public static Response success(T data) { + return new Response<>(ResponseCode.SUCCESS.getCode(), "success", data); + } + + public static Response success() { + return success(null); + } + + public static Response error(ResponseCode code, String message) { + return new Response<>(code.getCode(), message, null); + } + + public static Response error(ResponseCode code) { + return error(code, code.getMessage()); + } + + public byte[] toBytes() { + return JSONUtil.toJsonStr(this).getBytes(StandardCharsets.UTF_8); + } +} \ No newline at end of file diff --git a/disillusion/common/src/main/java/net/lensfrex/disillusion/response/general/ResponseCode.java b/disillusion/common/src/main/java/net/lensfrex/disillusion/response/general/ResponseCode.java new file mode 100644 index 0000000..54c7c02 --- /dev/null +++ b/disillusion/common/src/main/java/net/lensfrex/disillusion/response/general/ResponseCode.java @@ -0,0 +1,45 @@ +/* + * Class created by lensfrex. + */ + +package net.lensfrex.disillusion.response.general; + +public enum ResponseCode { + SUCCESS(20000, "成功"), + REQUEST_TOO_FAST(20001, "技能冷却中..."), + + INVALID_REQUEST(30000, "非法请求"), + PARAM_WRONG(30001, "参数错误"), + + PERMISSION_DENIED(40000, "权限不足"), + TOKEN_EXPIRED(40001, "token过期"), + TOKEN_INVALID(40002, "token无效"), + + IDENTIFY_ERROR(40100, "用户不存在或密码错误"), + USER_ALREADY_EXISTS(40101, "用户名已经被注册"), + + USER_NOT_EXISTS(40103, "用户不存在"), + + SERVER_INTERNAL_ERROR(50000, "服务器内部错误"), + + API_NOT_IMPLEMENT(0, "接口未实现"), + + REQUEST_FILE_DOES_NOT_EXIST(60701, "请求的文件不存在"); + + private final int code; + + private final String message; + + ResponseCode(int code, String message) { + this.code = code; + this.message = message; + } + + public int getCode() { + return code; + } + + public String getMessage() { + return message; + } +} diff --git a/disillusion/common/src/main/java/net/lensfrex/disillusion/util/IOUtil.java b/disillusion/common/src/main/java/net/lensfrex/disillusion/util/IOUtil.java new file mode 100644 index 0000000..379872e --- /dev/null +++ b/disillusion/common/src/main/java/net/lensfrex/disillusion/util/IOUtil.java @@ -0,0 +1,48 @@ +package net.lensfrex.disillusion.util; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.nio.charset.Charset; + +public class IOUtil { + public static String inputStreamToString(InputStream inputStream, Charset charSet) { + byte[] bytes = readDataFromInputStream(inputStream); + if (bytes != null) { + return new String(bytes, charSet); + } else { + return null; + } + } + + public static byte[] readDataFromInputStream(InputStream inputStream) { + return readDataFromInputStream(inputStream, 5);// read every 5kb in default + } + + public static byte[] readDataFromInputStream(InputStream inputStream, int byteAllocation) { + try { + ByteArrayOutputStream byteArrayInputStream = new ByteArrayOutputStream(); + byte[] bytes = new byte[1024 * byteAllocation]; + + for (int length; (length = inputStream.read(bytes)) != -1; ) { + byteArrayInputStream.write(bytes, 0, length); + } + + byteArrayInputStream.flush(); + + inputStream.close(); + byteArrayInputStream.close(); + return byteArrayInputStream.toByteArray(); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + public static void writeFile(byte[] bytes, File file) throws Exception { + FileOutputStream fileOutputStream = new FileOutputStream(file); + fileOutputStream.write(bytes); + fileOutputStream.close(); + } +} diff --git a/disillusion/common/src/main/resources/61212844_p0_compress.png b/disillusion/common/src/main/resources/61212844_p0_compress.png new file mode 100644 index 0000000..1ba397b Binary files /dev/null and b/disillusion/common/src/main/resources/61212844_p0_compress.png differ diff --git a/disillusion/common/src/main/resources/defaultAvatarBase64.txt b/disillusion/common/src/main/resources/defaultAvatarBase64.txt new file mode 100644 index 0000000..40acbd3 --- /dev/null +++ b/disillusion/common/src/main/resources/defaultAvatarBase64.txt @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/disillusion/pom.xml b/disillusion/pom.xml new file mode 100644 index 0000000..1377bf1 --- /dev/null +++ b/disillusion/pom.xml @@ -0,0 +1,99 @@ + + + 4.0.0 + + net.lensfrex + disillusion + ${revision} + pom + + + backend + common + virtual-database + + + + 17 + 17 + UTF-8 + + 0.0.1-SNAPSHOT + + 1.18.22 + + 1.5.3.Final + + + + + + jakarta.annotation + jakarta.annotation-api + 2.1.1 + + + + javax.annotation + javax.annotation-api + 1.3.2 + + + org.projectlombok + lombok + ${lombok.version} + + + + org.mapstruct + mapstruct + ${mapstruct.version} + + + + + + + + org.codehaus.mojo + flatten-maven-plugin + 1.3.0 + + + + + flatten + process-resources + + flatten + + + + flatten.clean + clean + + clean + + + + + + + \ No newline at end of file diff --git a/disillusion/virtual-database/.flattened-pom.xml b/disillusion/virtual-database/.flattened-pom.xml new file mode 100644 index 0000000..5b4000e --- /dev/null +++ b/disillusion/virtual-database/.flattened-pom.xml @@ -0,0 +1,46 @@ + + + 4.0.0 + net.lensfrex + virtual-database + 0.0.1-SNAPSHOT + + + com.fasterxml.jackson.core + jackson-databind + 2.14.1 + compile + + + net.lensfrex + common + 0.0.1-SNAPSHOT + compile + + + jakarta.annotation + jakarta.annotation-api + 2.1.1 + compile + + + javax.annotation + javax.annotation-api + 1.3.2 + compile + + + org.projectlombok + lombok + 1.18.22 + compile + + + org.mapstruct + mapstruct + 1.5.3.Final + compile + + + diff --git a/disillusion/virtual-database/pom.xml b/disillusion/virtual-database/pom.xml new file mode 100644 index 0000000..fedb970 --- /dev/null +++ b/disillusion/virtual-database/pom.xml @@ -0,0 +1,33 @@ + + + 4.0.0 + + net.lensfrex + disillusion + ${revision} + + + virtual-database + + + 17 + 17 + UTF-8 + + + + com.fasterxml.jackson.core + jackson-databind + 2.14.1 + + + + net.lensfrex + common + ${revision} + + + + \ No newline at end of file diff --git a/disillusion/virtual-database/src/main/java/net/lensfrex/disillusion/dao/database/DatabaseOperator.java b/disillusion/virtual-database/src/main/java/net/lensfrex/disillusion/dao/database/DatabaseOperator.java new file mode 100644 index 0000000..11706d3 --- /dev/null +++ b/disillusion/virtual-database/src/main/java/net/lensfrex/disillusion/dao/database/DatabaseOperator.java @@ -0,0 +1,79 @@ +package net.lensfrex.disillusion.dao.database; + +import java.io.EOFException; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 虚拟数据库底层 + */ +class DatabaseOperator { + private static String baseDir; + + private static boolean initial = false; + + private static final Map paths = new ConcurrentHashMap<>(); + + private static final Map tableLocks = new ConcurrentHashMap<>(); + + public static void write(String table, T data) throws IOException { + ObjectOutputStream outputStream = new ObjectOutputStream(Files.newOutputStream(Path.of(baseDir, table))); + outputStream.writeObject(data); + outputStream.flush(); + outputStream.close(); + + tableLocks.remove(table); + } + + public static T read(String table, Class type) throws IOException { + if (!tableLocks.containsKey(table)) { + tableLocks.put(table, new Object()); + } + + Path path = getPath(table); + + if (!Files.exists(path)) { + Files.createFile(path); + return null; + } + + try { + ObjectInputStream inputStream = new ObjectInputStream(Files.newInputStream(getPath(table))); + @SuppressWarnings("unchecked") T result = (T) inputStream.readObject(); + inputStream.close(); + + tableLocks.remove(table); + + return result; + } catch (EOFException e) { + return null; + } catch (Exception e) { + e.printStackTrace(); + throw new IOException("IOException"); + } + } + + private static Path getPath(String table) { + if (!paths.containsKey(table)) { + paths.put(table, Path.of(baseDir, table)); + } + + return paths.get(table); + } + + protected static void init(String baseDir) { + if (initial) { + return; + } + + DatabaseOperator.baseDir = baseDir; + Paths.get(baseDir).toFile().mkdirs(); + initial = true; + } +} diff --git a/disillusion/virtual-database/src/main/java/net/lensfrex/disillusion/dao/database/Table.java b/disillusion/virtual-database/src/main/java/net/lensfrex/disillusion/dao/database/Table.java new file mode 100644 index 0000000..17b0346 --- /dev/null +++ b/disillusion/virtual-database/src/main/java/net/lensfrex/disillusion/dao/database/Table.java @@ -0,0 +1,11 @@ +package net.lensfrex.disillusion.dao.database; + +import java.io.IOException; + +public interface Table { + T select(String id) throws IOException, ClassNotFoundException; + void delete(T record) throws IOException, ClassNotFoundException; + void delete(String id) throws IOException, ClassNotFoundException; + void insertOrUpdate(T record) throws IOException, ClassNotFoundException; + void update(T record) throws IOException, ClassNotFoundException; +} diff --git a/disillusion/virtual-database/src/main/java/net/lensfrex/disillusion/dao/database/VirtualDatabase.java b/disillusion/virtual-database/src/main/java/net/lensfrex/disillusion/dao/database/VirtualDatabase.java new file mode 100644 index 0000000..47ec621 --- /dev/null +++ b/disillusion/virtual-database/src/main/java/net/lensfrex/disillusion/dao/database/VirtualDatabase.java @@ -0,0 +1,17 @@ +package net.lensfrex.disillusion.dao.database; + +import java.io.IOException; + +public class VirtualDatabase { + public static T getTable(String tableName, Class objectClass) throws IOException, ClassNotFoundException { + return (T) DatabaseOperator.read(tableName, objectClass); + } + + public static void saveTable(String tableName, T tableObj) throws IOException { + DatabaseOperator.write(tableName, tableObj); + } + + public static void init(String baseDir) throws IOException { + DatabaseOperator.init(baseDir); + } +}