roles = employeeService.getRolesByEmployeeId(employee.getId());
+ Platform.runLater(() -> {
+ update(employee);
+ rolesProperty().setAll(roles);
+ future.complete(null);
+ });
+ });
+ /**
+ * 定时更新用户活动状态
+ */
+ executorService.scheduleWithFixedDelay(() -> {
+ try {
+ SpringApp.getBean(EmployeeService.class).updateActive(Desktop.instance.getSessionId());
+ } catch (Exception e) {
+ if (logger.isErrorEnabled()) {
+ logger.error("updateActive:{}", e.getMessage(), e);
+ }
+ }
+ }, 10, 10, TimeUnit.SECONDS);
+ return future;
+ }
+}
diff --git a/src/main/java/com/ecep/contract/manager/Desktop.java b/src/main/java/com/ecep/contract/manager/Desktop.java
new file mode 100644
index 0000000..0e7d470
--- /dev/null
+++ b/src/main/java/com/ecep/contract/manager/Desktop.java
@@ -0,0 +1,325 @@
+package com.ecep.contract.manager;
+
+import com.ecep.contract.manager.ds.other.controller.LoginWidowController;
+import com.ecep.contract.manager.ui.BaseController;
+import com.ecep.contract.manager.ui.MessageHolder;
+import com.ecep.contract.manager.ui.task.TaskMonitorCenter;
+import com.ecep.contract.manager.util.UITools;
+import javafx.application.Application;
+import javafx.application.Platform;
+import javafx.beans.property.SimpleIntegerProperty;
+import javafx.fxml.FXMLLoader;
+import javafx.scene.Node;
+import javafx.scene.Parent;
+import javafx.scene.Scene;
+import javafx.scene.control.ScrollPane;
+import javafx.scene.layout.VBox;
+import javafx.scene.paint.Color;
+import javafx.scene.text.Text;
+import javafx.stage.Stage;
+import javafx.stage.StageStyle;
+import lombok.Getter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
+import org.springframework.util.StringUtils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.net.URL;
+import java.util.List;
+import java.util.Properties;
+import java.util.concurrent.*;
+import java.util.function.Consumer;
+import java.util.logging.Level;
+
+/**
+ * JavaFx 应用程序
+ *
+ * @author ecep
+ * Created by ecep on 2017/05/08.
+ */
+public class Desktop extends Application {
+ private static final Logger logger = LoggerFactory.getLogger(Desktop.class);
+ public static Desktop instance;
+
+ /**
+ * 在默认浏览器中打开指定的URL。
+ *
+ * 该函数使用JavaFX的HostServices类来调用系统默认的浏览器,并打开传入的URL。
+ *
+ * @param url 要在浏览器中打开的URL字符串。该参数不能为空,且应为有效的URL格式。
+ */
+ public static void showInBrowse(String url) {
+ instance.getHostServices().showDocument(url);
+ }
+
+ /**
+ * 在系统的文件资源管理器中打开指定的文件夹。
+ *
+ * 该方法首先尝试使用 java.awt.Desktop API 打开文件夹。如果该 API 不支持,
+ * 则在 Windows 系统中使用 explorer.exe 打开文件夹。
+ *
+ * @param dir 要打开的文件夹对象。如果为 null 或无效路径,可能会抛出异常。
+ * @throws RuntimeException 如果使用 java.awt.Desktop 打开文件夹时发生 IOException,
+ * 则将其包装为 RuntimeException 抛出。
+ */
+ public static void showInExplorer(File dir) {
+ if (java.awt.Desktop.isDesktopSupported()) {
+ try {
+ java.awt.Desktop.getDesktop().open(dir);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ } else {
+ try {
+ // 在Windows中使用explorer.exe打开文件夹,路径用双引号括起来
+ Process process = Runtime.getRuntime().exec(
+ new String[] { "explorer.exe", "\"" + dir.getAbsolutePath() + "\"" }, null, new File("."));
+ // process.waitFor();
+ } catch (IOException e) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Unable open {}", dir.getAbsolutePath(), e);
+ }
+ }
+ }
+ }
+
+ public static void checkAndShowInExplorer(String path, Consumer consumer) {
+ if (!StringUtils.hasText(path)) {
+ consumer.accept("文件/目录为空,无法打开");
+ return;
+ }
+ File file = new File(path);
+ if (!file.exists()) {
+ if (file.isFile()) {
+ consumer.accept("文件 " + file.getAbsolutePath() + " 不存在,请确认");
+ } else {
+ consumer.accept("目录 " + file.getAbsolutePath() + " 不存在,请确认");
+ }
+ return;
+ }
+
+ try {
+ Desktop.showInExplorer(file);
+ consumer.accept("打开文件/目录 " + path);
+ } catch (Exception e) {
+ consumer.accept("打开文件错误:" + e.getMessage());
+ }
+ }
+
+ public static void shutdown() {
+ if (logger.isDebugEnabled()) {
+ logger.debug("shutdown");
+ }
+ if (instance != null) {
+ try {
+ instance.stop();
+ } catch (Throwable e) {
+ logger.error("shutdown error", e);
+ }
+ }
+ }
+
+ private ScheduledExecutorService scheduledExecutorService = null;
+ private final TaskMonitorCenter taskMonitorCenter = new TaskMonitorCenter();
+
+ private final SimpleIntegerProperty sessionId = new SimpleIntegerProperty(0);
+ @Getter
+ private final CurrentEmployee activeEmployee = new CurrentEmployee();
+
+ public void setActiveEmployeeId(int activeEmployeeId) {
+ activeEmployee.getId().set(activeEmployeeId);
+ }
+
+ public int getActiveEmployeeId() {
+ return activeEmployee.getId().get();
+ }
+
+ public int getSessionId() {
+ return sessionId.get();
+ }
+
+ public void setSessionId(int sessionId) {
+ this.sessionId.set(sessionId);
+ }
+
+ public ScheduledExecutorService getExecutorService() {
+ if (scheduledExecutorService == null) {
+ scheduledExecutorService = Executors.newScheduledThreadPool(3);
+ }
+ return scheduledExecutorService;
+ }
+
+ public TaskMonitorCenter getTaskMonitorCenter() {
+ return taskMonitorCenter;
+ }
+
+ @Override
+ public void start(Stage primaryStage) throws Exception {
+ if (logger.isDebugEnabled()) {
+ logger.debug("start");
+ }
+ if (instance != null) {
+ logger.error("Desktop already started");
+ }
+ instance = this;
+
+ URL resource = getClass().getResource("/ui/start_lamp.fxml");
+ FXMLLoader loader = new FXMLLoader(resource);
+ primaryStage.setTitle("CMS");
+ primaryStage.initStyle(StageStyle.TRANSPARENT);
+
+ Parent root = loader.load();
+ Scene scene = new Scene(root);
+ scene.getStylesheets().add("/ui/start_lamp.css");
+
+ primaryStage.setScene(scene);
+ primaryStage.setOnShown(e -> {
+ System.out.println("primaryStage#OnShown");
+ });
+ primaryStage.show();
+ System.out.println("Desktop.start -> primaryStage.show()");
+
+ try {
+ startSpringApp(primaryStage, root, loader);
+ } catch (Exception e) {
+ UITools.showExceptionAndWait("启动失败", e);
+ }
+ }
+
+ private void startSpringApp(Stage primaryStage, Parent root, FXMLLoader loader) {
+ System.out.println("Desktop.startSpringApp");
+ // 更新窗口标题
+ Node titleNode = root.lookup("#title");
+ if (titleNode != null) {
+ primaryStage.setTitle(((Text) titleNode).getText());
+ }
+
+ Node lookup = root.lookup("#logBox");
+ if (!(lookup instanceof VBox logBox)) {
+ throw new RuntimeException("启动界面加载失败, #logger 类型错误");
+ }
+
+ ScrollPane logPane = (ScrollPane) root.lookup("#logPane");
+
+ logBox.getChildren().clear();
+ MessageHolder holder = (level, message) -> {
+ Text text = new Text(message);
+ if (Level.WARNING == level) { // warning
+ text.setFill(Color.YELLOW);
+ } else if (Level.SEVERE == level) {// error
+ text.setFill(Color.RED);
+ } else if (Level.FINE == level) { // debug
+ text.setFill(Color.GRAY);
+ } else {
+ text.setFill(Color.WHITE);
+ }
+ Platform.runLater(() -> {
+ logBox.getChildren().add(text);
+ logPane.layout();
+ logPane.setVvalue(1.0);
+ });
+ };
+
+ holder.info("启动中,请稍后...");
+
+ CompletableFuture.runAsync(() -> {
+ try {
+ //
+ holder.info("读取配置文件...");
+ Properties properties = new Properties();
+ File configFile = new File("config.properties");
+ if (configFile.exists()) {
+ holder.debug("读取配置文件 " + configFile.getName() + "...");
+ try (FileInputStream input = new FileInputStream(configFile)) {
+ properties.load(input);
+ holder.info("配置文件读取成功.");
+ } catch (IOException e) {
+ holder.error("读取失败:" + e.getMessage());
+ logger.error(e.getMessage(), e);
+ return;
+ }
+ }
+
+ CompletableFuture.runAsync(() -> {
+ SpringApp.launch(properties, holder);
+ ConfigurableListableBeanFactory beanFactory = SpringApp.context.getBeanFactory();
+
+ beanFactory.registerSingleton("scheduledExecutorService", getExecutorService());
+ beanFactory.registerSingleton("taskMonitorCenter", taskMonitorCenter);
+
+ });
+
+ try {
+ LoginWidowController controller = new LoginWidowController();
+ controller.setHolder(holder);
+ controller.setPrimaryStage(primaryStage);
+ controller.setProperties(properties);
+ while (true) {
+ controller.tryLogin();
+ break;
+ }
+ if (logger.isDebugEnabled()) {
+ logger.debug("login in");
+ }
+ } catch (Exception e) {
+ holder.error("登录失败:" + e.getMessage());
+ logger.error(e.getMessage(), e);
+ }
+ } catch (Exception e) {
+ holder.error(e.getMessage());
+ logger.error(e.getMessage(), e);
+ }
+ });
+ System.out.println("Desktop.startSpringApp.");
+ }
+
+ @Override
+ public void stop() throws Exception {
+ if (logger.isDebugEnabled()) {
+ logger.debug("stop");
+ }
+ CompletableFuture future = BaseController.shutdown();
+ future.orTimeout(5, TimeUnit.SECONDS);
+ future.exceptionally(e -> {
+ logger.error(e.getMessage(), e);
+ return null;
+ });
+ future.thenRun(() -> {
+ SpringApp.shutdown();
+ try {
+ shutdownExecutorService();
+ super.stop();
+ if (logger.isDebugEnabled()) {
+ logger.debug("stopped");
+ }
+ } catch (Exception e) {
+ logger.error(e.getMessage(), e);
+ }
+ });
+ }
+
+ private void shutdownExecutorService() {
+ List runnableList = scheduledExecutorService.shutdownNow();
+ for (Runnable runnable : runnableList) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("shutdown runnable = {}", runnable);
+ }
+ if (runnable instanceof FutureTask> future) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("runnable as future, isCancelled() = {}, isDone() = {}", future.isCancelled(),
+ future.isDone());
+ }
+ if (future.cancel(true)) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("runnable as future canceled");
+ }
+ }
+ }
+ }
+ scheduledExecutorService.close();
+ }
+
+}
diff --git a/src/main/java/com/ecep/contract/manager/MyPersistentCookieStore.java b/src/main/java/com/ecep/contract/manager/MyPersistentCookieStore.java
new file mode 100644
index 0000000..109ad1f
--- /dev/null
+++ b/src/main/java/com/ecep/contract/manager/MyPersistentCookieStore.java
@@ -0,0 +1,205 @@
+package com.ecep.contract.manager;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import java.io.*;
+import java.net.CookieStore;
+import java.net.HttpCookie;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.*;
+
+public class MyPersistentCookieStore implements CookieStore {
+
+
+ private final File file;
+ private final ObjectMapper objectMapper;
+ private Map> cookiesMap = new HashMap<>();
+
+ public MyPersistentCookieStore(File file, ObjectMapper objectMapper) {
+ this.file = file;
+ this.objectMapper = objectMapper;
+ if (file != null && file.exists()) {
+ loadFromFile2();
+ }
+ }
+
+ private void loadFromFile2() {
+ try {
+ ObjectNode root = (ObjectNode) objectMapper.readTree(file);
+ for (Iterator> it = root.fields(); it.hasNext(); ) {
+ Map.Entry entry = it.next();
+ String key = entry.getKey();
+ ArrayNode value = (ArrayNode) entry.getValue();
+
+ List cookies = new ArrayList<>();
+ for (JsonNode node1 : value) {
+ HttpCookie cookie = new HttpCookie(node1.get("name").asText(), node1.get("value").asText());
+ objectMapper.updateValue(node1, cookie);
+ cookies.add(cookie);
+ }
+ URI uri = URI.create(key);
+ System.out.println(key + " -> " + uri);
+ cookiesMap.put(uri, cookies);
+ }
+
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public void saveToFile2() {
+ try {
+ objectMapper.writeValue(file, cookiesMap);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public void loadFromFile(File file) {
+ try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
+ String line;
+ while ((line = reader.readLine()) != null) {
+ // 假设格式为: name=value;domain=domain;path=path;expires=expires;
+ String[] parts = line.split(";");
+ String nameValue = parts[0];
+ String[] nameValueParts = nameValue.split("=");
+ String name = nameValueParts[0];
+ String value = nameValueParts.length > 1 ? nameValueParts[1] : "";
+ HttpCookie cookie = new HttpCookie(name, value);
+ for (int i = 1; i < parts.length; i++) {
+ String[] attribute = parts[i].split("=");
+ if (attribute[0].equals("domain")) {
+ cookie.setDomain(attribute[1]);
+ } else if (attribute[0].equals("path")) {
+ cookie.setPath(attribute[1]);
+ } else if (attribute[0].equals("expires")) {
+ // 解析日期格式并设置过期时间
+ // 这里只是示例,实际需要正确解析日期格式
+ Date expiresDate = new Date(Long.parseLong(attribute[1]));
+ cookie.setMaxAge(expiresDate.getTime() - System.currentTimeMillis());
+ }
+ }
+ URI uri = URI.create(cookie.getDomain());
+ List cookies = cookiesMap.getOrDefault(uri, new ArrayList<>());
+ cookies.add(cookie);
+ cookiesMap.put(uri, cookies);
+ }
+ } catch (IOException e) {
+ // 处理文件读取错误
+ e.printStackTrace();
+ }
+ }
+
+ public void saveToFile() {
+ try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) {
+ for (Map.Entry> entry : cookiesMap.entrySet()) {
+ for (HttpCookie cookie : entry.getValue()) {
+ String line = cookie.getName() + "=" + cookie.getValue() + ";domain=" + cookie.getDomain() + ";path=" + cookie.getPath();
+ if (cookie.getMaxAge() > 0) {
+ line += ";expires=" + (System.currentTimeMillis() + cookie.getMaxAge());
+ }
+ writer.write(line);
+ writer.newLine();
+ }
+ }
+ } catch (IOException e) {
+ // 处理文件写入错误
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public void add(URI uri, HttpCookie cookie) {
+ if (cookie == null) {
+ throw new NullPointerException("cookie is null");
+ }
+
+ if (cookie.getDomain() != null) {
+ URI key = getEffectiveURI(cookie);
+ List cookies = cookiesMap.getOrDefault(key, new ArrayList<>());
+ cookies.remove(cookie);
+ cookies.add(cookie);
+ cookiesMap.put(key, cookies);
+ }
+
+ if (uri != null) {
+ URI key = getEffectiveURI(uri);
+ List cookies = cookiesMap.getOrDefault(key, new ArrayList<>());
+ cookies.remove(cookie);
+ cookies.add(cookie);
+ cookiesMap.put(key, cookies);
+ }
+ saveToFile2();
+ }
+
+ @Override
+ public List get(URI uri) {
+ URI effectiveURI = getEffectiveURI(uri);
+ System.out.println("effectiveURI = " + effectiveURI);
+ return cookiesMap.getOrDefault(effectiveURI, new ArrayList<>());
+ }
+
+ @Override
+ public List getCookies() {
+ return cookiesMap.values().stream().flatMap(List::stream).toList();
+ }
+
+ @Override
+ public List getURIs() {
+ return cookiesMap.keySet().stream().toList();
+ }
+
+ @Override
+ public boolean remove(URI uri, HttpCookie cookie) {
+ URI key = getEffectiveURI(uri);
+ List httpCookies = cookiesMap.get(key);
+ if (httpCookies == null) {
+ return false;
+ }
+ return httpCookies.remove(cookie);
+ }
+
+ @Override
+ public boolean removeAll() {
+ cookiesMap.clear();
+ return true;
+ }
+
+ private URI getEffectiveURI(URI uri) {
+ URI effectiveURI = null;
+ try {
+ effectiveURI = new URI("http",
+ uri.getHost(),
+ null, // path component
+ null, // query component
+ null // fragment component
+ );
+ } catch (URISyntaxException ignored) {
+ ignored.printStackTrace();
+ effectiveURI = uri;
+ }
+
+ return effectiveURI;
+ }
+
+ private URI getEffectiveURI(HttpCookie cookie) {
+ URI effectiveURI = null;
+ try {
+ effectiveURI = new URI("http",
+ cookie.getDomain(),
+ null, // path component
+ null, // query component
+ null // fragment component
+ );
+ } catch (URISyntaxException ignored) {
+
+ }
+
+ return effectiveURI;
+ }
+}
diff --git a/src/main/java/com/ecep/contract/manager/MyPersistentCookieStore2.java b/src/main/java/com/ecep/contract/manager/MyPersistentCookieStore2.java
new file mode 100644
index 0000000..aa5d0ee
--- /dev/null
+++ b/src/main/java/com/ecep/contract/manager/MyPersistentCookieStore2.java
@@ -0,0 +1,436 @@
+package com.ecep.contract.manager;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import java.io.*;
+import java.net.CookieStore;
+import java.net.HttpCookie;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.*;
+import java.util.concurrent.locks.ReentrantLock;
+
+public class MyPersistentCookieStore2 implements CookieStore {
+
+
+ private final File file;
+ private final ObjectMapper objectMapper;
+
+ private List cookieJar = null;
+
+ // the cookies are indexed by its domain and associated uri (if present)
+ // CAUTION: when a cookie removed from main data structure (i.e. cookieJar),
+ // it won't be cleared in domainIndex & uriIndex. Double-check the
+ // presence of cookie when retrieve one form index store.
+ private Map> domainIndex = null;
+ private Map> uriIndex = null;
+
+ // use ReentrantLock instead of synchronized for scalability
+ private ReentrantLock lock = null;
+
+
+ public MyPersistentCookieStore2(File file, ObjectMapper objectMapper) {
+ this.file = file;
+ this.objectMapper = objectMapper;
+ cookieJar = new ArrayList<>();
+ domainIndex = new HashMap<>();
+ uriIndex = new HashMap<>();
+
+ lock = new ReentrantLock(false);
+
+ if (file != null && file.exists()) {
+ loadFromFile2();
+ }
+ }
+
+ private void loadFromFile2() {
+ try {
+ ObjectNode root = (ObjectNode) objectMapper.readTree(file);
+
+ ArrayNode cookieJarNode = (ArrayNode) root.get("cookieJar");
+ for (JsonNode node1 : cookieJarNode) {
+ HttpCookie cookie = new HttpCookie(node1.get("name").asText(), node1.get("value").asText());
+ objectMapper.updateValue(node1, cookie);
+ cookieJar.add(cookie);
+ }
+
+
+ ObjectNode domainIndexNode = (ObjectNode) root.get("domainIndex");
+ for (Iterator> it = domainIndexNode.fields(); it.hasNext(); ) {
+ Map.Entry entry = it.next();
+ String key = entry.getKey();
+ ArrayNode value = (ArrayNode) entry.getValue();
+
+ List cookies = new ArrayList<>();
+ for (JsonNode node1 : value) {
+ HttpCookie cookie = new HttpCookie(node1.get("name").asText(), node1.get("value").asText());
+ objectMapper.updateValue(node1, cookie);
+ cookies.add(cookie);
+ }
+ domainIndex.put(key, cookies);
+ }
+
+ ObjectNode uriIndexNode = (ObjectNode) root.get("uriIndex");
+ for (Iterator> it = uriIndexNode.fields(); it.hasNext(); ) {
+ Map.Entry entry = it.next();
+ String key = entry.getKey();
+ ArrayNode value = (ArrayNode) entry.getValue();
+
+ List cookies = new ArrayList<>();
+ for (JsonNode node1 : value) {
+ HttpCookie cookie = new HttpCookie(node1.get("name").asText(), node1.get("value").asText());
+ objectMapper.updateValue(node1, cookie);
+ cookies.add(cookie);
+ }
+ URI uri = URI.create(key);
+ System.out.println(key + " -> " + uri);
+ uriIndex.put(uri, cookies);
+ }
+
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public void saveToFile2() {
+ try {
+ HashMap map = new HashMap<>();
+ map.put("cookieJar", cookieJar);
+ map.put("domainIndex", domainIndex);
+ map.put("uriIndex", uriIndex);
+ objectMapper.writeValue(file, map);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+
+ /**
+ * Add one cookie into cookie store.
+ */
+ public void add(URI uri, HttpCookie cookie) {
+ // pre-condition : argument can't be null
+ if (cookie == null) {
+ throw new NullPointerException("cookie is null");
+ }
+
+
+ lock.lock();
+ try {
+ // remove the ole cookie if there has had one
+ cookieJar.remove(cookie);
+
+ // add new cookie if it has a non-zero max-age
+ if (cookie.getMaxAge() != 0) {
+ cookieJar.add(cookie);
+ // and add it to domain index
+ if (cookie.getDomain() != null) {
+ addIndex(domainIndex, cookie.getDomain(), cookie);
+ }
+ if (uri != null) {
+ // add it to uri index, too
+ addIndex(uriIndex, getEffectiveURI(uri), cookie);
+ }
+ }
+ } finally {
+ lock.unlock();
+ }
+
+ saveToFile2();
+ }
+
+
+ /**
+ * Get all cookies, which:
+ * 1) given uri domain-matches with, or, associated with
+ * given uri when added to the cookie store.
+ * 3) not expired.
+ * See RFC 2965 sec. 3.3.4 for more detail.
+ */
+ public List get(URI uri) {
+ // argument can't be null
+ if (uri == null) {
+ throw new NullPointerException("uri is null");
+ }
+
+ List cookies = new ArrayList<>();
+ boolean secureLink = "https".equalsIgnoreCase(uri.getScheme());
+ lock.lock();
+ try {
+ // check domainIndex first
+ getInternal1(cookies, domainIndex, uri.getHost(), secureLink);
+ // check uriIndex then
+ getInternal2(cookies, uriIndex, getEffectiveURI(uri), secureLink);
+ } finally {
+ lock.unlock();
+ }
+
+ return cookies;
+ }
+
+ /**
+ * Get all cookies in cookie store, except those have expired
+ */
+ public List getCookies() {
+ List rt;
+
+ lock.lock();
+ try {
+ Iterator it = cookieJar.iterator();
+ while (it.hasNext()) {
+ if (it.next().hasExpired()) {
+ it.remove();
+ }
+ }
+ } finally {
+ rt = Collections.unmodifiableList(cookieJar);
+ lock.unlock();
+ }
+
+ return rt;
+ }
+
+ /**
+ * Get all URIs, which are associated with at least one cookie
+ * of this cookie store.
+ */
+ public List getURIs() {
+ List uris = new ArrayList<>();
+
+ lock.lock();
+ try {
+ Iterator it = uriIndex.keySet().iterator();
+ while (it.hasNext()) {
+ URI uri = it.next();
+ List cookies = uriIndex.get(uri);
+ if (cookies == null || cookies.size() == 0) {
+ // no cookies list or an empty list associated with
+ // this uri entry, delete it
+ it.remove();
+ }
+ }
+ } finally {
+ uris.addAll(uriIndex.keySet());
+ lock.unlock();
+ }
+
+ return uris;
+ }
+
+
+ /**
+ * Remove a cookie from store
+ */
+ public boolean remove(URI uri, HttpCookie ck) {
+ // argument can't be null
+ if (ck == null) {
+ throw new NullPointerException("cookie is null");
+ }
+
+ boolean modified = false;
+ lock.lock();
+ try {
+ modified = cookieJar.remove(ck);
+ } finally {
+ lock.unlock();
+ }
+
+ return modified;
+ }
+
+
+ /**
+ * Remove all cookies in this cookie store.
+ */
+ public boolean removeAll() {
+ lock.lock();
+ try {
+ if (cookieJar.isEmpty()) {
+ return false;
+ }
+ cookieJar.clear();
+ domainIndex.clear();
+ uriIndex.clear();
+ } finally {
+ lock.unlock();
+ }
+
+ return true;
+ }
+
+
+ /* ---------------- Private operations -------------- */
+
+
+ /*
+ * This is almost the same as HttpCookie.domainMatches except for
+ * one difference: It won't reject cookies when the 'H' part of the
+ * domain contains a dot ('.').
+ * I.E.: RFC 2965 section 3.3.2 says that if host is x.y.domain.com
+ * and the cookie domain is .domain.com, then it should be rejected.
+ * However that's not how the real world works. Browsers don't reject and
+ * some sites, like yahoo.com do actually expect these cookies to be
+ * passed along.
+ * And should be used for 'old' style cookies (aka Netscape type of cookies)
+ */
+ private boolean netscapeDomainMatches(String domain, String host) {
+ if (domain == null || host == null) {
+ return false;
+ }
+
+ // if there's no embedded dot in domain and domain is not .local
+ boolean isLocalDomain = ".local".equalsIgnoreCase(domain);
+ int embeddedDotInDomain = domain.indexOf('.');
+ if (embeddedDotInDomain == 0) {
+ embeddedDotInDomain = domain.indexOf('.', 1);
+ }
+ if (!isLocalDomain && (embeddedDotInDomain == -1 || embeddedDotInDomain == domain.length() - 1)) {
+ return false;
+ }
+
+ // if the host name contains no dot and the domain name is .local
+ int firstDotInHost = host.indexOf('.');
+ if (firstDotInHost == -1 && isLocalDomain) {
+ return true;
+ }
+
+ int domainLength = domain.length();
+ int lengthDiff = host.length() - domainLength;
+ if (lengthDiff == 0) {
+ // if the host name and the domain name are just string-compare equal
+ return host.equalsIgnoreCase(domain);
+ } else if (lengthDiff > 0) {
+ // need to check H & D component
+ String H = host.substring(0, lengthDiff);
+ String D = host.substring(lengthDiff);
+
+ return (D.equalsIgnoreCase(domain));
+ } else if (lengthDiff == -1) {
+ // if domain is actually .host
+ return (domain.charAt(0) == '.' &&
+ host.equalsIgnoreCase(domain.substring(1)));
+ }
+
+ return false;
+ }
+
+ private void getInternal1(List cookies, Map> cookieIndex,
+ String host, boolean secureLink) {
+ // Use a separate list to handle cookies that need to be removed so
+ // that there is no conflict with iterators.
+ ArrayList toRemove = new ArrayList<>();
+ for (Map.Entry> entry : cookieIndex.entrySet()) {
+ String domain = entry.getKey();
+ List lst = entry.getValue();
+ for (HttpCookie c : lst) {
+ if ((c.getVersion() == 0 && netscapeDomainMatches(domain, host)) ||
+ (c.getVersion() == 1 && HttpCookie.domainMatches(domain, host))) {
+ if ((cookieJar.indexOf(c) != -1)) {
+ // the cookie still in main cookie store
+ if (!c.hasExpired()) {
+ // don't add twice and make sure it's the proper
+ // security level
+ if ((secureLink || !c.getSecure()) &&
+ !cookies.contains(c)) {
+ cookies.add(c);
+ }
+ } else {
+ toRemove.add(c);
+ }
+ } else {
+ // the cookie has been removed from main store,
+ // so also remove it from domain indexed store
+ toRemove.add(c);
+ }
+ }
+ }
+ // Clear up the cookies that need to be removed
+ for (HttpCookie c : toRemove) {
+ lst.remove(c);
+ cookieJar.remove(c);
+
+ }
+ toRemove.clear();
+ }
+ }
+
+ // @param cookies [OUT] contains the found cookies
+ // @param cookieIndex the index
+ // @param comparator the prediction to decide whether or not
+ // a cookie in index should be returned
+ private void getInternal2(List cookies,
+ Map> cookieIndex,
+ Comparable comparator, boolean secureLink) {
+ for (T index : cookieIndex.keySet()) {
+ if (comparator.compareTo(index) == 0) {
+ List indexedCookies = cookieIndex.get(index);
+ // check the list of cookies associated with this domain
+ if (indexedCookies != null) {
+ Iterator it = indexedCookies.iterator();
+ while (it.hasNext()) {
+ HttpCookie ck = it.next();
+ if (cookieJar.indexOf(ck) != -1) {
+ // the cookie still in main cookie store
+ if (!ck.hasExpired()) {
+ // don't add twice
+ if ((secureLink || !ck.getSecure()) &&
+ !cookies.contains(ck))
+ cookies.add(ck);
+ } else {
+ it.remove();
+ cookieJar.remove(ck);
+ }
+ } else {
+ // the cookie has been removed from main store,
+ // so also remove it from domain indexed store
+ it.remove();
+ }
+ }
+ } // end of indexedCookies != null
+ } // end of comparator.compareTo(index) == 0
+ } // end of cookieIndex iteration
+ }
+
+ // add 'cookie' indexed by 'index' into 'indexStore'
+ private void addIndex(Map> indexStore,
+ T index,
+ HttpCookie cookie) {
+ if (index != null) {
+ List cookies = indexStore.get(index);
+ if (cookies != null) {
+ // there may already have the same cookie, so remove it first
+ cookies.remove(cookie);
+
+ cookies.add(cookie);
+ } else {
+ cookies = new ArrayList<>();
+ cookies.add(cookie);
+ indexStore.put(index, cookies);
+ }
+ }
+ }
+
+
+ //
+ // for cookie purpose, the effective uri should only be http://host
+ // the path will be taken into account when path-match algorithm applied
+ //
+ private URI getEffectiveURI(URI uri) {
+ URI effectiveURI = null;
+ try {
+ effectiveURI = new URI("http",
+ uri.getHost(),
+ null, // path component
+ null, // query component
+ null // fragment component
+ );
+ } catch (URISyntaxException ignored) {
+ effectiveURI = uri;
+ }
+
+ return effectiveURI;
+ }
+}
diff --git a/src/main/java/com/ecep/contract/manager/MyProperties.java b/src/main/java/com/ecep/contract/manager/MyProperties.java
new file mode 100644
index 0000000..6447617
--- /dev/null
+++ b/src/main/java/com/ecep/contract/manager/MyProperties.java
@@ -0,0 +1,35 @@
+package com.ecep.contract.manager;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+
+import java.io.File;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+@Component
+@ConfigurationProperties(prefix = "my")
+public class MyProperties {
+ @Getter
+ @Setter
+ private String downloadsPath;
+
+
+ /**
+ * 尝试返回当前用户的下载文件夹
+ */
+ public File getDownloadDirectory() {
+ String downloadsPath = getDownloadsPath();
+ if (StringUtils.hasText(downloadsPath)) {
+ return new File(downloadsPath);
+ }
+
+ // 没有配置下载目录时,尝试使用默认设置
+ String home = System.getProperty("user.home");
+ Path path = Paths.get(home, "Downloads");
+ return path.toFile();
+ }
+}
diff --git a/src/main/java/com/ecep/contract/manager/SpringApp.java b/src/main/java/com/ecep/contract/manager/SpringApp.java
new file mode 100644
index 0000000..1c619a5
--- /dev/null
+++ b/src/main/java/com/ecep/contract/manager/SpringApp.java
@@ -0,0 +1,259 @@
+package com.ecep.contract.manager;
+
+import com.ecep.contract.manager.cloud.CloudRepositoriesConfig;
+import com.ecep.contract.manager.ds.DsRepositoriesConfig;
+import com.ecep.contract.manager.ui.MessageHolder;
+import com.ecep.contract.manager.util.MyDateTimeUtils;
+import com.ecep.contract.manager.util.UITools;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
+import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
+import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
+import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
+import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
+import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.BeansException;
+import org.springframework.boot.ConfigurableBootstrapContext;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.SpringApplicationHook;
+import org.springframework.boot.SpringApplicationRunListener;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.context.metrics.buffering.BufferingApplicationStartup;
+import org.springframework.boot.context.metrics.buffering.StartupTimeline;
+import org.springframework.cache.CacheManager;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.cache.caffeine.CaffeineCacheManager;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.event.ContextClosedEvent;
+import org.springframework.context.event.EventListener;
+import org.springframework.core.env.ConfigurableEnvironment;
+import org.springframework.core.env.PropertiesPropertySource;
+import org.springframework.core.env.PropertySource;
+import org.springframework.core.metrics.StartupStep;
+import org.springframework.scheduling.annotation.EnableAsync;
+import org.springframework.scheduling.annotation.EnableScheduling;
+
+import java.time.*;
+import java.time.format.DateTimeFormatter;
+import java.util.List;
+import java.util.Locale;
+import java.util.Properties;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executors;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.ScheduledExecutorService;
+
+@SpringBootApplication
+@EnableScheduling
+@EnableAsync
+@EnableCaching
+public class SpringApp {
+ private static final Logger logger = LoggerFactory.getLogger(SpringApp.class);
+
+ static SpringApplication application;
+ static ConfigurableApplicationContext context;
+
+ public static T getBean(Class requiredType) throws BeansException {
+ return context.getBean(requiredType);
+ }
+
+ public static void launch(Properties properties, MessageHolder holder) {
+ application = new SpringApplication(SpringApp.class);
+ BufferingApplicationStartup startup = new BufferingApplicationStartup(2000);
+ application.setApplicationStartup(startup);
+ //
+ holder.debug("应用程序环境准备中...");
+ SpringApplication.withHook(new Hook(holder), () -> {
+ // 动态地注册或修改这些组件和配置
+ application.addBootstrapRegistryInitializer(registry -> {
+ //
+ System.out.println("registry = " + registry);
+
+ });
+ application.addListeners(event -> {
+ logger.debug("SpringApp.launch ApplicationListener, event:{}", event);
+ });
+
+ application.addInitializers(app -> {
+ logger.debug("SpringApp.launch ApplicationContextInitializer");
+ ConfigurableEnvironment environment = app.getEnvironment();
+ logger.debug("environment = {}", environment);
+ PropertySource> dynamicProperties = environment.getPropertySources().get("dynamicProperties");
+ if (dynamicProperties != null) {
+ logger.debug("dynamicProperties = {}", dynamicProperties);
+ }
+ environment.getPropertySources().addLast(new PropertiesPropertySource("dynamicProperties", properties));
+ // app.getBeanFactory().registerSingleton("dataSource", dataSource());
+ logger.debug("app = {}", app);
+
+ if (app instanceof AnnotationConfigApplicationContext ctx) {
+ ctx.register(DsRepositoriesConfig.class);
+ ctx.register(CloudRepositoriesConfig.class);
+ }
+ });
+
+
+ startup.start("");
+ context = application.run();
+ logger.debug("SpringApp.launch application.run().");
+ Duration between = Duration.between(startup.getBufferedTimeline().getStartTime(), Instant.now());
+ holder.info("应用程序环境加载完成... " + between);
+ });
+ CompletableFuture.runAsync(() -> {
+ // 在这里调用 startup 性能分析
+ analyzeStartupPerformance(startup);
+ });
+ }
+
+ /**
+ * 分析启动性能数据并输出到日志
+ */
+ private static void analyzeStartupPerformance(BufferingApplicationStartup startup) {
+ // 获取所有记录的事件
+ StartupTimeline timeline = startup.getBufferedTimeline();
+ if (timeline == null || timeline.getEvents().isEmpty()) {
+ logger.warn("StartupTimeline 为空或没有事件!");
+ return;
+ }
+ logger.info("总共有 {} 个事件", timeline.getEvents().size());
+
+ // 找出与 Bean 初始化相关的步骤
+ timeline.getEvents().stream()
+ .filter(event -> event.getStartupStep().getName().startsWith("spring.beans."))
+ .sorted((a, b) -> Long.compare(b.getDuration().toMillis(), a.getDuration().toMillis()))
+ .limit(30)
+ .forEach(event -> {
+ String name = event.getStartupStep().getName();
+ long duration = event.getDuration().toMillis();
+ logger.info("Bean 初始化阶段: {} - 耗时: {} ms", name, duration);
+
+ for (StartupStep.Tag tag : event.getStartupStep().getTags()) {
+ if ("beanName".equals(tag.getKey())) {
+ logger.info(" └── Bean 名称: {}", tag.getValue());
+ }
+ }
+ });
+ }
+
+ public static String getMessage(String code, Object[] args, Locale locale) {
+ return context.getMessage(code, args, locale);
+ }
+
+ public static void shutdown() {
+ System.out.println("SpringApp.shutdown");
+ if (logger.isDebugEnabled()) {
+ logger.debug("shutdown");
+ }
+ if (context != null) {
+ if (context.isRunning()) {
+ context.close();
+ }
+ }
+ }
+
+ public static boolean isRunning() {
+ return context != null && context.isRunning();
+ }
+
+ static class Hook implements SpringApplicationHook, SpringApplicationRunListener {
+ MessageHolder holder;
+
+ Hook(MessageHolder holder) {
+ this.holder = holder;
+ }
+
+ public void debug(String msg) {
+ holder.debug(msg);
+ }
+
+ @Override
+ public void starting(ConfigurableBootstrapContext bootstrapContext) {
+ logger.debug("Desktop.starting");
+ debug("Spring Application 启动中...");
+ }
+
+ @Override
+ public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {
+ logger.debug("Desktop.environmentPrepared");
+ debug("初始化 Environment 中,请稍后...");
+ }
+
+ @Override
+ public void contextPrepared(ConfigurableApplicationContext context) {
+ logger.debug("Desktop.contextPrepared");
+ debug("Spring Application Context 预处理中,请稍后...");
+ }
+
+ @Override
+ public void contextLoaded(ConfigurableApplicationContext context) {
+ logger.debug("Desktop.contextLoaded");
+ debug("Spring Application Context 初始化完毕,请稍后...");
+ }
+
+
+ @Override
+ public void started(ConfigurableApplicationContext context, Duration timeTaken) {
+ logger.debug("Desktop.started");
+ debug("Spring Application 启动完毕.");
+ }
+
+ @Override
+ public void ready(ConfigurableApplicationContext context, Duration timeTaken) {
+ logger.debug("Desktop.ready");
+ debug("Spring Application ready.");
+ }
+
+ @Override
+ public void failed(ConfigurableApplicationContext context, Throwable exception) {
+ logger.error("Desktop.failed", exception);
+ holder.error("Spring Application 启动失败(" + exception.getMessage() + ").");
+ UITools.showExceptionAndWait("启动失败", exception);
+ }
+
+ @Override
+ public SpringApplicationRunListener getRunListener(SpringApplication springApplication) {
+ return this;
+ }
+ }
+
+ @EventListener
+ public void handleClosedEvent(ContextClosedEvent event) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("handleClosedEvent={}", event);
+ }
+ Desktop.shutdown();
+ }
+
+ @Bean
+ public CacheManager cacheManager() {
+// return new ConcurrentMapCacheManager("myCache");
+ CaffeineCacheManager cacheManager = new CaffeineCacheManager();
+ cacheManager.setAsyncCacheMode(true);
+ return cacheManager;
+ }
+
+ @Bean
+ public ObjectMapper objectMapper() {
+ ObjectMapper objectMapper = new ObjectMapper();
+ JavaTimeModule javaTimeModule = new JavaTimeModule();
+ javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ISO_LOCAL_DATE));
+ javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(MyDateTimeUtils.DEFAULT_DATETIME_FORMAT_PATTERN)));
+ javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
+ javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ISO_LOCAL_DATE));
+ javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(MyDateTimeUtils.DEFAULT_DATETIME_FORMAT_PATTERN)));
+ javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ISO_LOCAL_TIME));
+ objectMapper.registerModule(javaTimeModule);
+ return objectMapper;
+ }
+
+ @Bean
+ public ScheduledExecutorService scheduledExecutorService() {
+ return Executors.newScheduledThreadPool(3);
+ }
+
+}
diff --git a/src/main/java/com/ecep/contract/manager/cloud/AbstractCtx.java b/src/main/java/com/ecep/contract/manager/cloud/AbstractCtx.java
new file mode 100644
index 0000000..bf24c5f
--- /dev/null
+++ b/src/main/java/com/ecep/contract/manager/cloud/AbstractCtx.java
@@ -0,0 +1,173 @@
+package com.ecep.contract.manager.cloud;
+
+import com.ecep.contract.manager.ds.other.service.SysConfService;
+import com.ecep.contract.manager.ui.MessageHolder;
+import com.ecep.contract.manager.util.MyStringUtils;
+import com.ecep.contract.manager.util.NumberUtils;
+import lombok.Getter;
+import lombok.Setter;
+import org.springframework.util.StringUtils;
+
+import java.math.BigDecimal;
+import java.sql.Timestamp;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeParseException;
+import java.util.Locale;
+import java.util.Objects;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+import static com.ecep.contract.manager.SpringApp.getBean;
+
+public class AbstractCtx {
+ @Setter
+ SysConfService confService;
+
+ @Setter
+ @Getter
+ Locale locale = Locale.getDefault();
+
+ public SysConfService getConfService() {
+ if (confService == null) {
+ confService = getBean(SysConfService.class);
+ }
+ return confService;
+ }
+
+
+ public boolean updateText(Supplier getter, Consumer setter, String text, MessageHolder holder, String topic) {
+ if (!Objects.equals(getter.get(), text)) {
+ setter.accept(text);
+ holder.info(topic + "修改为: " + text);
+ return true;
+ }
+ return false;
+ }
+
+ public boolean updateAppendText(Supplier getter, Consumer setter, String text, MessageHolder holder, String topic) {
+ if (StringUtils.hasText(text)) {
+ String str = MyStringUtils.appendIfAbsent(getter.get(), text);
+ if (!Objects.equals(getter.get(), str)) {
+ setter.accept(text);
+ holder.info(topic + "修改为: " + text);
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+ public boolean updateLocalDate(Supplier getter, Consumer setter, java.sql.Date date, MessageHolder holder, String topic) {
+ if (date != null) {
+ return updateLocalDate(getter, setter, date.toLocalDate(), holder, topic);
+ }
+ return false;
+ }
+
+ public boolean updateLocalDate(Supplier getter, Consumer setter, LocalDate date, MessageHolder holder, String topic) {
+ return updateLocalDate(getter, setter, date, holder, topic, false);
+ }
+
+ public boolean updateLocalDate(Supplier getter, Consumer setter, LocalDate date, MessageHolder holder, String topic, boolean allowNull) {
+ if (date == null && !allowNull) {
+ return false;
+ }
+ if (!Objects.equals(getter.get(), date)) {
+ setter.accept(date);
+ holder.info(topic + "更新为 " + date);
+ return true;
+ }
+ return false;
+ }
+
+ public boolean updateLocalDate(Supplier getter, Consumer setter, String strDate, MessageHolder holder, String topic) {
+ LocalDate date = null;
+ if (StringUtils.hasText(strDate)) {
+ try {
+ date = LocalDate.parse(strDate);
+ } catch (DateTimeParseException e) {
+ holder.warn("无法解析的日期:" + strDate);
+ }
+ }
+ return updateLocalDate(getter, setter, date, holder, topic);
+ }
+
+ public boolean updateLocalDate(Supplier getter, Consumer setter, Timestamp timestamp, MessageHolder holder, String topic) {
+ LocalDate date = null;
+
+ if (timestamp != null) {
+ try {
+ date = timestamp.toLocalDateTime().toLocalDate();
+ } catch (DateTimeParseException e) {
+ holder.warn("解析日期" + timestamp + " 异常:" + e.getMessage());
+ }
+ }
+ return updateLocalDate(getter, setter, date, holder, topic);
+ }
+
+ public boolean updateLocalDateTime(Supplier getter, Consumer setter, Timestamp timestamp, MessageHolder holder, String topic) {
+ LocalDateTime dateTime = null;
+
+ if (timestamp != null) {
+ try {
+ // fixed nanos
+ timestamp.setNanos(0);
+ dateTime = timestamp.toLocalDateTime();
+ } catch (DateTimeParseException e) {
+ holder.warn("解析日期" + timestamp + " 异常:" + e.getMessage());
+ }
+ }
+
+ if (!Objects.equals(getter.get(), dateTime)) {
+ setter.accept(dateTime);
+ holder.info(topic + "修改为: " + dateTime);
+ return true;
+ }
+ return false;
+ }
+
+ public boolean updateInstant(Supplier getter, Consumer setter, Instant instant, MessageHolder holder, String topic) {
+ if (!Objects.equals(getter.get(), instant)) {
+ setter.accept(instant);
+ holder.info(topic + "修改为: " + instant);
+ return true;
+ }
+ return false;
+ }
+
+
+ public boolean updateNumber(Supplier getter, Consumer setter, BigDecimal value, MessageHolder holder, String topic) {
+ double val = value.doubleValue();
+ return updateNumber(getter, setter, val, holder, topic);
+ }
+
+ public boolean updateNumber(Supplier getter, Consumer setter, double value, MessageHolder holder, String topic) {
+ if (getter.get() == null || !NumberUtils.equals(getter.get(), value)) {
+ setter.accept(value);
+ holder.info(topic + "修改为: " + value);
+ return true;
+ }
+ return false;
+ }
+
+ public boolean updateNumber(Supplier getter, Consumer setter, float value, MessageHolder holder, String topic) {
+ if (getter.get() == null || !NumberUtils.equals(getter.get(), value)) {
+ setter.accept(value);
+ holder.info(topic + "修改为: " + value);
+ return true;
+ }
+ return false;
+ }
+
+
+ public boolean updateNumber(Supplier getter, Consumer setter, Integer value, MessageHolder holder, String topic) {
+ if (getter.get() == null || !NumberUtils.equals(getter.get(), value)) {
+ setter.accept(value);
+ holder.info(topic + "修改为: " + value);
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/src/main/java/com/ecep/contract/manager/cloud/CloudBaseInfo.java b/src/main/java/com/ecep/contract/manager/cloud/CloudBaseInfo.java
new file mode 100644
index 0000000..9c67b9b
--- /dev/null
+++ b/src/main/java/com/ecep/contract/manager/cloud/CloudBaseInfo.java
@@ -0,0 +1,73 @@
+package com.ecep.contract.manager.cloud;
+
+import com.ecep.contract.manager.ds.company.model.Company;
+import com.ecep.contract.manager.ds.other.model.IdentityEntity;
+import jakarta.persistence.*;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.ToString;
+import org.hibernate.annotations.ColumnDefault;
+import org.hibernate.proxy.HibernateProxy;
+
+import java.time.Instant;
+import java.util.Objects;
+
+/**
+ * 记录同步来源
+ */
+@Getter
+@Setter
+// @Entity
+// @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
+@ToString
+@MappedSuperclass
+public abstract class CloudBaseInfo implements IdentityEntity {
+ /**
+ * 主键
+ */
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Column(name = "ID", nullable = false)
+ private Integer id;
+
+ /**
+ * 平台编号
+ */
+ @Column(name = "CLOUD_ID")
+ private String cloudId;
+
+ /**
+ * 本地更新时间戳,控制更新频率和重复更新
+ */
+ @Column(name = "LATEST_UPDATE")
+ private Instant latestUpdate;
+
+ /**
+ * 关联的公司
+ */
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "COMPANY_ID")
+ @ToString.Exclude
+ private Company company;
+
+ @Version
+ @ColumnDefault("0")
+ @Column(name = "VERSION", nullable = false)
+ private int version;
+
+ @Override
+ public final boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null) return false;
+ Class> oEffectiveClass = o instanceof HibernateProxy ? ((HibernateProxy) o).getHibernateLazyInitializer().getPersistentClass() : o.getClass();
+ Class> thisEffectiveClass = this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass() : this.getClass();
+ if (thisEffectiveClass != oEffectiveClass) return false;
+ CloudBaseInfo cloudInfo = (CloudBaseInfo) o;
+ return getId() != null && Objects.equals(getId(), cloudInfo.getId());
+ }
+
+ @Override
+ public final int hashCode() {
+ return this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass().hashCode() : getClass().hashCode();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/ecep/contract/manager/cloud/CloudInfo.java b/src/main/java/com/ecep/contract/manager/cloud/CloudInfo.java
new file mode 100644
index 0000000..4a1c92c
--- /dev/null
+++ b/src/main/java/com/ecep/contract/manager/cloud/CloudInfo.java
@@ -0,0 +1,78 @@
+package com.ecep.contract.manager.cloud;
+
+import com.ecep.contract.manager.ds.company.model.Company;
+import jakarta.persistence.*;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.ToString;
+import org.hibernate.annotations.ColumnDefault;
+import org.hibernate.proxy.HibernateProxy;
+
+import java.time.Instant;
+import java.util.Objects;
+
+/**
+ * 记录同步来源
+ */
+@Getter
+@Setter
+@Entity
+//@org.springframework.data.relational.core.mapping.Table(name = "CLOUD_INFO")
+@Table(name = "CLOUD_INFO", schema = "supplier_ms")
+@ToString
+public class CloudInfo {
+ /**
+ * 主键
+ */
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Column(name = "ID", nullable = false)
+ private Integer id;
+
+ /**
+ * 记录源类型
+ */
+ @Column(name = "CLOUD_TYPE", length = 50)
+ private CloudType type;
+
+ /**
+ *
+ */
+ @Column(name = "CLOUD_ID")
+ private String cloudId;
+
+ /**
+ * 本地更新时间戳,控制更新频率和重复更新
+ */
+ @Column(name = "LATEST_UPDATE")
+ private Instant latestUpdate;
+
+ /**
+ * 关联的公司
+ */
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "COMPANY_ID")
+ @ToString.Exclude
+ private Company company;
+
+ @Version
+ @ColumnDefault("0")
+ @Column(name = "VERSION", nullable = false)
+ private int version;
+
+ @Override
+ public final boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null) return false;
+ Class> oEffectiveClass = o instanceof HibernateProxy ? ((HibernateProxy) o).getHibernateLazyInitializer().getPersistentClass() : o.getClass();
+ Class> thisEffectiveClass = this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass() : this.getClass();
+ if (thisEffectiveClass != oEffectiveClass) return false;
+ CloudInfo cloudInfo = (CloudInfo) o;
+ return getId() != null && Objects.equals(getId(), cloudInfo.getId());
+ }
+
+ @Override
+ public final int hashCode() {
+ return this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass().hashCode() : getClass().hashCode();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/ecep/contract/manager/cloud/CloudInfoRepository.java b/src/main/java/com/ecep/contract/manager/cloud/CloudInfoRepository.java
new file mode 100644
index 0000000..01b30f8
--- /dev/null
+++ b/src/main/java/com/ecep/contract/manager/cloud/CloudInfoRepository.java
@@ -0,0 +1,15 @@
+package com.ecep.contract.manager.cloud;
+
+import org.springframework.context.annotation.Lazy;
+import org.springframework.data.repository.CrudRepository;
+import org.springframework.data.repository.PagingAndSortingRepository;
+import org.springframework.stereotype.Repository;
+
+@Lazy
+@Repository
+public interface CloudInfoRepository
+ // curd
+ extends CrudRepository, PagingAndSortingRepository {
+
+
+}
diff --git a/src/main/java/com/ecep/contract/manager/cloud/CloudInfoViewModel.java b/src/main/java/com/ecep/contract/manager/cloud/CloudInfoViewModel.java
new file mode 100644
index 0000000..d9f9337
--- /dev/null
+++ b/src/main/java/com/ecep/contract/manager/cloud/CloudInfoViewModel.java
@@ -0,0 +1,79 @@
+package com.ecep.contract.manager.cloud;
+
+import com.ecep.contract.manager.ds.company.model.Company;
+import com.ecep.contract.manager.ds.other.vo.IdentityViewModel;
+import javafx.beans.property.SimpleIntegerProperty;
+import javafx.beans.property.SimpleObjectProperty;
+import javafx.beans.property.SimpleStringProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.time.*;
+import java.util.Objects;
+
+/**
+ * 云信息
+ */
+@EqualsAndHashCode(callSuper = false)
+@Data
+public class CloudInfoViewModel extends IdentityViewModel {
+
+ /**
+ * 云端Id
+ */
+ private SimpleStringProperty cloudId = new SimpleStringProperty();
+ /**
+ * 公司
+ */
+ private SimpleObjectProperty company = new SimpleObjectProperty<>();
+ /**
+ * 最后更新日期
+ */
+ private SimpleObjectProperty latest = new SimpleObjectProperty<>();
+ /**
+ * Version
+ */
+ private SimpleIntegerProperty version = new SimpleIntegerProperty();
+
+ @Override
+ protected void updateFrom(V info) {
+ super.updateFrom(info);
+ cloudId.set(info.getCloudId());
+ company.set(info.getCompany());
+
+ if (info.getLatestUpdate() != null) {
+ ZoneId zone = ZoneId.systemDefault();
+ ZonedDateTime zonedDateTime = info.getLatestUpdate().atZone(zone);
+ LocalDateTime localDateTime = zonedDateTime.toLocalDateTime();
+ latest.set(localDateTime);
+ } else {
+ latest.set(null);
+ }
+ version.set(info.getVersion());
+ }
+
+ @Override
+ public boolean copyTo(V info) {
+ boolean changed = super.copyTo(info);
+
+ if (!Objects.equals(cloudId.get(), info.getCloudId())) {
+ info.setCloudId(cloudId.get());
+ changed = true;
+ }
+
+ Instant latestUpdate = null;
+ LocalDateTime latestUpdateDateTime = latest.get();
+ if (latestUpdateDateTime != null) {
+ latestUpdate = latestUpdateDateTime.toInstant(ZoneOffset.ofHours(8));
+ }
+ if (!Objects.equals(latestUpdate, info.getLatestUpdate())) {
+ info.setLatestUpdate(latestUpdate);
+ changed = true;
+ }
+ return changed;
+ }
+
+
+}
+
+
diff --git a/src/main/java/com/ecep/contract/manager/cloud/CloudRepositoriesConfig.java b/src/main/java/com/ecep/contract/manager/cloud/CloudRepositoriesConfig.java
new file mode 100644
index 0000000..a8f01a1
--- /dev/null
+++ b/src/main/java/com/ecep/contract/manager/cloud/CloudRepositoriesConfig.java
@@ -0,0 +1,45 @@
+package com.ecep.contract.manager.cloud;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
+import org.springframework.data.repository.config.BootstrapMode;
+
+@Configuration
+@EnableJpaRepositories(bootstrapMode = BootstrapMode.LAZY)
+public class CloudRepositoriesConfig {
+// @Bean
+// @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
+// public BeanDefinition cloudInfoRepositoryDefinition() {
+// BeanDefinition definition = new org.springframework.beans.factory.support.GenericBeanDefinition();
+// definition.setBeanClassName("com.ecep.contract.manager.cloud.CloudInfoRepository");
+// definition.setLazyInit(true);
+// return definition;
+// }
+//
+// @Bean
+// @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
+// public BeanDefinition cloudRkRepositoryDefinition() {
+// BeanDefinition definition = new org.springframework.beans.factory.support.GenericBeanDefinition();
+// definition.setBeanClassName("com.ecep.contract.manager.cloud.u8.CloudRkRepository");
+// definition.setLazyInit(true);
+// return definition;
+// }
+//
+// @Bean
+// @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
+// public BeanDefinition cloudTycRepositoryDefinition() {
+// BeanDefinition definition = new org.springframework.beans.factory.support.GenericBeanDefinition();
+// definition.setBeanClassName("com.ecep.contract.manager.cloud.u8.CloudTycRepository");
+// definition.setLazyInit(true);
+// return definition;
+// }
+//
+// @Bean
+// @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
+// public BeanDefinition cloudYuRepositoryDefinition() {
+// BeanDefinition definition = new org.springframework.beans.factory.support.GenericBeanDefinition();
+// definition.setBeanClassName("com.ecep.contract.manager.cloud.u8.CloudYuRepository");
+// definition.setLazyInit(true);
+// return definition;
+// }
+}
diff --git a/src/main/java/com/ecep/contract/manager/cloud/CloudType.java b/src/main/java/com/ecep/contract/manager/cloud/CloudType.java
new file mode 100644
index 0000000..135d2d3
--- /dev/null
+++ b/src/main/java/com/ecep/contract/manager/cloud/CloudType.java
@@ -0,0 +1,23 @@
+package com.ecep.contract.manager.cloud;
+
+/**
+ * 记录源类型
+ */
+public enum CloudType {
+ /**
+ * 上海电气集团相关方平台
+ */
+ RK,
+ /**
+ * 天眼查
+ */
+ TYC,
+ /**
+ * 用友U8
+ */
+ U8,
+ /**
+ * 老版本APP
+ */
+ OLD;
+}
diff --git a/src/main/java/com/ecep/contract/manager/cloud/old/CompanyContactUtils.java b/src/main/java/com/ecep/contract/manager/cloud/old/CompanyContactUtils.java
new file mode 100644
index 0000000..4262f78
--- /dev/null
+++ b/src/main/java/com/ecep/contract/manager/cloud/old/CompanyContactUtils.java
@@ -0,0 +1,66 @@
+package com.ecep.contract.manager.cloud.old;
+
+import com.ecep.contract.manager.ds.company.model.CompanyContact;
+import com.ecep.contract.manager.ds.company.model.CompanyContract;
+
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ *
+ */
+public class CompanyContactUtils {
+ /**
+ * 使用 map 应用到 contact, 如有更新, 则返回 true
+ */
+ public static boolean applyContactByMap(CompanyContact contact, Map map) {
+ boolean modified = false;
+ String name = (String) map.get("NAME");
+ String phone = (String) map.get("PHONE");
+ String email = (String) map.get("EMAIL");
+ String address = (String) map.get("ADDRESS");
+ if (name != null) {
+ // 更新备注
+ if (!Objects.equals(contact.getName(), name)) {
+ contact.setName(name);
+ modified = true;
+ }
+ }
+ if (phone != null) {
+ // 更新备注
+ if (!Objects.equals(contact.getPhone(), phone)) {
+ contact.setPhone(phone);
+ modified = true;
+ }
+ }
+ if (email != null) {
+ // 更新备注
+ if (!Objects.equals(contact.getEmail(), email)) {
+ contact.setEmail(email);
+ modified = true;
+ }
+ }
+ if (address != null) {
+ // 更新备注
+ if (!Objects.equals(contact.getAddress(), address)) {
+ contact.setAddress(address);
+ modified = true;
+ }
+ }
+ return modified;
+ }
+
+ /**
+ * 要和{@link CompanyContract#getContactKey(CompanyContact)}一致
+ */
+ public static String getContactKey(Map map) {
+ if (map == null) {
+ return null;
+ }
+ String name = (String) map.get("NAME");
+ String phone = (String) map.get("PHONE");
+ String email = (String) map.get("EMAIL");
+ return name + phone + email;
+ }
+
+}
diff --git a/src/main/java/com/ecep/contract/manager/cloud/old/CompanyCustomerFileUtils.java b/src/main/java/com/ecep/contract/manager/cloud/old/CompanyCustomerFileUtils.java
new file mode 100644
index 0000000..c85975d
--- /dev/null
+++ b/src/main/java/com/ecep/contract/manager/cloud/old/CompanyCustomerFileUtils.java
@@ -0,0 +1,53 @@
+package com.ecep.contract.manager.cloud.old;
+
+import com.ecep.contract.manager.ds.customer.model.CompanyCustomerFile;
+
+import java.time.LocalDate;
+import java.util.Map;
+import java.util.Objects;
+
+public class CompanyCustomerFileUtils {
+ public static boolean applyCustomerFileByMap(CompanyCustomerFile customerFile, Map m) {
+ boolean modified = false;
+
+ String filePath = (String) m.get("FILE_PATH");
+ String editFilePath = (String) m.get("EDIT_FILE_PATH");
+ java.sql.Date signDate = (java.sql.Date) m.get("SIGN_DATE");
+ Boolean valid = (Boolean) m.get("VALID");
+
+ if (filePath != null) {
+ // file path
+ if (!Objects.equals(customerFile.getFilePath(), filePath)) {
+ customerFile.setFilePath(filePath);
+ modified = true;
+ }
+ }
+ if (editFilePath != null) {
+ // edit file path
+ if (!Objects.equals(customerFile.getEditFilePath(), editFilePath)) {
+ customerFile.setEditFilePath(editFilePath);
+ modified = true;
+ }
+ }
+ if (signDate != null) {
+ // date
+ LocalDate localDate = signDate.toLocalDate();
+ if (!Objects.equals(customerFile.getSignDate(), localDate)) {
+ customerFile.setSignDate(localDate);
+ modified = true;
+ }
+ }
+ if (valid != null) {
+ // valid
+ if (!Objects.equals(customerFile.isValid(), valid)) {
+ customerFile.setValid(valid);
+ modified = true;
+ }
+ }
+ return modified;
+ }
+
+ public static String getCustomerFileKey(Map m) {
+ return m.get("FILE_PATH").toString();
+ }
+}
diff --git a/src/main/java/com/ecep/contract/manager/cloud/old/CompanyVendorFileUtils.java b/src/main/java/com/ecep/contract/manager/cloud/old/CompanyVendorFileUtils.java
new file mode 100644
index 0000000..4fcb21e
--- /dev/null
+++ b/src/main/java/com/ecep/contract/manager/cloud/old/CompanyVendorFileUtils.java
@@ -0,0 +1,53 @@
+package com.ecep.contract.manager.cloud.old;
+
+import com.ecep.contract.manager.ds.vendor.model.CompanyVendorFile;
+
+import java.time.LocalDate;
+import java.util.Map;
+import java.util.Objects;
+
+public class CompanyVendorFileUtils {
+ public static boolean applyVendorFileByMap(CompanyVendorFile vendorFile, Map m) {
+ boolean modified = false;
+
+ String filePath = (String) m.get("FILE_PATH");
+ String editFilePath = (String) m.get("EDIT_FILE_PATH");
+ java.sql.Date signDate = (java.sql.Date) m.get("SIGN_DATE");
+ Boolean valid = (Boolean) m.get("VALID");
+
+ if (filePath != null) {
+ // file path
+ if (!Objects.equals(vendorFile.getFilePath(), filePath)) {
+ vendorFile.setFilePath(filePath);
+ modified = true;
+ }
+ }
+ if (editFilePath != null) {
+ // edit file path
+ if (!Objects.equals(vendorFile.getEditFilePath(), editFilePath)) {
+ vendorFile.setEditFilePath(editFilePath);
+ modified = true;
+ }
+ }
+ if (signDate != null) {
+ // date
+ LocalDate localDate = signDate.toLocalDate();
+ if (!Objects.equals(vendorFile.getSignDate(), localDate)) {
+ vendorFile.setSignDate(localDate);
+ modified = true;
+ }
+ }
+ if (valid != null) {
+ // valid
+ if (!Objects.equals(vendorFile.isValid(), valid)) {
+ vendorFile.setValid(valid);
+ modified = true;
+ }
+ }
+ return modified;
+ }
+
+ public static String getVendorFileKey(Map m) {
+ return m.get("FILE_PATH").toString();
+ }
+}
diff --git a/src/main/java/com/ecep/contract/manager/cloud/old/OldVersionService.java b/src/main/java/com/ecep/contract/manager/cloud/old/OldVersionService.java
new file mode 100644
index 0000000..85d459b
--- /dev/null
+++ b/src/main/java/com/ecep/contract/manager/cloud/old/OldVersionService.java
@@ -0,0 +1,1126 @@
+package com.ecep.contract.manager.cloud.old;
+
+import com.ecep.contract.manager.SpringApp;
+import com.ecep.contract.manager.cloud.rk.CloudRk;
+import com.ecep.contract.manager.cloud.rk.CloudRkService;
+import com.ecep.contract.manager.cloud.tyc.CloudTyc;
+import com.ecep.contract.manager.cloud.tyc.CloudTycService;
+import com.ecep.contract.manager.ds.company.CompanyFileType;
+import com.ecep.contract.manager.ds.company.model.*;
+import com.ecep.contract.manager.ds.company.repository.CompanyContactRepository;
+import com.ecep.contract.manager.ds.company.repository.CompanyOldNameRepository;
+import com.ecep.contract.manager.ds.contract.ContractFileType;
+import com.ecep.contract.manager.ds.other.repository.SysConfRepository;
+import com.ecep.contract.manager.ds.company.repository.CompanyFileRepository;
+import com.ecep.contract.manager.ds.contract.model.Contract;
+import com.ecep.contract.manager.ds.contract.model.ContractFile;
+import com.ecep.contract.manager.ds.contract.repository.ContractFileRepository;
+import com.ecep.contract.manager.ds.contract.repository.ContractRepository;
+import com.ecep.contract.manager.ds.customer.model.CompanyCustomer;
+import com.ecep.contract.manager.ds.customer.model.CompanyCustomerFile;
+import com.ecep.contract.manager.ds.customer.repository.CompanyCustomerFileRepository;
+import com.ecep.contract.manager.ds.customer.repository.CompanyCustomerRepository;
+import com.ecep.contract.manager.ds.vendor.VendorType;
+import com.ecep.contract.manager.ds.vendor.model.CompanyVendor;
+import com.ecep.contract.manager.ds.vendor.model.CompanyVendorFile;
+import com.ecep.contract.manager.ds.vendor.repository.CompanyVendorRepository;
+import com.ecep.contract.manager.ds.company.service.CompanyService;
+import com.ecep.contract.manager.ds.vendor.service.CompanyVendorFileService;
+import jakarta.transaction.Transactional;
+import javafx.application.Platform;
+import javafx.concurrent.Task;
+import org.controlsfx.control.TaskProgressView;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.jdbc.core.ColumnMapRowMapper;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+
+import java.beans.PropertyDescriptor;
+import java.io.File;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.util.*;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+import java.util.stream.Stream;
+
+/**
+ * 从老板APP的数据库中导入数据
+ *
+ * 数据结构不同
+ */
+@Lazy
+@Service
+public class OldVersionService {
+ private static final Logger logger = LoggerFactory.getLogger(OldVersionService.class);
+ @Autowired
+ private ContractFileRepository contractFileRepository;
+
+
+ public enum Types {
+ VENDOR, CUSTOMER
+ }
+
+ @Autowired
+ private JdbcTemplate jdbcTemplate;
+ @Autowired
+ private SysConfRepository confRepository;
+ @Lazy
+ @Autowired
+ private CompanyService companyService;
+ @Autowired
+ private CloudRkService cloudRkService;
+ @Autowired
+ private CloudTycService cloudTycService;
+ @Autowired
+ private ScheduledExecutorService scheduledExecutorService;
+ @Autowired
+ private CompanyContactRepository companyContactRepository;
+ @Autowired
+ private CompanyCustomerRepository companyCustomerRepository;
+ @Autowired
+ private CompanyVendorRepository companyVendorRepository;
+ @Autowired
+ private CompanyOldNameRepository companyOldNameRepository;
+ @Autowired
+ private ContractRepository contractRepository;
+ @Autowired
+ private CompanyFileRepository companyFileRepository;
+
+ public Long countOfVendor() {
+ return jdbcTemplate.queryForObject("select COUNT(*) from VENDOR;", Long.class);
+ }
+
+ public Stream