Bläddra i källkod

底护板录入

wangxichen 1 vecka sedan
incheckning
6ca83dc3a2
87 ändrade filer med 1640 tillägg och 0 borttagningar
  1. 24 0
      .classpath
  2. 7 0
      .gitignore
  3. 0 0
      .lock
  4. 28 0
      .project
  5. 2 0
      image/.gitignore
  6. BIN
      image/background.png
  7. BIN
      image/bg/a_side.png
  8. BIN
      image/bg/b_side.png
  9. BIN
      image/bg/bar_add.png
  10. BIN
      image/bg/bar_bookmark.png
  11. BIN
      image/bg/bar_cmd_go.png
  12. BIN
      image/bg/bar_edit.png
  13. BIN
      image/bg/bar_pause.png
  14. BIN
      image/bg/bar_sampling.png
  15. BIN
      image/bg/bar_setting.png
  16. BIN
      image/bg/bar_stop.png
  17. BIN
      image/bg/close_bt.png
  18. BIN
      image/bg/company_setting_logo.png
  19. BIN
      image/bg/delete.png
  20. BIN
      image/bg/department_setting_logo.png
  21. BIN
      image/bg/download.png
  22. BIN
      image/bg/equipment_setting_logo.png
  23. BIN
      image/bg/gj_001_1_20.png
  24. BIN
      image/bg/gj_001_21_40.png
  25. BIN
      image/bg/gj_001_41_60.png
  26. BIN
      image/bg/green_dot.png
  27. BIN
      image/bg/grey_dot.png
  28. BIN
      image/bg/icon.png
  29. BIN
      image/bg/inductance_logo.png
  30. BIN
      image/bg/insulation_logo.png
  31. BIN
      image/bg/login.png
  32. BIN
      image/bg/logo.png
  33. BIN
      image/bg/logoff.png
  34. BIN
      image/bg/menu_data_analysis.png
  35. BIN
      image/bg/menu_data_preprocess.png
  36. BIN
      image/bg/menu_file.png
  37. BIN
      image/bg/menu_setting.png
  38. BIN
      image/bg/min_bt.png
  39. BIN
      image/bg/ng_bg.png
  40. BIN
      image/bg/ok_bg.png
  41. BIN
      image/bg/open_file.png
  42. BIN
      image/bg/refresh.png
  43. BIN
      image/bg/reset_logo.png
  44. BIN
      image/bg/resistance_logo.png
  45. BIN
      image/bg/save_bg.png
  46. BIN
      image/bg/scan_barcode.png
  47. BIN
      image/bg/tree_folder_icon.png
  48. BIN
      image/bg/tree_leaf_icon.png
  49. BIN
      image/bg/user.png
  50. BIN
      image/bg/vertical_line.png
  51. BIN
      image/bg/zoom_in.png
  52. BIN
      image/bg/zoom_out.png
  53. BIN
      image/close_bt.png
  54. 0 0
      image/cmd.txt
  55. BIN
      image/icon.ico
  56. BIN
      image/info_bt.png
  57. BIN
      image/min_bt.png
  58. BIN
      lib/commons-codec-1.15.jar
  59. BIN
      lib/fastjson2-2.0.16.jar
  60. BIN
      lib/gson-2.10.jar
  61. BIN
      lib/iot-communication-1.4.4.jar
  62. BIN
      lib/jSerialComm-2.6.2.jar
  63. BIN
      lib/jfreechart-1.5.4.jar
  64. BIN
      lib/jshortcut-0.4-oberzalek.jar
  65. BIN
      lib/kotlin-stdlib-1.8.10.jar
  66. BIN
      lib/logback-classic-1.2.13.jar
  67. BIN
      lib/logback-core-1.2.13.jar
  68. BIN
      lib/netty-all-4.1.48.Final.jar
  69. BIN
      lib/okhttp-4.10.0.jar
  70. BIN
      lib/okio-jvm-3.3.0.jar
  71. BIN
      lib/s7connector-2.1.jar
  72. BIN
      lib/slf4j-api-1.7.36.jar
  73. BIN
      lib/sqlite-jdbc-3.36.0.3.jar
  74. 30 0
      src/com/mes/component/MyDialog.java
  75. 246 0
      src/com/mes/ui/DhbKcUtil.java
  76. 85 0
      src/com/mes/ui/LockUtil.java
  77. 190 0
      src/com/mes/ui/LoginFarme.java
  78. 402 0
      src/com/mes/ui/MesClient.java
  79. 67 0
      src/com/mes/ui/OprnoUtil.java
  80. 81 0
      src/com/mes/ui/ServerRoute.java
  81. 11 0
      src/com/mes/util/Base64Utils.java
  82. 32 0
      src/com/mes/util/DateLocalUtils.java
  83. 49 0
      src/com/mes/util/ErrorMsg.java
  84. 157 0
      src/com/mes/util/HttpUtils.java
  85. 158 0
      src/com/mes/util/JdbcUtils.java
  86. 27 0
      src/resources/config/config.properties
  87. 44 0
      src/resources/logback.xml

+ 24 - 0
.classpath

@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+    <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">
+		<attributes>
+			<attribute name="module" value="true"/>
+		</attributes>
+	</classpathentry>
+    <classpathentry kind="src" path="image"/>
+	<classpathentry excluding="resources/" kind="src" path="src"/>
+	<classpathentry kind="src" path="src/resources"/>
+	<classpathentry kind="output" path="bin"/>
+	<classpathentry kind="lib" path="lib/s7connector-2.1.jar"/>
+	<classpathentry kind="lib" path="lib/slf4j-api-2.0.6.jar"/>
+	<classpathentry kind="lib" path="lib/okhttp-4.10.0.jar"/>
+	<classpathentry kind="lib" path="lib/kotlin-stdlib-1.8.10.jar"/>
+	<classpathentry kind="lib" path="lib/okio-jvm-3.3.0.jar"/>
+	<classpathentry kind="lib" path="lib/gson-2.10.jar"/>
+	<classpathentry kind="lib" path="lib/jfreechart-1.5.4.jar"/>
+	<classpathentry kind="lib" path="lib/jshortcut-0.4-oberzalek.jar"/>
+	<classpathentry kind="lib" path="lib/netty-all-4.1.48.Final.jar"/>
+	<classpathentry kind="lib" path="lib/sqlite-jdbc-3.36.0.3.jar"/>
+	<classpathentry kind="lib" path="lib/commons-codec-1.15.jar"/>
+	<classpathentry kind="lib" path="lib/fastjson2-2.0.16.jar"/>
+</classpath>

+ 7 - 0
.gitignore

@@ -0,0 +1,7 @@
+.idea
+.settings
+classes
+bin
+out
+*.db
+*.iml

+ 0 - 0
.lock


+ 28 - 0
.project

@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>mesclient-sd</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+	<filteredResources>
+		<filter>
+			<id>1756977912238</id>
+			<name></name>
+			<type>30</type>
+			<matcher>
+				<id>org.eclipse.core.resources.regexFilterMatcher</id>
+				<arguments>node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__</arguments>
+			</matcher>
+		</filter>
+	</filteredResources>
+</projectDescription>

+ 2 - 0
image/.gitignore

@@ -0,0 +1,2 @@
+META-INF
+_system~.ini

BIN
image/background.png


BIN
image/bg/a_side.png


BIN
image/bg/b_side.png


BIN
image/bg/bar_add.png


BIN
image/bg/bar_bookmark.png


BIN
image/bg/bar_cmd_go.png


BIN
image/bg/bar_edit.png


BIN
image/bg/bar_pause.png


BIN
image/bg/bar_sampling.png


BIN
image/bg/bar_setting.png


BIN
image/bg/bar_stop.png


BIN
image/bg/close_bt.png


BIN
image/bg/company_setting_logo.png


BIN
image/bg/delete.png


BIN
image/bg/department_setting_logo.png


BIN
image/bg/download.png


BIN
image/bg/equipment_setting_logo.png


BIN
image/bg/gj_001_1_20.png


BIN
image/bg/gj_001_21_40.png


BIN
image/bg/gj_001_41_60.png


BIN
image/bg/green_dot.png


BIN
image/bg/grey_dot.png


BIN
image/bg/icon.png


BIN
image/bg/inductance_logo.png


BIN
image/bg/insulation_logo.png


BIN
image/bg/login.png


BIN
image/bg/logo.png


BIN
image/bg/logoff.png


BIN
image/bg/menu_data_analysis.png


BIN
image/bg/menu_data_preprocess.png


BIN
image/bg/menu_file.png


BIN
image/bg/menu_setting.png


BIN
image/bg/min_bt.png


BIN
image/bg/ng_bg.png


BIN
image/bg/ok_bg.png


BIN
image/bg/open_file.png


BIN
image/bg/refresh.png


BIN
image/bg/reset_logo.png


BIN
image/bg/resistance_logo.png


BIN
image/bg/save_bg.png


BIN
image/bg/scan_barcode.png


BIN
image/bg/tree_folder_icon.png


BIN
image/bg/tree_leaf_icon.png


BIN
image/bg/user.png


BIN
image/bg/vertical_line.png


BIN
image/bg/zoom_in.png


BIN
image/bg/zoom_out.png


BIN
image/close_bt.png


+ 0 - 0
image/cmd.txt


BIN
image/icon.ico


BIN
image/info_bt.png


BIN
image/min_bt.png


BIN
lib/commons-codec-1.15.jar


BIN
lib/fastjson2-2.0.16.jar


BIN
lib/gson-2.10.jar


BIN
lib/iot-communication-1.4.4.jar


BIN
lib/jSerialComm-2.6.2.jar


BIN
lib/jfreechart-1.5.4.jar


BIN
lib/jshortcut-0.4-oberzalek.jar


BIN
lib/kotlin-stdlib-1.8.10.jar


BIN
lib/logback-classic-1.2.13.jar


BIN
lib/logback-core-1.2.13.jar


BIN
lib/netty-all-4.1.48.Final.jar


BIN
lib/okhttp-4.10.0.jar


BIN
lib/okio-jvm-3.3.0.jar


BIN
lib/s7connector-2.1.jar


BIN
lib/slf4j-api-1.7.36.jar


BIN
lib/sqlite-jdbc-3.36.0.3.jar


+ 30 - 0
src/com/mes/component/MyDialog.java

@@ -0,0 +1,30 @@
+package com.mes.component;
+
+import javax.swing.*;
+import java.awt.*;
+
+public class MyDialog extends JDialog {
+
+    public MyDialog(Frame owner, String title, String msg){
+        super(owner, title);
+        init(msg);
+    }
+
+    private void init(String msg){
+        Container container = this.getContentPane();
+        container.setLayout(null);
+        container.setBackground(Color.BLACK);
+        this.setSize(800,220);
+        this.setLocationRelativeTo(null);
+
+        JLabel tslabel = new JLabel(msg);
+        tslabel.setHorizontalAlignment(SwingConstants.CENTER);
+        tslabel.setForeground(Color.RED);
+        tslabel.setFont(new Font("微软雅黑", Font.PLAIN,48));
+        tslabel.setBounds(0,30,800,100);
+
+        container.add(tslabel);
+        this.setModal(true);
+        this.setVisible(true);
+    }
+}

+ 246 - 0
src/com/mes/ui/DhbKcUtil.java

@@ -0,0 +1,246 @@
+package com.mes.ui;
+
+import com.alibaba.fastjson2.JSONObject;
+import com.mes.util.JdbcUtils;
+
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 底护板入库 HTTP 工具
+ * 失败时本地 SQLite 暂存,后台定时重试
+ */
+public class DhbKcUtil {
+
+    public static class Result {
+        public boolean ok;
+        public String message;
+        public ServerRoute.Server server;
+
+        public Result(boolean ok, String message, ServerRoute.Server server) {
+            this.ok = ok;
+            this.message = message;
+            this.server = server;
+        }
+    }
+
+    /** 同步入库(成功返回 ok=true,失败/无网络暂存到本地) */
+    public static Result in(String batchSn, String userCode, String lineSn) {
+        ServerRoute.Server srv = ServerRoute.route(batchSn);
+        if (srv == null) {
+            return new Result(false, "未识别的底护板码前缀", null);
+        }
+
+        String url = srv.httpBase() + "/mes/mesProductDhbKc/in";
+        String params = "__ajax=json"
+                + "&batchSn=" + enc(batchSn)
+                + "&oprno=" + enc(srv.oprno)
+                + "&lineSn=" + enc(lineSn)
+                + "&userCode=" + enc(userCode);
+
+        String resp = postForm(url, params, 5000, 10000);
+        if (resp == null) {
+            // 网络/服务异常,暂存
+            saveLocal(srv.key, batchSn, srv.oprno, lineSn, userCode, "0", "网络异常");
+            return new Result(false, "服务器不可达,已暂存待重试", srv);
+        }
+        try {
+            JSONObject json = JSONObject.parseObject(resp);
+            if (json != null && "true".equalsIgnoreCase(String.valueOf(json.get("result")))) {
+                saveLocal(srv.key, batchSn, srv.oprno, lineSn, userCode, "1", json.getString("message"));
+                return new Result(true, json.getString("message"), srv);
+            }
+            String msg = (json != null && json.get("message") != null) ? json.getString("message") : "服务端拒绝";
+            // 业务失败也记录一笔,state=2 表示已尝试但被拒
+            saveLocal(srv.key, batchSn, srv.oprno, lineSn, userCode, "2", msg);
+            return new Result(false, msg, srv);
+        } catch (Exception e) {
+            saveLocal(srv.key, batchSn, srv.oprno, lineSn, userCode, "0", "返回解析失败");
+            return new Result(false, "返回解析失败,已暂存", srv);
+        }
+    }
+
+    /** ping 健康检查 */
+    public static boolean ping(ServerRoute.Server srv) {
+        if (srv == null) return false;
+        String url = srv.httpBase() + "/mes/mesProductDhbKc/ping?__ajax=json";
+        String resp = postForm(url, "__ajax=json", 2000, 3000);
+        if (resp == null) return false;
+        try {
+            JSONObject json = JSONObject.parseObject(resp);
+            return json != null && "true".equalsIgnoreCase(String.valueOf(json.get("result")));
+        } catch (Exception e) {
+            return false;
+        }
+    }
+
+    /** 重试本地暂存的失败记录 */
+    public static int retryPending() {
+        int success = 0;
+        try {
+            if (JdbcUtils.conn == null || JdbcUtils.conn.isClosed()) {
+                JdbcUtils.openConnection();
+            }
+            List<long[]> idsByPrefix = new ArrayList<>();
+            List<String[]> rows = new ArrayList<>();
+            Statement st = JdbcUtils.conn.createStatement();
+            ResultSet rs = st.executeQuery("SELECT id, batch_sn, oprno, line_sn, user_code, server FROM dhb_kc_record WHERE state='0' ORDER BY id ASC LIMIT 50");
+            while (rs.next()) {
+                rows.add(new String[]{
+                        String.valueOf(rs.getLong("id")),
+                        rs.getString("batch_sn"),
+                        rs.getString("oprno"),
+                        rs.getString("line_sn"),
+                        rs.getString("user_code"),
+                        rs.getString("server")
+                });
+            }
+            rs.close();
+            st.close();
+
+            for (String[] r : rows) {
+                String id = r[0];
+                String batchSn = r[1];
+                ServerRoute.Server srv = ServerRoute.route(batchSn);
+                if (srv == null) continue;
+                String url = srv.httpBase() + "/mes/mesProductDhbKc/in";
+                String params = "__ajax=json"
+                        + "&batchSn=" + enc(batchSn)
+                        + "&oprno=" + enc(srv.oprno)
+                        + "&lineSn=" + enc(r[3])
+                        + "&userCode=" + enc(r[4]);
+                String resp = postForm(url, params, 3000, 5000);
+                if (resp == null) continue;
+                try {
+                    JSONObject json = JSONObject.parseObject(resp);
+                    if (json != null && "true".equalsIgnoreCase(String.valueOf(json.get("result")))) {
+                        Statement up = JdbcUtils.conn.createStatement();
+                        up.executeUpdate("UPDATE dhb_kc_record SET state='1', message='" + escape(json.getString("message")) + "' WHERE id=" + id);
+                        up.close();
+                        success++;
+                    }
+                } catch (Exception ignore) {}
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return success;
+    }
+
+    /** 查本地记录(按 server 过滤,最近 N 条) */
+    public static List<String[]> listLocal(String serverKey, int limit) {
+        List<String[]> rows = new ArrayList<>();
+        try {
+            if (JdbcUtils.conn == null || JdbcUtils.conn.isClosed()) {
+                JdbcUtils.openConnection();
+            }
+            String sql = "SELECT batch_sn, oprno, line_sn, user_code, state, message, record_time FROM dhb_kc_record"
+                    + (serverKey != null ? " WHERE server='" + escape(serverKey) + "'" : "")
+                    + " ORDER BY id DESC LIMIT " + Math.max(1, limit);
+            Statement st = JdbcUtils.conn.createStatement();
+            ResultSet rs = st.executeQuery(sql);
+            while (rs.next()) {
+                rows.add(new String[]{
+                        rs.getString("batch_sn"),
+                        rs.getString("oprno"),
+                        rs.getString("line_sn"),
+                        rs.getString("user_code"),
+                        renderState(rs.getString("state")),
+                        rs.getString("message"),
+                        rs.getString("record_time")
+                });
+            }
+            rs.close();
+            st.close();
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return rows;
+    }
+
+    private static String renderState(String s) {
+        if ("1".equals(s)) return "已提交";
+        if ("2".equals(s)) return "服务端拒绝";
+        return "待重试";
+    }
+
+    private static void saveLocal(String server, String batchSn, String oprno, String lineSn, String userCode, String state, String message) {
+        try {
+            if (JdbcUtils.conn == null || JdbcUtils.conn.isClosed()) {
+                JdbcUtils.openConnection();
+            }
+            String now = com.mes.util.DateLocalUtils.getCurrentTime();
+            PreparedStatement ps = JdbcUtils.conn.prepareStatement(
+                    "INSERT INTO dhb_kc_record(server,batch_sn,oprno,line_sn,user_code,state,message,record_time) VALUES(?,?,?,?,?,?,?,?)");
+            ps.setString(1, nv(server));
+            ps.setString(2, nv(batchSn));
+            ps.setString(3, nv(oprno));
+            ps.setString(4, nv(lineSn));
+            ps.setString(5, nv(userCode));
+            ps.setString(6, nv(state));
+            ps.setString(7, nv(message));
+            ps.setString(8, now);
+            ps.executeUpdate();
+            ps.close();
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    private static String nv(String s) { return s == null ? "" : s; }
+
+    private static String escape(String s) {
+        if (s == null) return "";
+        return s.replace("'", "''");
+    }
+
+    private static String enc(String s) {
+        try {
+            return URLEncoder.encode(s == null ? "" : s, "UTF-8");
+        } catch (Exception e) {
+            return "";
+        }
+    }
+
+    /** post 表单,超时返回 null */
+    private static String postForm(String urlStr, String params, int connTimeout, int readTimeout) {
+        HttpURLConnection conn = null;
+        try {
+            URL url = new URL(urlStr);
+            conn = (HttpURLConnection) url.openConnection();
+            conn.setRequestMethod("POST");
+            conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8");
+            conn.setConnectTimeout(connTimeout);
+            conn.setReadTimeout(readTimeout);
+            conn.setDoOutput(true);
+            conn.setDoInput(true);
+            try (OutputStream os = conn.getOutputStream()) {
+                os.write(params.getBytes(StandardCharsets.UTF_8));
+            }
+            int code = conn.getResponseCode();
+            if (code != 200) return null;
+            StringBuilder sb = new StringBuilder();
+            try (InputStream is = conn.getInputStream();
+                 BufferedReader br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) {
+                String line;
+                while ((line = br.readLine()) != null) sb.append(line);
+            }
+            return sb.toString();
+        } catch (Exception e) {
+            return null;
+        } finally {
+            if (conn != null) conn.disconnect();
+        }
+    }
+}

+ 85 - 0
src/com/mes/ui/LockUtil.java

@@ -0,0 +1,85 @@
+package com.mes.ui;
+
+import java.io.File;
+import java.io.RandomAccessFile;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileLock;
+import java.nio.channels.OverlappingFileLockException;
+
+public class LockUtil {
+    private FileChannel channel;
+
+    private FileLock lock;
+
+    private File file;
+
+    private static LockUtil instance = null;
+
+    /**
+     * 获取唯一进程限制的实例
+     * @Title: getInstance
+     * @return  唯一进程实例
+     */
+    public static LockUtil getInstance(){
+        if(instance == null){
+            instance = new LockUtil();
+        }
+        return instance;
+    }
+
+    /**
+     * 进程是否正在运行
+     * @Title: isAppActive
+     * @return  运行与否
+     */
+    public boolean isAppActive() {
+        File file = new File(".lock");
+        try {
+            channel = new RandomAccessFile(file, "rw").getChannel();
+            try {
+                lock = channel.tryLock();
+
+            } catch (OverlappingFileLockException e) {
+                closeLock();
+                return true;
+            }
+            if (lock == null) {
+                closeLock();
+                return true;
+            }
+            Runtime.getRuntime().addShutdownHook(new Thread() {
+                public void run() {
+                    closeLock();
+                    deleteFile();
+                }
+            });
+            return false;
+        } catch (Exception e) {
+            closeLock();
+            return true;
+        }
+    }
+
+    protected void deleteFile() {
+        try {
+            file.delete();
+        } catch (Exception e) {
+
+        }
+
+    }
+
+    private void closeLock() {
+        try {
+            lock.release();
+        } catch (Exception e) {
+
+        }
+
+        try {
+            channel.close();
+        } catch (Exception e) {
+
+        }
+    }
+}

+ 190 - 0
src/com/mes/ui/LoginFarme.java

@@ -0,0 +1,190 @@
+package com.mes.ui;
+
+import com.alibaba.fastjson2.JSONObject;
+import com.mes.util.Base64Utils;
+import com.mes.util.HttpUtils;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.time.LocalDateTime;
+
+public class LoginFarme extends JFrame {
+    //登录模块组件
+    static JLabel userNameLabel= new JLabel("<html><body>用户名:</body></html>",JLabel.LEFT);//用户名
+    static JLabel userPasswordLabel= new JLabel("<html><body>密码:</body></html>",JLabel.LEFT);//用户名
+    public static JTextField userNameTxt;
+    public static JPasswordField userPasswordTxt;
+    static JButton loginButton = new JButton("用户密码登录");
+    static JButton scanLoginButton = new JButton("扫  码  登  录");
+
+    public LoginFarme(){
+        setTitle("MES系统客户端:"+MesClient.mes_gw+" - "+MesClient.mes_gw_des);
+
+        ImageIcon bg = new ImageIcon(MesClient.class.getResource("/background.png"));
+        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+        JLabel imgLabel = new JLabel(bg);//将背景图放在标签里。
+        getLayeredPane().add(imgLabel, new Integer(Integer.MIN_VALUE));//注意这里是关键,将背景标签添加到jfram的LayeredPane面板里。
+        imgLabel.setBounds(0,0,bg.getIconWidth(), bg.getIconHeight());//设置背景标签的位置
+        Container contentPane=getContentPane();
+        contentPane.setLayout(null);//布局,很重要
+        JPanel welcomePanel = new JPanel();
+        //welcomePanel.setLayout(new GridLayout(3, 1));
+        welcomePanel.setLayout(null);
+        //welcomeLable.setBounds(10, 5, 700, 100);
+        welcomePanel.setBounds(30, 330, 890, 300);
+        welcomePanel.setOpaque(false);//背景透明
+        contentPane.add(welcomePanel);
+
+        //登录页面
+        userNameLabel.setBounds(300, 100, 120, 40);
+        userNameLabel.setFont(new java.awt.Font("Dialog",   1,   16));
+        userPasswordLabel.setBounds(300, 150, 120, 40);
+        userPasswordLabel.setFont(new java.awt.Font("Dialog",   1,   16));
+        userNameTxt = new JTextField(20);
+        userNameTxt.setText("system");
+        userNameTxt.setBounds(400, 105, 150, 30);
+        userPasswordTxt = new JPasswordField(20);
+        userPasswordTxt.setBounds(400, 155, 150, 30);
+        userPasswordTxt.setText("Aa111111");
+        loginButton.setFont(new java.awt.Font("Dialog",   1,   16));
+        loginButton.setBounds(300, 200, 255, 40);
+        loginButton.setIcon(new ImageIcon(MesClient.class.getResource("/bg/user.png")));
+
+//        scanLoginButton.setVisible(false);
+        scanLoginButton.setFont(new java.awt.Font("Dialog",   1,   16));
+        scanLoginButton.setBounds(300, 250, 255, 40);
+        scanLoginButton.setIcon(new ImageIcon(MesClient.class.getResource("/bg/scan_barcode.png")));
+
+        welcomePanel.add(userNameLabel);
+        welcomePanel.add(userPasswordLabel);
+        welcomePanel.add(userNameTxt);
+        welcomePanel.add(userPasswordTxt);
+        welcomePanel.add(loginButton);
+        welcomePanel.add(scanLoginButton);
+        loginButton.addActionListener(new ActionListener(){
+            public void actionPerformed(ActionEvent e) {
+                login();
+            }
+        });
+        scanLoginButton.addActionListener(new ActionListener(){
+            public void actionPerformed(ActionEvent e) {
+                scanLogin();
+            }
+        });
+
+        ((JPanel)contentPane).setOpaque(false); //注意这里,将内容面板设为透明。这样LayeredPane面板中的背景才能显示出来。
+        //welcomeWin.setSize(902,678);
+        setResizable(false);//禁止最大化
+        setIconImage(Toolkit.getDefaultToolkit().getImage(MesClient.class.getResource("/bg/logo.png")));
+
+        Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
+        int width = 902;
+        int height = 678;
+        setBounds((d.width-width)/2, (d.height-height)/2-(d.height-height)/4, width, height);
+        /*****正常模式******/
+        setVisible(true);
+    }
+
+    //登录
+    public static void login() {
+        String user_str = userNameTxt.getText().toString();
+        String password_str = userPasswordTxt.getText().toString();
+        if(user_str.equalsIgnoreCase("")||password_str.equalsIgnoreCase("")) {
+            JOptionPane.showMessageDialog(MesClient.mesClientFrame,"用户名或密码不能为空","提示窗口", JOptionPane.INFORMATION_MESSAGE);
+            return;
+        }
+        String username = Base64Utils.getBase64(user_str);
+        String password = Base64Utils.getBase64(password_str);
+        System.out.println("&username=" + username + "&password=" + password);
+        String url = "http://"+MesClient.mes_server_ip+":8980/js/a/login?__ajax=json&username="+username+"&password="+password+"&validCode=&__sid=";
+        String loginResult = HttpUtils.sendRequest(url);
+        if(loginResult.equalsIgnoreCase("false")) {
+            JOptionPane.showMessageDialog(MesClient.mesClientFrame,"登录异常,请检查网络或联系网络管理员","提示窗口", JOptionPane.INFORMATION_MESSAGE);
+            return;
+        }else {
+            JSONObject retObj = JSONObject.parseObject(loginResult);
+            if(retObj.get("result")!=null&&retObj.get("result").toString().equalsIgnoreCase("true")) {
+                //检查用户权限是否可登录界面
+                checkUserAuthority(retObj);
+            }else {
+                //ret = "msg save error";
+                //ret = false;
+                JOptionPane.showMessageDialog(MesClient.mesClientFrame,"登录失败,用户名或密码错误","提示窗口", JOptionPane.INFORMATION_MESSAGE);
+                return;
+            }
+        }
+    }
+    //扫码登录
+    public static void scanLogin() {
+        //userNameTxt.setText("");
+        //userPasswordTxt.setText("");
+        String scanContent = JOptionPane.showInputDialog(null, "请扫码工牌二维码");
+        System.out.println("scanContent="+scanContent);
+        if(scanContent!=null&&!scanContent.equalsIgnoreCase("")) {
+            String url = "http://"+MesClient.mes_server_ip+":8980/js/a/mes/mesLogin/login?__login=true&__ajax=json&username="+scanContent;
+            String loginResult = HttpUtils.sendRequest(url);
+            System.out.println("loginResult="+loginResult);
+            if(loginResult.equalsIgnoreCase("false")) {
+                JOptionPane.showMessageDialog(MesClient.mesClientFrame,"登录异常,请检查网络或联系网络管理员","提示窗口", JOptionPane.INFORMATION_MESSAGE);
+                return;
+            }else {
+                JSONObject retObj = JSONObject.parseObject(loginResult);
+                if(retObj.get("result")!=null&&retObj.get("result").toString().equalsIgnoreCase("true")) {
+                    //检查用户权限是否可登录界面
+                    checkUserAuthority(retObj);
+                }else {
+                    JOptionPane.showMessageDialog(MesClient.mesClientFrame,"登录失败,用户名或密码错误","提示窗口", JOptionPane.INFORMATION_MESSAGE);
+                    return;
+                }
+            }
+        }else {
+            JOptionPane.showMessageDialog(MesClient.mesClientFrame,"扫码内容错误","提示窗口", JOptionPane.INFORMATION_MESSAGE);
+            return;
+        }
+    }
+
+    //检查用户权限是否可登录界面
+    public static void checkUserAuthority(JSONObject retObj) {
+        //设置登录用户名
+        JSONObject userObj = JSONObject.parseObject(retObj.get("user").toString());
+        String user_id = userObj.getString("id").toString();
+
+        //获取sessionid,判断权限
+        MesClient.sessionid = retObj.get("sessionid").toString();
+        if(MesClient.sessionid!=null&&!MesClient.sessionid.equalsIgnoreCase("")) {
+            //请求权限
+            String url_authority = "http://"+MesClient.mes_server_ip+":8980/js/a/mes/mesLineProcess/userAuth?__ajax=json&type=0&__sid="+MesClient.sessionid+"&oprno="+MesClient.mes_gw+"&lineSn="+MesClient.mes_line_sn;
+            String authorityResult = HttpUtils.sendRequest(url_authority);
+            System.out.println("authorityResult="+authorityResult);
+            JSONObject authorityObj = JSONObject.parseObject(authorityResult);
+            if(authorityObj.get("result")!=null&&authorityObj.get("result").toString().equalsIgnoreCase("true")) {
+                JSONObject authObjTmp = JSONObject.parseObject(authorityObj.get("data").toString());
+                MesClient.mes_auth = Integer.parseInt(authObjTmp.getString("auth").toString());
+                if(MesClient.mes_auth==0) {
+                    //无权限登录
+                    JOptionPane.showMessageDialog(MesClient.mesClientFrame,"您无权登录该工位","提示窗口", JOptionPane.INFORMATION_MESSAGE);
+                    return;
+                }else if(MesClient.mes_auth==1||MesClient.mes_auth==2) {
+                    // 获取等于所处时间-当前小时
+                    LocalDateTime now = LocalDateTime.now();
+                    MesClient.userLoginHours = now.getHour();
+
+                    //1操作工人,2管理员 - 入库工位不需要 TCP,直接进入主界面
+                    MesClient.user_menu.setText(user_id);
+                    MesClient.user20 = user_id;
+                    MesClient.welcomeWin.setVisible(false);
+                    MesClient.mesClientFrame.setVisible(true);
+
+                    MesClient.initWarehouseData();
+                }
+
+            }
+
+        }else {
+            JOptionPane.showMessageDialog(MesClient.mesClientFrame,"登录失败,用户名或密码错误","提示窗口", JOptionPane.INFORMATION_MESSAGE);
+            return;
+        }
+    }
+}

+ 402 - 0
src/com/mes/ui/MesClient.java

@@ -0,0 +1,402 @@
+package com.mes.ui;
+
+import com.mes.util.DateLocalUtils;
+import com.mes.util.JdbcUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.swing.*;
+import javax.swing.border.EmptyBorder;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.time.LocalDateTime;
+import java.util.List;
+import java.util.Properties;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * 底护板清洗 + 入库 客户端
+ * 工位:520-OP285 / 610-OP290(按底护板码前缀路由)
+ *
+ * 流程:
+ *  扫底护板码 → 按前缀路由到 520 或 610 → POST /mesProductDhbKc/in → 显示结果
+ *  网络异常时本地 SQLite 暂存,后台 5 秒一次重试
+ */
+public class MesClient extends JFrame {
+    public static final Logger log = LoggerFactory.getLogger(MesClient.class);
+
+    public static int mes_auth = 0;
+    public static String mes_gw = "";          // 本机工位号(仅显示用)
+    public static String mes_gw_des = "";
+    public static String mes_server_ip = "";   // 兼容字段:登录用主服务器 IP
+    public static int mes_tcp_port = 8980;
+    public static String mes_line_sn = "";
+
+    // 兼容字段(被 LoginFarme/legacy 引用,新逻辑下无作用)
+    public static Object nettyClient = null;
+    public static boolean tcp_connect_flag = true;
+    public static boolean connect_request_flag = false;
+    public static String sessionid = "";
+
+    public static JPanel contentPane;
+    public static MesClient mesClientFrame;
+    public static JTabbedPane tabbedPane;
+
+    public static JButton status_menu;     // 顶部状态文字
+    public static JButton user_menu;
+    public static JButton server520_dot;   // 520 在线指示
+    public static JButton server610_dot;   // 610 在线指示
+
+    public static JTextField scan_input;   // 底护板码输入框
+    public static JButton scan_button;
+    public static JLabel route_label;      // 显示当前码路由到哪个服务器
+
+    public static JTable record520Table;
+    public static JTable record610Table;
+    public static Object[] recordColumns = {"底护板码", "工位", "产线", "操作员", "状态", "消息", "时间"};
+
+    public static JFrame welcomeWin;
+    public static String user20 = "";
+
+    public static int userLoginHours;
+    public static boolean mes_flag = false;
+    public static JButton mesbtn;
+
+    // ============ 入口 ============
+    public static void main(String[] args) {
+        if (LockUtil.getInstance().isAppActive()) return;
+        EventQueue.invokeLater(() -> {
+            try {
+                readProperty();
+                JdbcUtils.getConn();
+
+                mesClientFrame = new MesClient();
+                mesClientFrame.setVisible(false);
+
+                welcomeWin = new LoginFarme();
+                welcomeWin.setVisible(true);
+
+                startBackgroundTasks();
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        });
+    }
+
+    private static void readProperty() throws IOException {
+        InputStream is = ClassLoader.getSystemResourceAsStream("config/config.properties");
+        Properties pro = new Properties();
+        BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
+        pro.load(br);
+        mes_gw = pro.getProperty("mes.gw", "OP290").trim();
+        mes_line_sn = pro.getProperty("mes.line_sn", "XT").trim();
+        mes_gw_des = OprnoUtil.getGwDes(mes_line_sn, mes_gw);
+
+        // 多服务器配置
+        ServerRoute.init(pro);
+
+        // 登录用主服务器(默认 520)
+        ServerRoute.Server login = ServerRoute.login();
+        if (login != null) {
+            mes_server_ip = login.ip;
+            mes_tcp_port = login.port;
+        }
+        log.info("入库客户端启动 gw={} lineSn={} loginServer={}", mes_gw, mes_line_sn, mes_server_ip);
+    }
+
+    /** 启动后台任务:健康检查 + 失败重试 */
+    private static void startBackgroundTasks() {
+        // 健康检查 5 秒一次
+        Timer t = new Timer(true);
+        t.schedule(new TimerTask() {
+            public void run() {
+                for (ServerRoute.Server s : ServerRoute.all()) {
+                    s.online = DhbKcUtil.ping(s);
+                }
+                refreshServerDots();
+            }
+        }, 1000, 5000);
+
+        // 失败重试 5 秒一次
+        Timer t2 = new Timer(true);
+        t2.schedule(new TimerTask() {
+            public void run() {
+                int n = DhbKcUtil.retryPending();
+                if (n > 0) refreshRecordTables();
+            }
+        }, 3000, 5000);
+    }
+
+    private static void refreshServerDots() {
+        if (server520_dot == null || server610_dot == null) return;
+        EventQueue.invokeLater(() -> {
+            ServerRoute.Server s520 = ServerRoute.get("520");
+            ServerRoute.Server s610 = ServerRoute.get("610");
+            applyDot(server520_dot, "520", s520 != null && s520.online);
+            applyDot(server610_dot, "610", s610 != null && s610.online);
+        });
+    }
+
+    private static void applyDot(JButton btn, String label, boolean online) {
+        if (online) {
+            btn.setIcon(new ImageIcon(MesClient.class.getResource("/bg/green_dot.png")));
+            btn.setText(label + " 在线");
+            btn.setForeground(new Color(0, 128, 0));
+        } else {
+            btn.setIcon(new ImageIcon(MesClient.class.getResource("/bg/grey_dot.png")));
+            btn.setText(label + " 离线");
+            btn.setForeground(Color.RED);
+        }
+    }
+
+    public static void setMenuStatus(String msg, int error) {
+        if (status_menu == null) return;
+        status_menu.setForeground(error == 0 ? new Color(0, 128, 0) : Color.RED);
+        status_menu.setText(msg);
+    }
+
+    public static void logoff() {
+        welcomeWin.setVisible(true);
+        mesClientFrame.setVisible(false);
+    }
+
+    // ============ 主界面 ============
+    public MesClient() {
+        setIconImage(Toolkit.getDefaultToolkit().getImage(MesClient.class.getResource("/bg/logo.png")));
+        setTitle("底护板清洗+录入客户端:" + mes_gw + " - " + mes_gw_des);
+        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+        setBounds(0, 0, 1024, 768);
+
+        // ---- 菜单栏 ----
+        JMenuBar menuBar = new JMenuBar();
+        menuBar.setFont(new Font("Microsoft YaHei UI", Font.PLAIN, 20));
+        setJMenuBar(menuBar);
+
+        JMenu fileMenu = new JMenu("用户");
+        fileMenu.setIcon(new ImageIcon(MesClient.class.getResource("/bg/user.png")));
+        fileMenu.setFont(new Font("微软雅黑", Font.PLAIN, 18));
+        menuBar.add(fileMenu);
+
+        JMenuItem exitMenuItem = new JMenuItem("退出登录");
+        exitMenuItem.setIcon(new ImageIcon(MesClient.class.getResource("/bg/logoff.png")));
+        exitMenuItem.setFont(new Font("微软雅黑", Font.PLAIN, 18));
+        fileMenu.add(exitMenuItem);
+        exitMenuItem.addMouseListener(new MouseAdapter() {
+            public void mousePressed(MouseEvent e) { logoff(); }
+        });
+
+        // ---- 顶部状态栏 ----
+        contentPane = new JPanel();
+        contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
+        setContentPane(contentPane);
+        contentPane.setLayout(new BorderLayout(0, 0));
+
+        JToolBar toolBar = new JToolBar();
+        contentPane.add(toolBar, BorderLayout.NORTH);
+
+        JLabel lblState = new JLabel("状态:");
+        lblState.setFont(new Font("微软雅黑", Font.PLAIN, 18));
+        toolBar.add(lblState);
+
+        status_menu = new JButton("等待登录");
+        status_menu.setFont(new Font("微软雅黑", Font.PLAIN, 18));
+        status_menu.setForeground(new Color(0, 128, 0));
+        toolBar.add(status_menu);
+
+        toolBar.add(new JLabel("    "));
+
+        server520_dot = new JButton("520 检测中");
+        server520_dot.setFont(new Font("微软雅黑", Font.PLAIN, 18));
+        server520_dot.setIcon(new ImageIcon(MesClient.class.getResource("/bg/grey_dot.png")));
+        toolBar.add(server520_dot);
+
+        server610_dot = new JButton("610 检测中");
+        server610_dot.setFont(new Font("微软雅黑", Font.PLAIN, 18));
+        server610_dot.setIcon(new ImageIcon(MesClient.class.getResource("/bg/grey_dot.png")));
+        toolBar.add(server610_dot);
+
+        toolBar.add(new JLabel("    "));
+
+        JLabel lblUser = new JLabel("登录用户:");
+        lblUser.setFont(new Font("微软雅黑", Font.PLAIN, 18));
+        toolBar.add(lblUser);
+
+        user_menu = new JButton("--");
+        user_menu.setFont(new Font("微软雅黑", Font.PLAIN, 18));
+        toolBar.add(user_menu);
+
+        // 兼容:旧代码引用了 mesbtn
+        mesbtn = new JButton("");
+        mesbtn.setVisible(false);
+
+        // ---- Tab ----
+        tabbedPane = new JTabbedPane(JTabbedPane.TOP);
+        tabbedPane.setFont(new Font("微软雅黑", Font.BOLD, 18));
+        contentPane.add(tabbedPane);
+
+        tabbedPane.addTab("录入", new ImageIcon(MesClient.class.getResource("/bg/a_side.png")), buildScanPanel(), null);
+        tabbedPane.addTab("520 工作记录", new ImageIcon(MesClient.class.getResource("/bg/menu_data_preprocess.png")), buildRecordPanel("520"), null);
+        tabbedPane.addTab("610 工作记录", new ImageIcon(MesClient.class.getResource("/bg/menu_data_preprocess.png")), buildRecordPanel("610"), null);
+
+        tabbedPane.addChangeListener(e -> refreshRecordTables());
+    }
+
+    private JPanel buildScanPanel() {
+        JPanel root = new JPanel();
+        root.setLayout(null);
+
+        JLabel title = new JLabel("扫底护板码录入");
+        title.setFont(new Font("微软雅黑", Font.BOLD, 36));
+        title.setHorizontalAlignment(SwingConstants.CENTER);
+        title.setBounds(0, 30, 1000, 60);
+        root.add(title);
+
+        scan_input = new JTextField();
+        scan_input.setHorizontalAlignment(SwingConstants.CENTER);
+        scan_input.setFont(new Font("微软雅黑", Font.PLAIN, 32));
+        scan_input.setBounds(80, 130, 600, 80);
+        root.add(scan_input);
+
+        scan_button = new JButton("录入");
+        scan_button.setIcon(new ImageIcon(MesClient.class.getResource("/bg/scan_barcode.png")));
+        scan_button.setFont(new Font("微软雅黑", Font.PLAIN, 28));
+        scan_button.setBounds(700, 130, 200, 80);
+        root.add(scan_button);
+
+        route_label = new JLabel(" ");
+        route_label.setFont(new Font("微软雅黑", Font.PLAIN, 20));
+        route_label.setBounds(80, 220, 820, 30);
+        route_label.setHorizontalAlignment(SwingConstants.CENTER);
+        root.add(route_label);
+
+        JLabel hint = new JLabel("提示:底护板码以 +KB77 开头入 520,+KB78 开头入 610。重复扫码不影响。");
+        hint.setFont(new Font("微软雅黑", Font.PLAIN, 14));
+        hint.setForeground(Color.GRAY);
+        hint.setBounds(80, 260, 820, 30);
+        hint.setHorizontalAlignment(SwingConstants.CENTER);
+        root.add(hint);
+
+        // 输入框监听:实时显示路由结果
+        scan_input.getDocument().addDocumentListener(new javax.swing.event.DocumentListener() {
+            public void insertUpdate(javax.swing.event.DocumentEvent e) { updateRouteHint(); }
+            public void removeUpdate(javax.swing.event.DocumentEvent e) { updateRouteHint(); }
+            public void changedUpdate(javax.swing.event.DocumentEvent e) { updateRouteHint(); }
+        });
+        scan_input.addActionListener(e -> doScan());
+        scan_button.addActionListener(e -> doScan());
+
+        return root;
+    }
+
+    private static void updateRouteHint() {
+        String code = scan_input.getText().trim();
+        if (code.isEmpty()) { route_label.setText(" "); return; }
+        ServerRoute.Server s = ServerRoute.route(code);
+        if (s == null) {
+            route_label.setForeground(Color.RED);
+            route_label.setText("⚠ 未识别的前缀,无法路由");
+        } else {
+            route_label.setForeground(new Color(0, 128, 0));
+            route_label.setText("→ 将提交到 " + s.label() + "  工位 " + s.oprno);
+        }
+    }
+
+    private static void doScan() {
+        String code = scan_input.getText().trim();
+        if (code.isEmpty()) return;
+
+        ServerRoute.Server srv = ServerRoute.route(code);
+        if (srv == null) {
+            setMenuStatus("未识别的底护板码前缀: " + code, -1);
+            scan_input.selectAll();
+            return;
+        }
+
+        DhbKcUtil.Result r = DhbKcUtil.in(code, user20.trim(), mes_line_sn);
+        if (r.ok) {
+            setMenuStatus("[" + srv.key + "] " + r.message, 0);
+        } else {
+            setMenuStatus("[" + srv.key + "] " + r.message, -1);
+        }
+        scan_input.setText("");
+        scan_input.requestFocus();
+        refreshRecordTables();
+    }
+
+    private JPanel buildRecordPanel(String serverKey) {
+        JPanel root = new JPanel(new BorderLayout());
+        JTable table = new JTable(new Object[0][7], recordColumns);
+        table.setFont(new Font("微软雅黑", Font.PLAIN, 14));
+        table.setRowHeight(28);
+        table.getTableHeader().setFont(new Font("微软雅黑", Font.BOLD, 14));
+        table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
+        // 列宽:底护板码 / 工位 / 产线 / 操作员 / 状态 / 消息 / 时间
+        int[] widths = {260, 80, 70, 100, 100, 260, 160};
+        for (int i = 0; i < widths.length && i < table.getColumnCount(); i++) {
+            table.getColumnModel().getColumn(i).setPreferredWidth(widths[i]);
+        }
+        if ("520".equals(serverKey)) record520Table = table;
+        else record610Table = table;
+        root.add(new JScrollPane(table), BorderLayout.CENTER);
+
+        JButton refresh = new JButton("刷新");
+        refresh.setFont(new Font("微软雅黑", Font.PLAIN, 14));
+        refresh.addActionListener(e -> refreshRecordTables());
+        JPanel south = new JPanel(new FlowLayout(FlowLayout.LEFT));
+        south.add(refresh);
+        root.add(south, BorderLayout.SOUTH);
+        return root;
+    }
+
+    public static void refreshRecordTables() {
+        EventQueue.invokeLater(() -> {
+            fillTable(record520Table, "520");
+            fillTable(record610Table, "610");
+        });
+    }
+
+    private static void fillTable(JTable table, String serverKey) {
+        if (table == null) return;
+        List<String[]> rows = DhbKcUtil.listLocal(serverKey, 200);
+        Object[][] data = new Object[rows.size()][recordColumns.length];
+        for (int i = 0; i < rows.size(); i++) {
+            data[i] = rows.get(i);
+        }
+        // 仅更新数据,保留列宽配置
+        javax.swing.table.DefaultTableModel model = new javax.swing.table.DefaultTableModel(data, recordColumns) {
+            @Override
+            public boolean isCellEditable(int row, int column) { return false; }
+        };
+        // 保存列宽
+        int[] widths = new int[table.getColumnCount()];
+        for (int i = 0; i < widths.length; i++) {
+            widths[i] = table.getColumnModel().getColumn(i).getPreferredWidth();
+        }
+        table.setModel(model);
+        // 恢复列宽
+        for (int i = 0; i < widths.length && i < table.getColumnCount(); i++) {
+            table.getColumnModel().getColumn(i).setPreferredWidth(widths[i]);
+        }
+    }
+
+    /** 入库工位无物料绑定,不做事 */
+    public static void initWarehouseData() {
+        setMenuStatus("已就绪,请扫底护板码", 0);
+        if (scan_input != null) scan_input.requestFocus();
+        refreshRecordTables();
+    }
+
+    public static void getMaterailData() {
+        // 入库工位无物料绑定,不做事
+    }
+}

+ 67 - 0
src/com/mes/ui/OprnoUtil.java

@@ -0,0 +1,67 @@
+package com.mes.ui;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+public class OprnoUtil {
+    public static String[] xtoprnos = new String[]{
+            "OP040","OP050","OP060","OP070","OP080","OP090","OP100",
+            "OP110","OP120","OP130","OP140","OP150","OP170","OP190",
+            "OP200","OP210","OP220","OP230","OP240","OP250","OP260",
+            "OP270","OP280","OP290","OP300","OP310","OP320","OP340",
+            "OP350","OP360","OP370","OP370","OP380","OP390","OP400","OP410","OP420"
+    };
+    public static String[] xtoprnodes = new String[]{
+            "镭雕二维码","前边梁压铆,单部件拉铆","框架CMT焊接","人工补焊",
+            "焊道检查","焊道打磨","","框架气密","框架反面CNC加工+去毛刺","框架反面,吹铝屑+清洁",
+            "框架涂胶","液冷板安装+水冷板点焊","液冷板FSW","匙孔补焊+匙孔补焊打磨",
+            "总成正面CNC+去毛刺","总成正面,吹铝屑+清洁","总成正面装配","套筒涂胶","封堵片焊接",
+            "左右边梁封堵","半成品气密","VHB胶带粘贴+冷板泡棉粘贴","冷板背面涂胶",
+            "底护板清洗","安装底护板","成品气密","液冷板气密","总成检具检验","箱体封堵",
+            "总成清洁","粘贴云母片","CCD","终检","GP12正面","后梁底涂镭雕","GP12反面","包装"
+    };
+    public static String[] lboprnos = new String[]{
+
+    };
+    public static String[] lboprnodes = new String[]{
+
+    };
+    public static String getGwDes(String lineSn,String oprno){
+        String des = "";
+        oprno = formatOprno(oprno);
+        if(lineSn.equals("XT")){
+            int i = 0;
+            for (String gw:xtoprnos){
+                if(gw.equals(oprno)){
+                    des = xtoprnodes[i];
+                    break;
+                }
+                i++;
+            }
+        }else if(lineSn.equals("LB")){
+            int i = 0;
+            for (String gw:lboprnos){
+                if(gw.equals(oprno)){
+                    des = lboprnodes[i];
+                    break;
+                }
+                i++;
+            }
+        }
+        return des;
+    }
+
+    public static String formatOprno(String oprno){
+        List<String> lists = new ArrayList<>();
+
+        if(oprno.length() == 6){
+            String ysoprno = oprno.substring(0,5).trim();
+            if(!lists.contains(ysoprno)){
+                oprno = ysoprno;
+            }
+        }
+
+        return  oprno;
+    }
+}

+ 81 - 0
src/com/mes/ui/ServerRoute.java

@@ -0,0 +1,81 @@
+package com.mes.ui;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 按底护板码前缀路由到对应服务器
+ * 服务器配置:520 / 610
+ */
+public class ServerRoute {
+
+    /** 服务器条目 */
+    public static class Server {
+        public String key;        // 520 / 610
+        public String ip;
+        public int port;
+        public String oprno;      // 服务器对应的工位号
+        public List<String> prefixes = new ArrayList<>();
+        public boolean online = false;  // 健康状态
+
+        public String httpBase() {
+            return "http://" + ip + ":" + port + "/js/a";
+        }
+
+        public String label() {
+            return key + "(" + ip + ")";
+        }
+    }
+
+    private static final Map<String, Server> SERVERS = new LinkedHashMap<>();
+    private static String loginServerKey = "520";
+
+    public static void init(java.util.Properties pro) {
+        SERVERS.clear();
+        addServer(pro, "520");
+        addServer(pro, "610");
+        String login = pro.getProperty("mes.login.server", "520").trim();
+        if (SERVERS.containsKey(login)) {
+            loginServerKey = login;
+        }
+    }
+
+    private static void addServer(java.util.Properties pro, String key) {
+        String ip = pro.getProperty("mes.server." + key + ".ip", "").trim();
+        if (ip.isEmpty()) return;
+        Server s = new Server();
+        s.key = key;
+        s.ip = ip;
+        s.port = parseInt(pro.getProperty("mes.server." + key + ".port", "8980").trim(), 8980);
+        s.oprno = pro.getProperty("mes.server." + key + ".oprno", "").trim();
+        String prefixes = pro.getProperty("mes.server." + key + ".prefixes", "").trim();
+        for (String p : prefixes.split(",")) {
+            String t = p.trim();
+            if (!t.isEmpty()) s.prefixes.add(t);
+        }
+        SERVERS.put(key, s);
+    }
+
+    private static int parseInt(String s, int def) {
+        try { return Integer.parseInt(s); } catch (Exception e) { return def; }
+    }
+
+    /** 按底护板码路由,找不到返回 null */
+    public static Server route(String batchSn) {
+        if (batchSn == null) return null;
+        for (Server s : SERVERS.values()) {
+            for (String p : s.prefixes) {
+                if (batchSn.startsWith(p)) return s;
+            }
+        }
+        return null;
+    }
+
+    public static Server get(String key) { return SERVERS.get(key); }
+
+    public static java.util.Collection<Server> all() { return SERVERS.values(); }
+
+    public static Server login() { return SERVERS.get(loginServerKey); }
+}

+ 11 - 0
src/com/mes/util/Base64Utils.java

@@ -0,0 +1,11 @@
+package com.mes.util;
+
+import org.apache.commons.codec.binary.Base64;
+
+public class Base64Utils {
+	public static String getBase64(String str) {
+        byte[] binaryData = str.getBytes();
+        String encodedString = Base64.encodeBase64String(binaryData);
+        return encodedString;
+	}
+}

+ 32 - 0
src/com/mes/util/DateLocalUtils.java

@@ -0,0 +1,32 @@
+package com.mes.util;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+public class DateLocalUtils {
+	public static SimpleDateFormat DATA_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//设置日期格式
+	public static SimpleDateFormat DATA_FORMAT1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
+	
+	
+	public static String getCurrentTime() {
+		String currentTime = DATA_FORMAT.format(new Date());
+		return currentTime;
+	}
+	
+	public static String getCurrentTime1() {
+		String currentTime = DATA_FORMAT1.format(new Date());
+		return currentTime;
+	}
+
+	public static SimpleDateFormat DATA_FORMAT2 = new SimpleDateFormat("yyyy-MM-dd");
+	public static String getCurrentDate() {
+		String currentTime = DATA_FORMAT2.format(new Date());
+		return currentTime;
+	}
+
+	public static SimpleDateFormat DATA_FORMAT3 = new SimpleDateFormat("HH:mm:ss");
+	public static String getCurrentTimeHMS() {
+		String currentTime = DATA_FORMAT3.format(new Date());
+		return currentTime;
+	}
+}

+ 49 - 0
src/com/mes/util/ErrorMsg.java

@@ -0,0 +1,49 @@
+package com.mes.util;
+
+public class ErrorMsg {
+    public static String getErrorMsg(String processMsgRet,String lx){
+        String lmsg = "该工件本工位不可加工";
+        try{
+            if(processMsgRet.equalsIgnoreCase("OK")||processMsgRet.equalsIgnoreCase("NG")) {
+                lmsg = "该工件本工位已加工,结果:"+processMsgRet;
+            }else if(processMsgRet.equalsIgnoreCase("NE")) {
+                lmsg = "该工件未录入系统";
+            }else if(processMsgRet.equalsIgnoreCase("NN")) {
+                lmsg = "该工件跳过该工位";
+            }else if(processMsgRet.equalsIgnoreCase("QN")) {
+                lmsg = "该工件OP"+ lx+"0加工NG";
+            }else if(processMsgRet.equalsIgnoreCase("QD")) {
+                lmsg = "该工件OP"+ lx+"0未加工";
+            }else if(processMsgRet.equalsIgnoreCase("NF")) {
+                lmsg = "该工件已合格下线";
+            }else if(processMsgRet.equalsIgnoreCase("NR")) {
+                lmsg = "该工件离线返修中";
+            }else if(processMsgRet.equalsIgnoreCase("NB")) {
+                lmsg = "该工件已报废";
+            }else if(processMsgRet.equalsIgnoreCase("ND")) {
+                lmsg = "该工件NG待处理";
+            }else if(processMsgRet.equalsIgnoreCase("FN")) {
+                lmsg = "首件检查工件不合格停机";
+            }else if(processMsgRet.equalsIgnoreCase("GN")) {
+                lmsg = "更换配件首件检查不合格停机";
+            }else if(processMsgRet.equalsIgnoreCase("CS")) {
+                lmsg = "首件检查两小时内未检查,超时停机";
+            }else if(processMsgRet.equalsIgnoreCase("DJ")) {
+                lmsg = "未进行开班点检";
+            }else if(processMsgRet.equalsIgnoreCase("BM")) {
+                lmsg = "未绑定物料";
+            }else if(processMsgRet.equalsIgnoreCase("PL")) {
+                lmsg = "配件寿命不足";
+            }else if(processMsgRet.equalsIgnoreCase("QM")) {
+                lmsg = "两次气密必须间隔15分钟";
+            }else if(processMsgRet.equalsIgnoreCase("GS")) {
+                lmsg = "工件码格式不正确";
+            }else if(processMsgRet.equalsIgnoreCase("CF")) {
+                lmsg = "工件码重复";
+            }else if(processMsgRet.equalsIgnoreCase("GQ")) {
+                lmsg = "2025-06-10之前的工件,不能加工";
+            }
+        }catch (Exception e){ }
+        return lmsg;
+    }
+}

+ 157 - 0
src/com/mes/util/HttpUtils.java

@@ -0,0 +1,157 @@
+package com.mes.util;
+
+import java.io.*;
+import java.net.HttpURLConnection;
+import java.net.URISyntaxException;
+import java.net.URL;
+
+public class HttpUtils {
+	/**
+	 * HTTP请求,正确报文解析,并保存数据到生产记录数据
+	 * @return 
+	 * @throws URISyntaxException
+	 */
+    //http post请求
+    public static String sendRequest(String urlParam) {
+		String requestType = "POST";
+        //根据接收内容返回数据结果
+    	String ret = "";
+    	System.out.println(urlParam);
+        HttpURLConnection con = null;
+        BufferedReader buffer = null;
+        StringBuffer resultBuffer = null;
+        try {
+            URL url = new URL(urlParam);
+            //得到连接对象
+            con = (HttpURLConnection) url.openConnection();
+            //设置请求类型
+            con.setRequestMethod(requestType);
+            //设置请求需要返回的数据类型和字符集类型
+            con.setRequestProperty("Content-Type", "application/json;charset=GBK");  
+            //允许写出
+            con.setDoOutput(true);
+            //允许读入
+            con.setDoInput(true);
+            //不使用缓存
+            con.setUseCaches(false);
+            //得到响应码
+            int responseCode = con.getResponseCode();
+            System.out.println("responseCode="+responseCode);
+            if(responseCode == HttpURLConnection.HTTP_OK){
+                //得到响应流
+                InputStream inputStream = con.getInputStream();
+                //将响应流转换成字符串
+                resultBuffer = new StringBuffer();
+                String line;
+                buffer = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
+                while ((line = buffer.readLine()) != null) {
+                    resultBuffer.append(line);
+                }
+                System.out.println(resultBuffer.toString());
+                ret = resultBuffer.toString();
+            }else {
+            	//ret = String.valueOf(responseCode);
+            	ret = "false";
+            }
+            con.disconnect();
+        }catch(Exception e) {
+        	//ret = false;
+        	//System.out.println("e.toString()="+e.toString());
+        	//return e.toString();
+            //e.printStackTrace();
+        	ret = "false";
+        }
+        return ret;
+    }
+
+
+    public static String sendPostRequest(String urlParam, String params) {
+        String requestType = "POST";
+        //根据接收内容返回数据结果
+        String ret = "";
+        System.out.println(urlParam);
+        HttpURLConnection con = null;
+        BufferedReader buffer = null;
+        StringBuffer resultBuffer = null;
+        try {
+            URL url = new URL(urlParam);
+            //得到连接对象
+            con = (HttpURLConnection) url.openConnection();
+            //设置请求类型
+            con.setRequestMethod(requestType);
+            //设置请求需要返回的数据类型和字符集类型
+            con.setRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=GBK");
+            //允许写出
+            con.setDoOutput(true);
+            //允许读入
+            con.setDoInput(true);
+            //不使用缓存
+            con.setUseCaches(false);
+
+            // 获取URLConnection对象对应的输出流
+            OutputStreamWriter out = new OutputStreamWriter( con.getOutputStream(),"UTF-8");// utf-8编码
+            // 发送请求参数
+            out.write(params);
+
+            //得到响应码
+            int responseCode = con.getResponseCode();
+            System.out.println("responseCode="+responseCode);
+            if(responseCode == HttpURLConnection.HTTP_OK){
+                //得到响应流
+                InputStream inputStream = con.getInputStream();
+                //将响应流转换成字符串
+                resultBuffer = new StringBuffer();
+                String line;
+                buffer = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
+                while ((line = buffer.readLine()) != null) {
+                    resultBuffer.append(line);
+                }
+                System.out.println(resultBuffer.toString());
+                ret = resultBuffer.toString();
+            }else {
+                //ret = String.valueOf(responseCode);
+                ret = "false";
+            }
+            con.disconnect();
+        }catch(Exception e) {
+            //ret = false;
+            //System.out.println("e.toString()="+e.toString());
+            //return e.toString();
+            //e.printStackTrace();
+            ret = "false";
+        }
+        return ret;
+    }
+
+    public static String sendPostRequestJson(String apiUrl, String jsonData) throws IOException {
+        URL url = new URL(apiUrl);
+        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+        conn.setRequestMethod("POST");
+        conn.setRequestProperty("Content-Type", "application/json");
+        conn.setDoOutput(true);
+
+        // 发送请求数据
+        try (OutputStream os = conn.getOutputStream()) {
+            byte[] input = jsonData.getBytes("utf-8");
+            os.write(input, 0, input.length);
+        }
+
+        // 获取响应数据
+        StringBuilder response = new StringBuilder();
+        int responseCode = conn.getResponseCode();
+//        System.out.println("Sending POST request to URL: " + apiUrl);
+//        System.out.println("Post parameters: " + jsonData);
+//        System.out.println("Response Code: " + responseCode);
+
+        try (BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8"))) {
+            String responseLine;
+            while ((responseLine = br.readLine()) != null) {
+                response.append(responseLine.trim());
+            }
+        }
+
+        conn.disconnect();
+        return response.toString();
+    }
+
+}

+ 158 - 0
src/com/mes/util/JdbcUtils.java

@@ -0,0 +1,158 @@
+package com.mes.util;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+public class JdbcUtils {
+	public static final Logger log =  LoggerFactory.getLogger(JdbcUtils.class);
+	//通过上面的工具就可以获取到properties文件中的键值从而可以加载驱动 获取链接 从而 可以增删改查
+	public static Connection conn = null;
+    public static String Drivde="org.sqlite.JDBC";
+	public static String DATABASE_URL="jdbc:sqlite:mes_db.db";
+    
+    public static Connection getConn(){
+        try {
+            Class.forName(Drivde);// 加载驱动,连接sqlite的jdbc
+            conn = DriverManager.getConnection("jdbc:sqlite:mes_db.db");//连接数据库zhou.db,不存在则创建
+            System.out.println("连接到SQLite数据库成功!");
+            create_bw_record();//初始化结构表
+            //create_measure_data();//初始化测试数据表
+        } catch (Exception e) {
+            // TODO Auto-generated catch block
+        	close();//关闭数据库连接
+            e.printStackTrace();
+        }
+        return conn;
+    }
+
+	public static void openConnection() {
+		try {
+			Class.forName(Drivde);// 加载驱动,连接sqlite的jdbc
+			conn = DriverManager.getConnection(DATABASE_URL);
+		} catch (ClassNotFoundException e) {
+			e.printStackTrace();
+		} catch (SQLException e) {
+			e.printStackTrace();
+			// 如果连接失败,尝试重连
+			reconnect();
+		}
+	}
+
+	private static void reconnect() {
+		try {
+			// 关闭旧连接
+			if (conn != null && !conn.isClosed()) {
+				conn.close();
+			}
+			// 重新建立连接
+			conn = DriverManager.getConnection(DATABASE_URL);
+		} catch (SQLException e) {
+			e.printStackTrace();
+			// 如果重连失败,可以进一步处理异常,比如记录日志、通知管理员等
+		}
+	}
+    
+    public static void create_bw_record() throws SQLException {
+    	Statement statement=conn.createStatement();   //创建连接对象,是Java的一个操作数据库的重要接口
+        //设备结构数据库
+        String sqlEquipment = "CREATE TABLE if not exists bw_record("
+        		+ "id INTEGER PRIMARY KEY AUTOINCREMENT,gw VARCHAR(20),gy VARCHAR(20),message_type VARCHAR(20),sn VARCHAR(48),bw VARCHAR(1000),record_time DATETIME,"
+        		+ "info_01 VARCHAR(200),info_02 VARCHAR(200),info_03 VARCHAR(200))";
+//        statement.executeUpdate("drop table if exists bw_record");//判断是否有表tables的存在。有则删除
+        statement.executeUpdate(sqlEquipment);
+
+		// 创建 提交记录表
+		String submitRecord = "CREATE TABLE if not exists submit_record(\n" +
+				"   id INTEGER PRIMARY KEY AUTOINCREMENT, -- 自增ID\n" +
+				"   oprno VARCHAR(20),                    -- 工位号 \n" +
+				"   sn VARCHAR(48),                       -- 二维码\n" +
+				"   bw VARCHAR(1000),                     -- 报文 \n" +
+				"   record_time DATETIME,                 -- 记录时间\n" +
+				"   state CHAR(1)      -- 状态(0 ->未提交, 1 ->已提交)\n" +
+				")";
+		statement.executeUpdate(submitRecord);
+
+		// 底护板入库本地记录表(成功/失败/待重试都进这张表,按 server 区分)
+		String dhbKc = "CREATE TABLE if not exists dhb_kc_record("
+				+ "id INTEGER PRIMARY KEY AUTOINCREMENT,"
+				+ "server VARCHAR(8),"          // 520 / 610
+				+ "batch_sn VARCHAR(100),"      // 底护板码
+				+ "oprno VARCHAR(20),"          // 入库工位
+				+ "line_sn VARCHAR(8),"
+				+ "user_code VARCHAR(64),"
+				+ "state CHAR(1),"              // 0 待重试 1 已提交 2 服务端拒绝
+				+ "message VARCHAR(255),"
+				+ "record_time DATETIME)";
+		statement.executeUpdate(dhbKc);
+
+        statement.close();
+    }
+    
+    //插入数据
+    public static boolean insertData(String gw, String gy, String bw, String message_type, String sn) {
+    	boolean ret = false;
+    	String record_time = DateLocalUtils.getCurrentTime();
+		try {
+			// 确保连接已经打开
+			if (JdbcUtils.conn == null || JdbcUtils.conn.isClosed()) {
+				JdbcUtils.openConnection();
+			}
+			//创建连接对象,是Java的一个操作数据库的重要接口
+			Statement statement=conn.createStatement();
+			statement.executeUpdate("INSERT INTO bw_record (gw,gy,bw,record_time,message_type,sn) VALUES"
+					+ " ('"+gw+"', '"+gy+"', '"+bw+"', '"+record_time+"','"+message_type+"','"+sn+"')");//向数据库中插入数据
+			statement.close();
+			ret = true;
+		} catch (SQLException e) {
+			// TODO Auto-generated catch block
+			//e.printStackTrace();
+			ret = false;
+		}
+
+		return ret;
+    }
+
+	// 向 submit_record表 插入提交记录数据
+	public static boolean insertSubmitRecord(String oprno, String sn, String bw){
+		boolean ret = false;
+		String record_time = DateLocalUtils.getCurrentTime();
+		try {
+			// 确保连接已经打开
+			if (JdbcUtils.conn == null || JdbcUtils.conn.isClosed()) {
+				JdbcUtils.openConnection();
+			}
+			Statement statement=conn.createStatement();
+			String insertSQL = "INSERT INTO submit_record (oprno, sn, bw, record_time, state)" +
+					"VALUES('" + oprno + "', '" + sn + "', '" + bw + "', '" + record_time + "', '0')";
+			statement.executeUpdate(insertSQL);
+			statement.close();
+			ret = true;
+			log.info("向submit_record表插入数据成功: {}", insertSQL);
+		} catch (SQLException e) {
+			ret = false;
+			log.info("向submit_record表插入数据失败");
+		}
+		return ret;
+	}
+
+    
+    public static void close(){
+        try {
+        	if(conn!=null) {
+        		conn.close();
+        	}
+        } catch (SQLException e) {
+            e.printStackTrace();
+        }
+    }
+    
+    
+
+}
+ 
+

+ 27 - 0
src/resources/config/config.properties

@@ -0,0 +1,27 @@
+## \u5DE5\u4F4D / \u4EA7\u7EBF
+mes.gw=OP290A
+mes.line_sn=XT
+
+## \u53CC\u670D\u52A1\u5668\u914D\u7F6E\uFF08\u6309\u5E95\u62A4\u677F\u7801\u524D\u7F00\u8DEF\u7531\uFF09
+# 520 \u4EA7\u7EBF (P3, AY7-520)
+#mes.server.520.ip=127.0.0.1
+mes.server.520.ip=192.168.112.99
+mes.server.520.port=8980
+mes.server.520.oprno=OP285
+mes.server.520.prefixes=+KB77
+
+# 610 \u4EA7\u7EBF (P4, AY7-610)
+mes.server.610.ip=192.168.113.99
+#mes.server.610.ip=127.0.0.1
+mes.server.610.port=8980
+mes.server.610.oprno=OP290
+mes.server.610.prefixes=+KB78
+
+## \u4E3B\u767B\u5F55\u670D\u52A1\u5668\uFF08\u7528\u4E8E\u64CD\u4F5C\u5458\u9274\u6743\uFF0C\u5EFA\u8BAE\u6307\u5411\u4EA7\u7EBF\u65E5\u5E38\u4F7F\u7528\u6700\u9891\u7E41\u7684\u90A3\u4E2A\uFF09
+mes.login.server=610
+
+## \u517C\u5BB9\u5B57\u6BB5\uFF08\u4E0D\u518D\u4F7F\u7528\uFF0C\u4F46\u90E8\u5206\u4EE3\u7801\u4F9D\u8D56\uFF09
+#mes.server_ip=127.0.0.1
+mes.server_ip=192.168.113.99
+mes.tcp_port=3000
+mes.heart_beat_cycle=60

+ 44 - 0
src/resources/logback.xml

@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<configuration>
+
+    <!-- 定义日志文件的存储位置及文件名 -->
+    <property name="LOG_HOME" value="D:/Logs"/>
+    <property name="PROJECT_NAME" value="log"/>
+
+    <!-- 控制台输出 -->
+    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+        <encoder>
+            <pattern>%d{yyyy-MM-dd HH:mm:ss} %-5level  %logger{36} - %msg%n</pattern>
+        </encoder>
+        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
+            <level>INFO</level>
+        </filter>
+    </appender>
+
+    <!-- 文件输出 -->
+    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
+        <file>${LOG_HOME}/${PROJECT_NAME}/${PROJECT_NAME}.log</file>
+        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+            <fileNamePattern>${LOG_HOME}/${PROJECT_NAME}/archived/${PROJECT_NAME}_%d{yyyy-MM-dd}.log</fileNamePattern>
+            <!-- 保留最近 1个月内的日志-->
+            <maxHistory>30</maxHistory>  <!-- 如果删除该项配置,则logback无法自动删除旧日志, 导致totalSizeCap也将失去作用-->
+            <!-- 滚动文件总大小不超过1GB-->
+            <totalSizeCap>1GB</totalSizeCap>
+        </rollingPolicy>
+        <encoder>
+            <pattern>%d{yyyy-MM-dd HH:mm:ss} %-5level  %logger{36} - %msg%n</pattern>
+        </encoder>
+    </appender>
+
+    <!-- 根Logger -->
+    <root level="info">
+        <appender-ref ref="STDOUT" />
+        <appender-ref ref="FILE" />
+    </root>
+
+    <logger name="org.slf4j" level="ERROR"/>
+    <logger name="ch.qos.logback" level="ERROR"/>
+    <logger name="ch.qos.logback.classic.joran.action.ConfigurationAction" level="OFF"/>
+
+</configuration>