# django_admin_log

object_id: 2
object_repr: 超杰
action_time: 2023-07-28 16:20:43.747570
change_message:
  [{"changed": {"fields": ["Username", "First name", "Last name", "Email address", "Staff status", "Superuser status"]}}]


# django_session

session_key: az3xx3319qp38rlwyytqphx7w52qsy0c
expire_date: 2023-07-31 04:12:40.864281
session_data:
  .eJxNUktv00AQTtuEPEiLQHBDQkg5tCBZ9SOOc0AqSQ9IoRRRUlou1tq7a2_iR_DaLYmEBAdKkVZcMvxG_ga7TiplLWvnm5nd-ebb-V77W9mplOsb7ItdFxV56BacZC7DILZ0EI82fB7ypySRgQM8QUmQan6a5BnzNJWiraNcO0kxiQZ3uQ82LggRD0Ec9Xqmh6weceyuYer00NL1PrL7OtWdvqlbluM4tu1j0-jTrulhYnm2R2Q-7joWdWwDRCtKA5aUl4J4smaDPS1WpbmkwgmIvRK5RTJj_jQisARRVVzkNlYHb-AnfADV-D2X5ygnEDZEq2R_VsIlHPxYxRHGLAngt9jGHog6JhQVUQ6iTRmJMHd95IdEphae2JbKjbZEY4Y4v04zqcDFzJtiarg8REbX7tiHanXm1pvj3snH4SWZnp8mw3fH9vvhpd1B8eDzeTqmF2-vP3mn-eshm08HX-dx8iWcUWswDl5mIxovkgV2XikdEM_dUgwQDSxJ5ywmm-YShq36v_rDant_9-kfCJ-JhvIv0kTFJGwqKFvOkcSjivp-SVmUNDfyF23GXV7MSFaKfSsaak-QqlL3Q5ROmLRalGWSyMpdO5N6pyCaJbm1bzj31FvVSIxYBOLx-qhu9Y8C5ZKzFEveqliOKJWFmtJGfs6uiAT3VUPuJGUJwRA-L3va22uryYXwRUm0fjeeYVUO3Wom3CuScZZKcWqWZmimeiBVY6XYrdiJeQCiAoX2Hy3LH70:1qLFbE:LCQX4oH_ucbpqp7-VrSVfuEjW8g8sTv26f20cbaszTo


session_key: 0gm4cvq52f4mwaq3chncsxkg2ojpxwll
expire_date: 2023-07-31 04:41:35.084277
session_data:
  .eJxNUk1v00AQTdqEfBBagUDihhA5EJCs2nEc54BU4h6QQimipLRcrLV3N97EH8FrtyQSEhwoRdpbht_IT-DKrpNKWcvaeTOzO2_ezvfqn9JuqVjf4Lm456I8C9yck9RlGERZB_Fgy-chf0ZiGejgKYonieYncZYyT1Mp2ibKteMEk3B4m7u_dUGAeADisN_vesjsE9vqGV2dHpi6PkDWQKe6PejqpmnbtmX5uGsMaK_rYWJ6lkdkPu7ZJrUtA0QzTCYsLi4F8WjDBntapEpzSYUTEHsFcvN4zvxZSGAFoqK4yG2sDl7DT_gAqvE7Ls9QRiCoi2bB_rSAK-j8WMcRxiyewG-xgz0QNUwoysMMRIsyEmLu-sgPiEzNPbEjlRuVRX2OOL9KUqnA-dybYWq4PEBGz2pbB2q1F-abo_7xR-eCzM5OYufdkfXeubDaKBp-PkvG9Pzt1SfvJHvtsMVs-HURxV-COTWH48nLdESjZbzE9iulA-KZW4gBoo4l6YxFZNtcgdOs_a3dr3SelR__g-CJqCv_MolVTMKGgrLlDEk8Kqnvl5RFSXMtf9Fi3OX5nKSF2DeirvYYqSo1P0DJlEmrSVkqiazd1VOpdwKiUZDb-JyFp96qSiLEQhAPN0d1c3A4US45S5HkrYpliFJZqCFt5GfskkhwVzXkThMWEwzB06Knvb2WmlwIXhREa7fjGVTk0K1nwr0kKWeJFKdqaobWVQ-kaqwVuxG7EZ-AKEGu_Qeq_yAu:1qLG3D:tl-AAiRKgtuXzPl2-5pnDr2Jx3q5ZrBoF-OlfBebD44


session_key: mk414n8hq73n42s8edosz1r4gu26co20
expire_date: 2023-08-02 02:38:11.203764
session_data:
  .eJxNUktv00AQTtOGPFqCxOOMkCJEQbLqRxzngFSSHpBCKaKktFystXfX3sSP4LVbEgkJDpQi7S3Dj-EXceFHsOukUtaydr6Z2Z1vvp1vtd-V7Uq5vsIzcddFRR66BSeZyzCILR3E_Q2fh_wpSWRgH09QEqSanyZ5xjxNpWjrKNeOU0yiwW3uvY0LQsRDEIe9nukhq0ccu2uYOj2wdL2P7L5Odadv6pblOI5t-9g0-rRrephYnu0RmY-7jkUd2wDRitKAJeWlIB6t2WBPi1VpLqlwAqJdIrdIZsyfRgSWIHYUF7mN1cFr-AHvQTV-x-U5ygmEDdEq2Z-WcAn731dxhDFLAvglqtgDUceEoiLKQexRRiLMXR_5IZGphSeqUrnRlmjMEOdXaSYVOJ95U0wNl4fI6Nod-0Ctztx6fdQ7_jC8INOzk2T49sh-N7ywOygefDpLx_T8zdVH7yR_NWTz6eDLPE4-hzNqDcbBi2xE40WywM5LpQPiuVuKAaKBJemcxWTTXMKwVf9bf1B9ulv99wfCx6Kh_Is0UTEJmwrKlnMk8aiivp9SFiXNtfzFHuMuL2YkK8W-EQ21J0hVqfshSidMWi3KMklk5a6dSr1TEM2S3No3nHvqrWokRiwC8XB9VLf6h4FyyVmKJW9VLEeUykJNaSM_Z5dEgl3VkDtJWUIwhE_KntrtPTW5ED4vidZvxzPckUO3mgn3kmScpVKcmqUZmqkeSNVYKXYjtmMegKhAof0HNKYgqg:1qLx4t:uggX3a0YpkRcHTMCPgCp2glHHid7Fgg8xPk47ZgzUFE


session_key: a6rt5hsxj5ufe72zvr02548fwc7g6dw1
expire_date: 2023-08-02 06:47:04.045962
session_data:
  .eJxNUt1uEkEUppXyZ6WJPw9gQoy9WdkfFrgwqWI0pRYtFZXeTGZ3ZtiBZReZXRqamOiFpiYTbzg-mE_hC_gAzizUMDfn_5zvfHO-5H_93c1l7zM8lncQTpMApYLOEScgd0yQd7d8HvYnNFKBQzLG0Sg2_DhK5twzdIqxiQrjNCY0fH6Te7DVIMAiAHmErTrzWsw1GWswr-06pk3q7brTtm3i-I5FTc9vWi6ziOmYDddsNV2_5WDXsTzXazZskJUwHvEoawrywQYN8YypHi0UFEFBVjMLpdGM-5OQwgpkXmNRYqALv8M36INevIBEghMKQUlWMvTnmbmCw6_rOCaERyP4IXeJB7JIKMNpmIDcZ5yGRCAf-wFVqakndxVzJzuyNMNCXMZzxcDHmTchzEIiwFbDrVluXb3a2bL38qQ9evPa-vDJcS66Az60rxa1gTtt-a-WZ8PeaZLGi6E5e086i7fHwgn7bNg_vnjR7j7r2DjtXj7VPGCRoIwMkCWiQCd8SrfVFXQqxT_Fe4Un-dzP32r12TK5UgIN3nX0gn1FQ18twgUS6YzOM0qvZUnLCOteRT_A8ZgrrcL4XI1bu_fOFasxyHIGYePrLD39I3t0inkI8v6m1HTaRyPtUhczVej0sAQzpgaVlY79hC-oMm5r2Ggc84gSCB5myKvVfX2fEDzKgJbRf2J7snhzkUFe3dn6DNCCzgWPFR8F27AMq64_RU9cs3Qtb03FCGQOUuMfxPAcAQ:1qM0xk:4MXU4oolC4XBbGl7P4jxyOB2Ld4A1zAs_9GszOkQLr8


session_key: ejbko3l3erwn10ig80jn16aaxspahwh2
expire_date: 2023-08-02 06:49:52.918912
session_data:
  .eJxNUttu00AQbUPapKFNJS4fgBQh-mLFlzjxA1IhCNSUFpoSIH2x1t5dexPfyNqpUgkJHkBF2rcMP8cP9DfYdVIp-zLXnTlzZn5U_95Vtsr3HV6IAxcVeegWnMxchkFs6yAebfg85E9JIgNHeIKSINX8NMlnzNNUiraOcu0sxSR6fZ97uFEgRDwEcYyMNvV61NYp7VDPsS3dxG2nbTmmiS3fMoju-V3DpgbWLb1j672u7fcsZFuGZ3vdjgmiEaUBS8qiIJ6u0WBPi1VrLqFwAqJZWm6RZMyfRgSWIKoKixQj9fE3_IIhqMF3XZ6jnEBYF40S_WVpLuHo5yqOMGZJAH9EBXsgaphQVEQ5iH3KSIS56yM_JDK18ERFMne6LeoZ4vw6nUkGvmbeFFPD5SEyOnbLsNvytS4W529PneDDe-PLN8u6GozY2LyZt0Z23PPfLS7G52d5kc7HevYZ9-cfT7gVDel4eHL1xhm86puoGFy_VDwgnrslGSDqWILOWUw21SX0G7W72uNd3Tr4pxjIFvmNFO7oU18NOJQ0DOUgjLu8yMispPRW1JVMkKpV80OUTpjUGpTNZLuVe-dSspqC2CshrH39hac2skNixCIQT9Zfdcs5DpRLXkws0almOaJUNtqTOvJzNifSeKhgu5OUJQRD-KxE3mzuq_uE8HkJtHZ_hGFVntZq8-6czDhLJQW7pmZoRlvtQTVZEXMrHsQ8ALEFhfYfiSYW-A:1qM10S:uYusIXG_hzsf-UuN7TTdN90G9HIq59YKDu0jL05GgqQ


session_key: qeznqizecf179nlzg99rzx14leriypsr
expire_date: 2023-08-09 02:52:10.442709
session_data:
  .eJxNUs1v0zAU38pouzKGhODClR42CUVN4qbNAWlQBFrHBuso0F0iO7YTt_kiTjp1EhIcQEPyrebKv7d_AzvtpPryvt_7vZ_fj52_t7Wt6n2XB-JezAMptqR46MGyCL2Sk9xjWIptU4rHGz4E_RlJVOAQT2ESpIafJkXOkKFTjHWUG6cpJtHru9xHGw1CyEMpjqDVoahPHZPSLkWuA0wbd9wOcG0bAx9YxER-z3KohU1gdh2z33P8PoAOsJCDel1bilaUBiypmkrxdI0GIyPWo7mCwokU-5XllUnG_FlE5FKKHY1FibEu_C1_yZHUDNQ9XsCCyLAlWhX6i8pcysOfqzjEmCWB_CNqGEnRwITCMiqk2KOMRJh7PvRDolJLJGqKuZNt0cwg51dprhj4mqEZppbHQ2h1nbbldNRrny_O3p64wYf31pdvAFwOx2xiX8_bYyfu--8W55Oz06JM5xMz-4wH84_HHEQjOhkdX75xh68GNiyHVy81D5AXXkWGFE2sQBcsJpvqUg5ajdvGsxpo1f-9UKtni-JaCW_8aaAXHCkaRmoRxj1eZiSvKL0RTS0TqHs1_BCmU6a0FmW5Grdy3x8skGZ_t4Kw9l0oplMlSQxZJMWTdakJ3KNAu9TFxAqdHlZAStWgXaVDv2BzoowHGrY3TVlCsAyfV8j39_f0ocrwoALauDvCsK5Oa_Xz3pzknKWKgrptWIbV0f-gh6yIuSmN_01iFjI:1qOUdG:b7Xhsj2HaJReZ6w_WvXNu70PdVEZauW3PzGG5MmBKPI


session_key: r8y7xr3l1pq3wm11g5wgxw48cbpq5oav
expire_date: 2023-08-17 01:30:17.378895
session_data:
  .eJxNUltv0zAUXrtu7croBIJXJKQKsZeouTRtHpAGRaB1bLCOAt2L5cR24jY34qRTJyHBA2hIfqsRf4t_sb-BnXZS_XLu53zn8_le-3Nb3SrfN_Gc3wewyANQMJwBigSv6II_3PC50JvhWAYO0RTGfqJ5SZxn1NVUiraOMu00QTh8dZd7sNEggCwQ_AgaHeL2ia0T0iWuY1u6iTpOx3JME1meZWDd9XqGTQykW3rX1vs92-tb0LYM13Z7XVPwZpj4NC6bCv54jQa5WqRGMwmFYcFbpQWKOKXeLMRiKXhNYZFirAp_iZ9iJNTiu4DlMMciaPBmif6iNJfi8McqDhGisS9-8ypyBa8jTGAR5oLvE4pDxIAHvQDL1MLlVcncSYU3UsjYVZJJBr6k7gwRA7AAGl27bdgd-drni7M3J47__p3x-atlXQ7HdGJez9tjO-p7bxfnk7PTvEjmEz39hAbzD8fMCkdkMjq-fO0MXw5MWAyvXigeIMtBSYbgDSRB5zTCm-pSDJr128Z25cmDnb__5OrpIr-WAow_DtSCI0nDSC5CGWBFirOS0hveUDKGqlfdC2AypVJrEprJcSv3zmDhKvb3Sghr34VkOpESR5CGgj9al-qWc-Qrl7yYSKJTw3JIiBy0J3Xo5XSOpXFPwQbThMYYieCpQl5vtfbVfYrgWQm0fneEQU2e1urnwRxnjCaSgl1TMzSjo_5BDVkRc8O3I-YLviUK7T8urBag:1qRNAP:vu_bZW1BZ99ivmkC2NMmvqrL-IokAuUKFVg1LyY11t0


session_key: ung8wx257ubtxxm8fujx5dnmiu82thjv
expire_date: 2023-08-23 14:21:55.749287
session_data:
  .eJxNUltv0zAU3kZZLxudhNgPQKoQe4kaJ02bB6RBEWgdG6ylQPcS2bHduM2NOOnUSUjwABqS32p-4_4GdtpJ9cu5n_Odz-dn5d_93k75fsiX4okHizzwCk4yj2Epdk0pnm75EPTnJFaBEzyD8TQx_CTOM4YMnWJsoty4SDAJ3zzkHm01CCAPpDiFoE1RjzompR2KXMc2Ldx227ZrWdj2bUBM5HeBQwE2bbPjmL2u4_ds6NgAOajbsaRohMmUxWVTKY43aDAyIj2aKyicSNEsLa-IU-bPQyJXUlQ0FiXGuvCP_C2HUi--7_Ec5kQGNdEo0Y9KcyVPfq3jEGMWT-VfsYeRFFVMKCzCXIpDykiIuedDPyAqtUBiTzF3vitqKeT8JskUA99SNMcUeDyAoOO0gNNWr3W1vHx37k4_fgBfv9v29WDMJtbtojV2op7_fnk1ubzIi2QxMdMvuL_4dMbtcEgnw7Prt-7gdd-CxeDmleYB8twryZCihhXonEVkW13JfqN6X6s3j7sHI6BWT5f5rRLe-HNfLzhUNAzVIox7vEhJVlJ6J2paxlD3qvoBTGZMaQ3KMjVu7X7cXyLNfr2EsPGNFNOJkiSCLJTi2abUtN3TqXapi4kUOj0sh5SqQXWlQz9nC6KMAw3bmyUsJlgGzzXyarN5qO9TBi9KoNWHIwwq6rTWP-8tSMZZoijYtwxggLb-Bz1kTcydeBTxqRQ7sjD-A4HAFfQ:1qTk4R:ipsauiSLJYnTltCTiLk4Hp8Z2nyBOnsqqjlysPunVlw


session_key: 2ohxwvtyhd7j1sbhhfoe54s3edg0r0kg
expire_date: 2023-08-23 14:31:02.476071
session_data:
  .eJxNUs1v0zAU38po2jI6CcGZAxXaLlHz0bQ5IA2KQOvYYB0Fuktkx3biNl_ESadOQoIDaEi-1fyN-zew006qL-_7vd_7-f3c-3dX26neD3HIH8QsEHxH8MceKIvQKxnOPYoE3zUEf7Llg8Cf40QGjtAMJEGq-2lS5BTqKkXfRJl-liIcvbnPPdhqEAIWCn4MzC6BA-IYhPQIdB3bsFDX7dquZSHbt01sQL9vOsREhm30HGPQd_yBDRzbhA7s9yzBW1Ea0KRqKvizDRoE9ViNZhIKw4K3K8srk4z68wiLleB7CosUE1X4R_wWY6EYqHusAAUWYYu3KvSXlbkSR7_WcYAQTQLxl9cQFFxDmIAyKgTfJxRHiHk-8EMsU0vIa5K5013eyABj12kuGfiWwTkipsdCYPacjul05etcLM_fnbrBxw_m1--2fTWa0Kl1s-hMnHjgv19eTM_PijJdTI3sCxouPp0wOxqT6fjk6q07ej20QDm6fqV4AKzwKjIEbyAJuqAx3lZXYtjS7hrN9vOa9rIuV8-WxY0U3uTzUC04ljSM5SKUeazMcF5RessbSiZA9dL8EKQzKrUWobkct3Y_HC6hYr9ZQdj4LiXTqZQ4BjQS_Omm1LDd40C55MXEEp0aVgBC5KCm1IFf0AWWxiMF25ulNMFIhC8Ucq3d3leHKsLDCqh2f4RhXZ7W-ue9Bc4ZTSUFdUs3dbOr_kENWRNzW-r_AZOAFXQ:1qTkDG:zp8JlPaQDZeS7mgpd-T-_svlweWv73_QHeA98bjkcSs


session_key: zu8bn1ek0kzvhotz9aayw8zj4h92io9y
expire_date: 2023-08-24 06:29:21.431686
session_data:
  gASVJQAAAAAAAAB9lIwDbXNnlIwY55So5oi35ZCN5a-G56CB5LiN5q2j56GulHMu:1qTzAf:4orsPRmMWSa2Mh801l0-C3ChqMDoWbck4_CbwVIXw78


session_key: 0vhvc61scrrxwh1pnc2czjxg38jhcse9
expire_date: 2023-08-25 12:41:21.069223
session_data:
  .eJxNUt1u0zAU7kpZu650EmIPgFQhdhM1P02bC6RBEWgdG6yjQHcT2bGduM0fcdKpk5DgAjQk39U8BE-2S14BO-2k-ub8n_Odz-d77c9dtVK-b-I5f-SCIg_cguHMpUjwHV3wx1s-CLw5jmXgCM1A7Ceal8R5RqGmUrRNlGlnCcLhq_vcg60GAWCB4MfA6BI4ILZOSI9Ax7Z0E3WdruWYJrI8y8A69PqGTQykW3rP1gd92xtYwLYMaMN-zxS8GSY-jcumgh9u0CCoRWo0k1AYFrxdWm4Rp9Sbh1isBK8pLFJMVOEv8VOMhVp812U5yLEIGrxZor8szZU4-rGOA4Ro7IvfvIqg4HWECSjCXPAWoThEzPWAF2CZWkBelcyd7vBGChi7TjLJwJcUzhExXBYAo2d3DLsrX-dief7m1PHfvzM-f7Wsq9GETs2bRWdiRwPv7fJien6WF8liqqef0HDx4YRZ4ZhMxydXr53Ry6EJitH1C8UDYLlbkiF4A0nQOY3wtroSw2b9rrHfOjqs_PsrV0-X-Y0U7uTjUC04ljSM5SKUuaxIcVZSessbSsZA9ap7AUhmVGpNQjM5bu1-OFxCxf5eCWHju5RMJ1LiCNBQ8CebUt1yjn3lkhcTSXRqWA4IkYP2pA68nC6wNPYVbHeW0BgjETxVyOvtdkvdpwielUDr90cY1ORprX_eXeCM0URSsGtqhmZ01T-oIWtibvmDiPmCV0Sh_Qd-NRbu:1qURSD:jQjzRNvLhBdiHBmVUcTulxEGf-sg6b4XGJq25_997FY


session_key: he6eb5h2wthv3h7jm6aq7cq6lak7riki
expire_date: 2023-08-25 12:46:48.689130
session_data:
  .eJxNUltv0zAU3kpZu64UCcEPQKoQe4maS9PmAWlQBNrGBuso0L1YdmwnbnMjTjp1EhI8gIbkt5rfuL-BnXZS_XLu53zn8_lZ_3dX26neD_lSPAKwLEJQcpIDhqXYNaV4suVD0J-TRAUO8QwmQWr4aVLkDBk6xdhEuXGWYhK9uc99vNUghDyU4ghaPYqG1DUp7VPkuY5p457Xczzbxo7vWMRE_sByqYVNx-y75nDg-kMHuo6FXDTo21K0ojRgSdVUimcbNBgZsR7NFRROpOhUFiiTjPnziMiVFHWNRYmJLvwjf8ux1IvvAV7AgsiwKVoV-svKXMnDX-s4xJglgfwrahhJ0cCEwjIqpGhTRiLMgQ_9kKjUEomaYu50VzQzyPl1misGvmVojqkFeAitvtu13J563Yvl-btTL_j4wfr63XGuTiZsat8suhM3HvrvlxfT87OiTBdTM_uCR4tPx9yJxnQ6Pr566528HtmwPLl-pXmAvAAVGVI0sQJdsJhsqys5ajXumgdto9cKoFo9WxY3SoDJ55FecKxoGKtFGAe8zEheUXormlomUPdq-CFMZ0xpLcpyNW7tfjhaIs3-fgVh47tUTKdKkhiySIqnm1LT8Y4C7VIXEyt0elgBKVWD9pUO_YItiDIONGwwS1lCsAyfa-SNTqet71OGLyqgjfsjDOvqtNY_DxYk5yxVFOzZhmVYPf0PesiamFvxIOaBFDuyNP4D1ssWSA:1qURXU:y4vgAsChxKAtsmxkRpt8A2ZsETSYR7n_fKLwrR-QzXU


session_key: 4wktulgs3zory66mus3mdxirqbtszntf
expire_date: 2023-08-27 12:13:20.185828
session_data:
  .eJxNUt1u0zAU3kpZu9J2EhoPgFQhdhM1P02bC6RBEWgdG6yjQHcT2bGduM0fcdKpk5DgAjQk39U8CE-1R-AWO-2k-ub8n_Odz-d79c9dZad838Rz3nJBkQduwXDmUiT4ri744y0fBN4cxzJwhGYg9hPNS-I8o1BTKdomyrSzBOHw1X3uwVaDALBA8GNgdAkcEFsnpEegY1u6ibpO13JME1meZWAden3DJgbSLb1n64O-7Q0sYFsGtGG_ZwreCBOfxmVTwZ9s0CCoRWo0k1AYFrxdWm4Rp9Sbh1isBK8qLFJMVOEv8VOMhVp8z2U5yLEI6rxRor8szZU4-rGOA4Ro7IvfvIKg4DWECSjCXPAmoThEzPWAF2CZWkBekcyd7vJ6Chi7TjLJwJcUzhExXBYAo2d3DLsrX-dief7m1PHfvzM-f7Wsq9GETs2bRWdiRwPv7fJien6WF8liqqef0HDx4YRZ4ZhMxydXr53Ry6EJitH1C8UDYLlbkiF4HUnQOY3wtroSw0btrt5qtg4rf__J1dNlfiOFO_k4VAuOJQ1juQhlLitSnJWU3vK6kjFQvWpeAJIZlVqD0EyOW7sfDpdQsb9fQtj4LiXTiZQ4AjQU_HBTqlvOsa9c8mIiiU4NywEhctC-1IGX0wWWxiMF250lNMZIBE8V8lq73VT3KYJnJdDa_REGVXla6593FzhjNJEU7JmaoRld9Q9qyJqYW_4gYr7gO6LQ_gN0ghbl:1qV9yC:j61KjgpgL4zre70M-BcJHU4uYBeDFE2O_IKrNTiR7LY


session_key: 9ms9l3u4yd38hlgp636dgyx3wum4jvn3
expire_date: 2023-09-01 08:13:05.369427
session_data:
  gASVJQAAAAAAAAB9lIwDbXNnlIwY55So5oi35ZCN5a-G56CB5LiN5q2j56GulHMu:1qWubR:mMdLH5-DDDAu797mljjIhrffeVM2WhAclU8ddbvAozo


session_key: 2btg7hz45hrdj8txppm6ecuyx4mormsn
expire_date: 2023-09-01 08:13:10.914086
session_data:
  gASVJQAAAAAAAAB9lIwDbXNnlIwY55So5oi35ZCN5a-G56CB5LiN5q2j56GulHMu:1qWubW:2MkgmSj359ej4JJ2Q0K1s2xKEYy6KgRgFKLxGqYCIEQ


session_key: 6uf9rjvt5n9342koy1413hgkhpsnckl9
expire_date: 2023-09-01 08:13:14.893199
session_data:
  gASVJQAAAAAAAAB9lIwDbXNnlIwY55So5oi35ZCN5a-G56CB5LiN5q2j56GulHMu:1qWuba:J2ReOjQ5__ZZbw8p3wLy-y83OxUhavAfh9Djb9ectFU


session_key: uw34hlwj0yh6cbvxr3km77nk9iq5riog
expire_date: 2023-09-01 08:13:20.644563
session_data:
  gASVJQAAAAAAAAB9lIwDbXNnlIwY55So5oi35ZCN5a-G56CB5LiN5q2j56GulHMu:1qWubg:PR2AWQU0BPqV8hhRDKowMqPpHH5zUS1qldMTDrVRvKQ


session_key: 7lavo9vz417t1b74m61bttz73wdab4jp
expire_date: 2023-09-01 08:13:27.997244
session_data:
  gASVJQAAAAAAAAB9lIwDbXNnlIwY55So5oi35ZCN5a-G56CB5LiN5q2j56GulHMu:1qWubn:WbpPOFnqduMpNxA9pF_yaYtH7YeX41h4IouZTbWBTOM


session_key: f5mk2zbo9shuckng3ir002vxtdh8aemt
expire_date: 2023-09-01 08:13:31.628565
session_data:
  gASVJQAAAAAAAAB9lIwDbXNnlIwY55So5oi35ZCN5a-G56CB5LiN5q2j56GulHMu:1qWubr:DaOWLeQK-VuNbsNVpNso7hrge2bc9gcPYwtqauVnzck


session_key: qwgtuqzu8hil9w135xz0l8nn1s69x9eo
expire_date: 2023-09-01 08:13:37.603803
session_data:
  gASVJQAAAAAAAAB9lIwDbXNnlIwY55So5oi35ZCN5a-G56CB5LiN5q2j56GulHMu:1qWubx:HcfM7oXl7hvL51e8W7KPh_Q3p-uIu-zNF16OkdYEDAI


session_key: ebgzj336m1ahghzu4zhqyo68kuyexlsl
expire_date: 2023-09-01 08:13:41.583338
session_data:
  gASVJQAAAAAAAAB9lIwDbXNnlIwY55So5oi35ZCN5a-G56CB5LiN5q2j56GulHMu:1qWuc1:rNS2tWdLThRJ9xZns1T4ZlSgz09aLWrlAmyn6UlipPE


session_key: 0gig524qwszrcvgzmiwclzce8x1zfios
expire_date: 2023-09-01 08:13:51.436178
session_data:
  gASVJQAAAAAAAAB9lIwDbXNnlIwY55So5oi35ZCN5a-G56CB5LiN5q2j56GulHMu:1qWucB:kL40tTEbq2rzsFStUsO6ecLLpPPngEp4gGOp5dci8Qo


session_key: 3qn53hhhg6a7eb8kq3644wnohoyipx51
expire_date: 2023-09-01 08:13:55.171567
session_data:
  gASVJQAAAAAAAAB9lIwDbXNnlIwY55So5oi35ZCN5a-G56CB5LiN5q2j56GulHMu:1qWucF:4_hU86kTgt5XuLtwCPZdFAjAB1EQkXQGoCPy6oK4t-k


session_key: avbyd319tt813wsgt9dx8bqsve5wi3l6
expire_date: 2023-09-01 08:14:01.555687
session_data:
  gASVJQAAAAAAAAB9lIwDbXNnlIwY55So5oi35ZCN5a-G56CB5LiN5q2j56GulHMu:1qWucL:Dby3aKRO-VFKanQVCYPll5kKx9Tb4OUix4KvuqFKZyE


session_key: dk50hn87jk8tk8s1ga3uivoet7360zea
expire_date: 2023-09-01 08:14:05.829500
session_data:
  gASVJQAAAAAAAAB9lIwDbXNnlIwY55So5oi35ZCN5a-G56CB5LiN5q2j56GulHMu:1qWucP:pJODJHX0s92pml_KhuI6EBYw07NqgKmWlwtM3NPikG0


session_key: 01sbv55g5g1birs0s91itqq3dy0ahrwv
expire_date: 2023-09-01 08:14:13.900238
session_data:
  gASVJQAAAAAAAAB9lIwDbXNnlIwY55So5oi35ZCN5a-G56CB5LiN5q2j56GulHMu:1qWucX:LZTsi1yh1GxleH2y9vz5O6a4Jp2ZvcYNhO8ZXU1lPXY


session_key: pkvtolpp307eyluzfwpu6utonsu49cpp
expire_date: 2023-09-01 08:14:23.427708
session_data:
  gASVJQAAAAAAAAB9lIwDbXNnlIwY55So5oi35ZCN5a-G56CB5LiN5q2j56GulHMu:1qWuch:Ms-oZ5JPEorcRh2KDpISEVy_rupu9nUl8ainsA57jls


session_key: p4uvusy9h1088aay8lqi36xe0sdspu5q
expire_date: 2023-09-01 08:14:25.703752
session_data:
  gASVJQAAAAAAAAB9lIwDbXNnlIwY55So5oi35ZCN5a-G56CB5LiN5q2j56GulHMu:1qWucj:zCrKBbysplpGMCMSG4vhUDlNsuZVmmvxF8ZKCzT7q8I


session_key: qqy9jvp1wwjao1yv6fu047i614htfk86
expire_date: 2023-09-01 08:14:35.514629
session_data:
  gASVJQAAAAAAAAB9lIwDbXNnlIwY55So5oi35ZCN5a-G56CB5LiN5q2j56GulHMu:1qWuct:EJjsLM9xAQ19plpt9852I9SGHgK_8ZfdhQn0VCKnAJI


session_key: xbygh2rrk7029ffbdqkgx3pmj1c5ihoc
expire_date: 2023-09-01 08:14:36.136693
session_data:
  gASVJQAAAAAAAAB9lIwDbXNnlIwY55So5oi35ZCN5a-G56CB5LiN5q2j56GulHMu:1qWucu:QeIFTehsillP3oeJ9eL6_kvhycRWgWkXDTPLulL8LsY


session_key: yfyfjeo7p4fcq4ejaz4t7quucdrtksbs
expire_date: 2023-09-01 08:14:45.778774
session_data:
  gASVJQAAAAAAAAB9lIwDbXNnlIwY55So5oi35ZCN5a-G56CB5LiN5q2j56GulHMu:1qWud3:DO-9lMHLN91fKL890Re8InXK8BqPSI4qa373NEKsxow


session_key: 21pbc61byhsmdnlsqg5bxdpsg2uhwm67
expire_date: 2023-09-01 08:14:48.005083
session_data:
  gASVJQAAAAAAAAB9lIwDbXNnlIwY55So5oi35ZCN5a-G56CB5LiN5q2j56GulHMu:1qWud6:xtqunvSdp_5uQ7_65ja-8KftkFk2Gzc_52jglsi9rRc


session_key: 10rcwmjbs9m7d3ytcphbbvqoes23ovfm
expire_date: 2023-09-01 08:14:57.507711
session_data:
  gASVJQAAAAAAAAB9lIwDbXNnlIwY55So5oi35ZCN5a-G56CB5LiN5q2j56GulHMu:1qWudF:boY-_ZODFmKJQ_EGlXQUt81IC59fn5LgvFvvirS9-Hk


session_key: 7igploe3qvah5r73r68j9zw7ewit9lc5
expire_date: 2023-09-01 08:15:06.988201
session_data:
  gASVJQAAAAAAAAB9lIwDbXNnlIwY55So5oi35ZCN5a-G56CB5LiN5q2j56GulHMu:1qWudO:Z4ACyOxol6J43d-uNBgcC7Z0e8DG97xhIFS9ehRxpCU


session_key: ygosxy4z8czgddnp7ab5iurioufl68h7
expire_date: 2023-09-01 08:15:12.514708
session_data:
  gASVJQAAAAAAAAB9lIwDbXNnlIwY55So5oi35ZCN5a-G56CB5LiN5q2j56GulHMu:1qWudU:vEO1Ww4-4HJmXx6hvZxblVuN7L1P_9KZCV_Wqz2CSzY


session_key: 0wlv787lr7291emnamui209teqbvmny2
expire_date: 2023-09-01 08:15:19.192024
session_data:
  gASVJQAAAAAAAAB9lIwDbXNnlIwY55So5oi35ZCN5a-G56CB5LiN5q2j56GulHMu:1qWudb:oOAHlOyt8rhudu8coMImWwRkb8UUX06aVmuJMKeEf24


session_key: nd04jigquz1zubq307aa7wczewat1uco
expire_date: 2023-09-01 08:15:24.068651
session_data:
  gASVJQAAAAAAAAB9lIwDbXNnlIwY55So5oi35ZCN5a-G56CB5LiN5q2j56GulHMu:1qWudg:hn1WjlOJ8cnzmCSXDh1Zk8muA7q4kcEKOj8vqPd23xk


session_key: 6jfrrh3g4hycxk9yvee29nlyq3bc299k
expire_date: 2023-09-01 08:15:28.674910
session_data:
  gASVJQAAAAAAAAB9lIwDbXNnlIwY55So5oi35ZCN5a-G56CB5LiN5q2j56GulHMu:1qWudk:clQALqmkhmxuxBrzINyWbi8N8G4At9-bkfuAVB_eDy0


session_key: 0b1wz7v0ugdz824o7e2s1ke6sb8bndkq
expire_date: 2023-09-01 08:15:33.136232
session_data:
  gASVJQAAAAAAAAB9lIwDbXNnlIwY55So5oi35ZCN5a-G56CB5LiN5q2j56GulHMu:1qWudp:0zz3S_8F9O1RMAwrDpBUksw7vfW9c91qZQ0YcUQLDXY


session_key: dgf3y5uxouxxaioanf3rvx6pjx781wgh
expire_date: 2023-09-01 08:15:41.980660
session_data:
  gASVJQAAAAAAAAB9lIwDbXNnlIwY55So5oi35ZCN5a-G56CB5LiN5q2j56GulHMu:1qWudx:4-F3qwXMo-uyCr6dXPLraBUesrgZyx5-W3QdUQR_w5E


session_key: v3d4c72d715pmkkn117rbaahkyj2fah2
expire_date: 2023-09-01 08:15:43.110348
session_data:
  gASVJQAAAAAAAAB9lIwDbXNnlIwY55So5oi35ZCN5a-G56CB5LiN5q2j56GulHMu:1qWudz:Nn9IJaau_JdGkXmxVENifTycXTghLDhRHPuq2sJkg-8


session_key: wwno3qddil2jhs6yuzcwiqrz9cuih732
expire_date: 2023-09-01 08:15:51.500722
session_data:
  gASVJQAAAAAAAAB9lIwDbXNnlIwY55So5oi35ZCN5a-G56CB5LiN5q2j56GulHMu:1qWue7:Ux9KZE__TF7O4edfTlq_u4jdMIGftpoICfmlc6OrQac


session_key: 1tjp6omsq928hn1ko4qz2n6phzpxj1w1
expire_date: 2023-09-01 08:15:56.483987
session_data:
  gASVJQAAAAAAAAB9lIwDbXNnlIwY55So5oi35ZCN5a-G56CB5LiN5q2j56GulHMu:1qWueC:-8dHTnj_6yiXvctZC6vKjFjgoDtgrZFW1__DH-ANcPI


session_key: 5w4saqkmz9lflcib9lv8skbkqptsaxlh
expire_date: 2023-09-01 08:16:03.318677
session_data:
  gASVJQAAAAAAAAB9lIwDbXNnlIwY55So5oi35ZCN5a-G56CB5LiN5q2j56GulHMu:1qWueJ:rosCRTPSRKyJ87Zvnkq6QFaJP5nneA8tnZPAUKfdF7A


session_key: zqer0vp5k6dyyg4qq4ygh9gab2gsygz3
expire_date: 2023-09-01 08:16:06.285918
session_data:
  gASVJQAAAAAAAAB9lIwDbXNnlIwY55So5oi35ZCN5a-G56CB5LiN5q2j56GulHMu:1qWueM:M2yWhD9kOec135VLYS-vcadkL0gHINrkuvYnUA0ZSKM


session_key: oo5sv52bs64e2lb8mldobslpaqh7caq6
expire_date: 2023-09-01 08:16:12.794902
session_data:
  gASVJQAAAAAAAAB9lIwDbXNnlIwY55So5oi35ZCN5a-G56CB5LiN5q2j56GulHMu:1qWueS:YOf4Es8qAlhZsYUiJsqgu0NuENvEKMtDKGuC3-6dzPQ


session_key: 4xy5njczxwlghvfyvppcijjh3rlb14hh
expire_date: 2023-09-01 08:16:19.302568
session_data:
  gASVJQAAAAAAAAB9lIwDbXNnlIwY55So5oi35ZCN5a-G56CB5LiN5q2j56GulHMu:1qWueZ:y1b_LiLTw2upS7pKPlnmnb3cLlbfbBY6EGSH6AsXfJk


session_key: 4uyky0zdm8w6efb1m28qdoqb2nkj8jz5
expire_date: 2023-09-01 08:16:25.272658
session_data:
  gASVJQAAAAAAAAB9lIwDbXNnlIwY55So5oi35ZCN5a-G56CB5LiN5q2j56GulHMu:1qWuef:kfbAdI1z862g94ocwk9uDS3DC3NIbzIKPIzbjLKRNkY


session_key: 73jk7lh0s31hneoyuscbddkvuy0zaato
expire_date: 2023-09-01 08:16:29.103649
session_data:
  gASVJQAAAAAAAAB9lIwDbXNnlIwY55So5oi35ZCN5a-G56CB5LiN5q2j56GulHMu:1qWuej:43kdf2E8629A4FQtwchKCqe4QhhGMafk3e8fgxTFtbs


session_key: d8qsbd1hjusnd6nv4jkah0war1fe5xxt
expire_date: 2023-09-01 08:16:34.944064
session_data:
  gASVJQAAAAAAAAB9lIwDbXNnlIwY55So5oi35ZCN5a-G56CB5LiN5q2j56GulHMu:1qWueo:7ZLyr5hH8mwf9-iM5EBS9DjTjYd1eWgJ0gT2-tUlNR4


session_key: fzpqfqtkpqs4pdvmeiihm89rosgnc6yu
expire_date: 2023-09-01 08:16:40.933280
session_data:
  gASVJQAAAAAAAAB9lIwDbXNnlIwY55So5oi35ZCN5a-G56CB5LiN5q2j56GulHMu:1qWueu:zyeFW425riksqS_2RVr6dAos88QTof_zAI-Ra5RSi0k


session_key: kv8djcye9zgvlchwihrela57j33ckpys
expire_date: 2023-09-01 08:16:50.575060
session_data:
  gASVJQAAAAAAAAB9lIwDbXNnlIwY55So5oi35ZCN5a-G56CB5LiN5q2j56GulHMu:1qWuf4:ob_0bw0roWMONIP6ZhJEXC6prQDbAxFBtwAntnbomR8


session_key: ko8mdv8qoe4onwu0a3dic7mq9ldxklag
expire_date: 2023-09-01 08:17:03.664971
session_data:
  gASVJQAAAAAAAAB9lIwDbXNnlIwY55So5oi35ZCN5a-G56CB5LiN5q2j56GulHMu:1qWufH:YbFhGjRIYpsB67pJYod7JMiitSTlZv-3gP_ZNTPRLCU


session_key: s7b3ct8yqr1evepq6jmrtpfgfrp07gyw
expire_date: 2023-09-01 08:17:17.214727
session_data:
  gASVJQAAAAAAAAB9lIwDbXNnlIwY55So5oi35ZCN5a-G56CB5LiN5q2j56GulHMu:1qWufV:h25X4mOZqI4-2tnMD5orSXlntlS7M3s3BJR_N7CYhj8


session_key: j46us8yz40isoqn8opigbeaznz7b0y8v
expire_date: 2023-09-01 08:17:27.444479
session_data:
  gASVJQAAAAAAAAB9lIwDbXNnlIwY55So5oi35ZCN5a-G56CB5LiN5q2j56GulHMu:1qWuff:47lB1oqqXll4s1PYGshcAlvoUmzMgtfmV_f8f5HQng8


session_key: clh3btfbwgba45sebrnrqeinsrwc1jf1
expire_date: 2023-09-01 08:17:37.104767
session_data:
  gASVJQAAAAAAAAB9lIwDbXNnlIwY55So5oi35ZCN5a-G56CB5LiN5q2j56GulHMu:1qWufp:4BtecM7bRaOLrjExugGuEVByCVEKw-KgDiPLZ6wSlug


session_key: jubf0qbcn852uxqeva5r6e5a0xzt2x38
expire_date: 2023-09-01 08:17:48.136306
session_data:
  gASVJQAAAAAAAAB9lIwDbXNnlIwY55So5oi35ZCN5a-G56CB5LiN5q2j56GulHMu:1qWug0:SC9xhi6YYnE1GB93pWc-ZRCZ8uX4YkiNMSLhCBADR90


session_key: 2jly0pq4p0vb3yon8cq3cnlew8zum3d4
expire_date: 2023-09-01 08:17:58.557745
session_data:
  gASVJQAAAAAAAAB9lIwDbXNnlIwY55So5oi35ZCN5a-G56CB5LiN5q2j56GulHMu:1qWugA:lZpaYBwgP9LG3MS-OK9hoz2pZAm6E_gYErAwSYGDpeY


session_key: m1x8y3bytkzwu72xjuf68i4i2wkj7cyg
expire_date: 2023-09-01 08:18:12.354102
session_data:
  gASVJQAAAAAAAAB9lIwDbXNnlIwY55So5oi35ZCN5a-G56CB5LiN5q2j56GulHMu:1qWugO:kei8_bhBf8BKK9hqFincHXRJmYh-2HaJPpD2OaU-IXI


session_key: pmlzwc4r28d2i915wfcpyv55olvnqsxo
expire_date: 2023-09-01 08:18:22.109448
session_data:
  gASVJQAAAAAAAAB9lIwDbXNnlIwY55So5oi35ZCN5a-G56CB5LiN5q2j56GulHMu:1qWugY:-BgxCaY6YMJDV01ojRAAU6k3rGBZRxzv9K8YPVOpSJw


session_key: am2j5rxi4pveyq4vwszw87ncgv1oy5qz
expire_date: 2023-09-01 08:18:33.060639
session_data:
  gASVJQAAAAAAAAB9lIwDbXNnlIwY55So5oi35ZCN5a-G56CB5LiN5q2j56GulHMu:1qWugj:WtGkHtDhhBDLxWBvyc2G-1CgLA8ZACMpWBxDF1DVm_I


session_key: m2oqxmf3m1yl4p2exu36jdpdz93wlgi5
expire_date: 2023-09-01 08:18:42.832133
session_data:
  gASVJQAAAAAAAAB9lIwDbXNnlIwY55So5oi35ZCN5a-G56CB5LiN5q2j56GulHMu:1qWugs:BdhL8-w4O-zNiuvVdaa0lA_1BdIjpHoFTLFK-Dt3y4E


session_key: 9v4wp8vgqv8eb1thl3ij8sm4nxxmkrpq
expire_date: 2023-09-01 08:18:56.553961
session_data:
  gASVJQAAAAAAAAB9lIwDbXNnlIwY55So5oi35ZCN5a-G56CB5LiN5q2j56GulHMu:1qWuh6:n2y1PkVGClLleL6ZvAThvVnf5WJDeoAyMdJx7Y-Vun4


session_key: rudxlykj121z1r080ddj5wn9ey67cu8w
expire_date: 2023-09-01 08:19:06.381055
session_data:
  gASVJQAAAAAAAAB9lIwDbXNnlIwY55So5oi35ZCN5a-G56CB5LiN5q2j56GulHMu:1qWuhG:3ZOzr4qq3_qZl0AoKxrBzPwuReuE0gKeCIJJCW2bvXM


session_key: xvn5sy2w7aexjmuxc16anlabjva9l4x9
expire_date: 2023-09-01 08:19:19.597084
session_data:
  gASVJQAAAAAAAAB9lIwDbXNnlIwY55So5oi35ZCN5a-G56CB5LiN5q2j56GulHMu:1qWuhT:UoNzTWAt225FVM_QhARTt49hMaggS2KFgk6OxpX6p2k


session_key: lbnv2u6r7x9280yzimb3ow4cd18mudpa
expire_date: 2023-09-01 08:19:29.358615
session_data:
  gASVJQAAAAAAAAB9lIwDbXNnlIwY55So5oi35ZCN5a-G56CB5LiN5q2j56GulHMu:1qWuhd:cU45lkEfRixboUBxOc2Q1F3ba8Iu6sqkeW9qtE22PT4


session_key: ngty7cs9gmqhci6e4zglksws1buxb13a
expire_date: 2023-09-04 01:47:59.996253
session_data:
  .eJxNUt1u0zAUXktZuzI6CdEHQKoQuwnNT9NGCGlQBFrHBuso0N1YTmwnbvNHnHTqJCS4AA3JdzWvxKvsNbDTTqpvzv853_l8ftT-3lZ3yvddPOMPASzyABQMZ4AiwSu64I-2fC705jiWgUM0g7GfaF4S5xl1NZWibaJMO00QDl_f5R5sNQggCwQ_gkaXuANi64T0iOvYlm6irtO1HNNElmcZWHe9vmETA-mW3rP1Qd_2Bha0LcO13X7PFLwZJj6Ny6aCtzdokKtFajSTUBgWvFVaoIhT6s1DLFaC1xQWKSaq8Lf4JcZCLb4LWA5zLIIGb5boL0pzJQ5_ruMQIRr74g-vIlfwOsIEFmEu-D6hOEQMeNALsEwtXF6VzJ1UeCOFjF0lmWTga-rOETEAC6DRszuG3ZWvc748e3vi-B_eG1--WdblaEKn5vWiM7GjgfdueT49O82LZDHV089ouPh4zKxwTKbj48s3zujV0ITF6Oql4gGyHJRkCN5AEnROI7ytrsSwWb9ttCvPXxy0_8nV02V-LQWYfBqqBceShrFchDLAihRnJaU3vKFkDFWvuhfAZEal1iQ0k-PW7vvDpavY3yshbHwXkulEShxBGgr-eFOqW86Rr1zyYiKJTg3LISFy0J7UoZfTBZbGAwUbzBIaYySCJwp5vdXaV_cpgqcl0PrdEQY1eVrrnwcLnDGaSAp2Tc3QjK76BzVkTcwNvxcxX_AdUWj_AfnzFms:1qXu1P:a03AigJcEl-lyah3_Q21jbjVYu4Z7d3Nssl2JnnZlO8


# blog_article

title: Gantt/BTS 生产计划电子看板甘特图
intro: “甘特图是一种用于说明项目进度的条形图。该图表在纵轴上列出了要执行的任务，在横轴上列出了时间间隔。图中水平条的宽度显示了每项活动的持续时间。”
created_time: 2021-11-15 08:59:05.403556
html_text:
  <h2>前言</h2><p>参考：<a href="https://www.freecodecamp.org/news/create-gantt-chart-using-css-grid/" target="">https://www.freecodecamp.org/news/create-gantt-chart-using-css-grid/</a></p><blockquote>“甘特图是一种用于说明项目进度的条形图。该图表在纵轴上列出了要执行的任务，在横轴上列出了时间间隔。图中水平条的宽度显示了每项活动的持续时间。” – 维基百科</blockquote><h2>成品</h2><p><img src="https://img2020.cnblogs.com/blog/1252682/202107/1252682-20210701163224876-306973203.png" alt=""></p><p>计算思路<br>首先生成 7:00 - 6:00 24小时时间表头<br></p><pre><code class="language-lang-javascript">generateTimeRange () {
        // let h1 = parseInt(start.substring(0, 2))
        let timeRange = []
        for (let i = 7; i &lt; 24; i++) {
          timeRange.push((i &lt; 10 ? '0' + i : i) + ':' + '00')
          this.timeRangeOrigin.push(i)
        }
        for (let i = 0; i &lt; 7; i++) {
          timeRange.push((i &lt; 10 ? '0' + i : i) + ':' + '00')
          this.timeRangeOrigin.push(i)
        }
        return timeRange
      },
  </code></pre><p><img src="https://img2020.cnblogs.com/blog/1252682/202106/1252682-20210603174950361-1632420587.png" alt=""></p><p>如第一条Item，30和0这两个条状图像，时间范围分别是 7:40 - 8:12 和 7:30 - 8:30</p><p>如何让条状图形正好偏移到时间点呢？</p><p>首先需要一个存储所以时间的数组，为了方便查找，里面的数据大概为: [7,8,9,10,11 …. 6]</p><p>以第二条为例：</p><p><img src="https://img2020.cnblogs.com/blog/1252682/202106/1252682-20210603175006725-1613106171.png" alt=""></p><p>180 这一条的起始时间为 2021-06-03 08:22:46 - 2021-06-03 11:49:15</p><p>我们先来计算宽度，这个180该多长：</p><p>let width = ((endIdx - startIdx) === 0 ? 1 : (endIdx - startIdx)) * this.itemWidth<br>endIdx 和 startIdx 为结束时间的分钟在数组中的下标，itemWidth为每个单元格的宽度<br></p><p>那么上面的时间区间就是 (4 - 1) * 150 大约三个单元格的宽度</p><p>那么再进行时间偏移</p><p>180 的起始时间是8点，也就是需要跳跃7这个单元格到8，分钟时22分，那么算出偏移量</p><p>(单元格宽度 / 60) * 分钟数 = 偏移量</p><pre><code class="language-lang-javascript"> getOffset (minute) {
        return (this.itemWidth / 60) * minute
      },
  </code></pre><p>那么最后还要处理一下宽度，这个时候的结束时间位置应该时不正确的。</p><pre><code class="language-lang-javascript">let style = 'width:' + ((width - this.getOffset(parseInt(m1)) + this.getOffset(parseInt(m2))) - this.itemWidth) + 'px;'
  </code></pre><p>最终宽度为: ((宽度 - 左偏移量 ) + 右偏移量) - 一个单元格宽度</p><p>完整计算如下：</p><pre><code class="language-lang-javascript"> getBarStyleVersion2 (flag, startTime, endTime) {
        let s = this.getMinutes(this.getTimestamp(startTime))
        let h1 = parseInt(s.substring(0, 2))
        let m1 = s.substring(s.indexOf(':') + 1, s.indexOf(':') + 3)
        let m2
        let h2
        if (endTime !== null) {
          let e = this.getMinutes(this.getTimestamp(endTime))
          h2 = parseInt(e.substring(0, 2))
          m2 = e.substring(s.indexOf(':') + 1, e.indexOf(':') + 3)
        } else {
          h2 = h1
          m2 = m1
        }
        let temp
        if (h1 &gt; h2) {
          temp = h1
          h1 = h2
          h2 = temp
        }
        let startIdx = this.getTimeIdx(h1)

        let endIdx = this.getTimeIdx(h2) + 1
        // 计算长度
        // // console.log(startIdx)
        // // console.log(endIdx)
        // // console.log('w:' + ((endIdx - startIdx) === 0 ? 1 : (endIdx - startIdx)) * this.itemWidth)

        let width = ((endIdx - startIdx) === 0 ? 1 : (endIdx - startIdx)) * this.itemWidth
        // console.log('offset:' + this.getOffset(parseInt(m1)))
        let marginLeft = 0

        if (startIdx &gt; 0) {
          marginLeft = (this.itemWidth * startIdx) + this.getOffset(parseInt(m1))
        } else {
          marginLeft = this.getOffset(parseInt(m1))
        }

        let style = 'width:' + ((width - this.getOffset(parseInt(m1)) + this.getOffset(parseInt(m2))) - this.itemWidth) + 'px;'
        // 计算margin
        style += 'margin-left:' + marginLeft + 'px;'
        // 计算偏移

        // console.log('m1: -&gt; ' + m1)
        // console.log('w: -&gt; ' + this.itemWidth)
        // // console.log('m2: -&gt; ' + m2)
        // // console.log(s)
        if (flag === 0) {
          style += 'background-color:#4F97A3;'
        } else {
          style += 'background-color:#0E4D92;'
        }
        return style
      },
      getOffset (minute) {
        return (this.itemWidth / 60) * minute
      },
  </code></pre><h2>时间轴</h2><p>就是图中的绿色竖线，代表当前时间。</p><p>首先一个DIV</p><p>然后生成样式：</p><pre><code class="language-lang-javascript"> getNowLine () {
        let styles = 'border:1px dashed green;height: 900px;width: 1px; position: fixed;z-index:999;'
        let now = this.getMinutes(Date.parse(new Date()))
        let m1 = parseInt(now.substring(0, 2))
        let s1 = parseInt(now.substring(now.indexOf(':') + 1, now.indexOf(':') + 3))
        let idx = this.getTimeIdx(m1)

        styles += 'margin-left:' + ((this.itemWidth * (idx + 1)) + (150 - (60 - this.getOffset(s1)))) + 'px'
        // console.log(idx)
        return styles
      }
  </code></pre><p>偏移量计算为 (单元格长度 * (小时数下标 + 1)) + (左侧固定栏宽度 - (60分钟 - 分钟数下标偏移量))</p><p><br></p>

markdown_text:
  前言
  参考：https://www.freecodecamp.org/news/create-gantt-chart-using-css-grid/
  “甘特图是一种用于说明项目进度的条形图。该图表在纵轴上列出了要执行的任务，在横轴上列出了时间间隔。图中水平条的宽度显示了每项活动的持续时间。” – 维基百科
  成品

  计算思路
  首先生成 7:00 - 6:00 24小时时间表头

  generateTimeRange () {
        // let h1 = parseInt(start.substring(0, 2))
        let timeRange = []
        for (let i = 7; i < 24; i++) {
          timeRange.push((i < 10 ? '0' + i : i) + ':' + '00')
          this.timeRangeOrigin.push(i)
        }
        for (let i = 0; i < 7; i++) {
          timeRange.push((i < 10 ? '0' + i : i) + ':' + '00')
          this.timeRangeOrigin.push(i)
        }
        return timeRange
      },


  如第一条Item，30和0这两个条状图像，时间范围分别是 7:40 - 8:12 和 7:30 - 8:30
  如何让条状图形正好偏移到时间点呢？
  首先需要一个存储所以时间的数组，为了方便查找，里面的数据大概为: [7,8,9,10,11 …. 6]
  以第二条为例：

  180 这一条的起始时间为 2021-06-03 08:22:46 - 2021-06-03 11:49:15
  我们先来计算宽度，这个180该多长：
  let width = ((endIdx - startIdx) === 0 ? 1 : (endIdx - startIdx)) * this.itemWidth
  endIdx 和 startIdx 为结束时间的分钟在数组中的下标，itemWidth为每个单元格的宽度

  那么上面的时间区间就是 (4 - 1) * 150 大约三个单元格的宽度
  那么再进行时间偏移
  180 的起始时间是8点，也就是需要跳跃7这个单元格到8，分钟时22分，那么算出偏移量
  (单元格宽度 / 60) * 分钟数 = 偏移量
   getOffset (minute) {
        return (this.itemWidth / 60) * minute
      },

  那么最后还要处理一下宽度，这个时候的结束时间位置应该时不正确的。
  let style = 'width:' + ((width - this.getOffset(parseInt(m1)) + this.getOffset(parseInt(m2))) - this.itemWidth) + 'px;'

  最终宽度为: ((宽度 - 左偏移量 ) + 右偏移量) - 一个单元格宽度
  完整计算如下：
   getBarStyleVersion2 (flag, startTime, endTime) {
        let s = this.getMinutes(this.getTimestamp(startTime))
        let h1 = parseInt(s.substring(0, 2))
        let m1 = s.substring(s.indexOf(':') + 1, s.indexOf(':') + 3)
        let m2
        let h2
        if (endTime !== null) {
          let e = this.getMinutes(this.getTimestamp(endTime))
          h2 = parseInt(e.substring(0, 2))
          m2 = e.substring(s.indexOf(':') + 1, e.indexOf(':') + 3)
        } else {
          h2 = h1
          m2 = m1
        }
        let temp
        if (h1 > h2) {
          temp = h1
          h1 = h2
          h2 = temp
        }
        let startIdx = this.getTimeIdx(h1)

        let endIdx = this.getTimeIdx(h2) + 1
        // 计算长度
        // // console.log(startIdx)
        // // console.log(endIdx)
        // // console.log('w:' + ((endIdx - startIdx) === 0 ? 1 : (endIdx - startIdx)) * this.itemWidth)

        let width = ((endIdx - startIdx) === 0 ? 1 : (endIdx - startIdx)) * this.itemWidth
        // console.log('offset:' + this.getOffset(parseInt(m1)))
        let marginLeft = 0

        if (startIdx > 0) {
          marginLeft = (this.itemWidth * startIdx) + this.getOffset(parseInt(m1))
        } else {
          marginLeft = this.getOffset(parseInt(m1))
        }

        let style = 'width:' + ((width - this.getOffset(parseInt(m1)) + this.getOffset(parseInt(m2))) - this.itemWidth) + 'px;'
        // 计算margin
        style += 'margin-left:' + marginLeft + 'px;'
        // 计算偏移

        // console.log('m1: -> ' + m1)
        // console.log('w: -> ' + this.itemWidth)
        // // console.log('m2: -> ' + m2)
        // // console.log(s)
        if (flag === 0) {
          style += 'background-color:#4F97A3;'
        } else {
          style += 'background-color:#0E4D92;'
        }
        return style
      },
      getOffset (minute) {
        return (this.itemWidth / 60) * minute
      },

  时间轴
  就是图中的绿色竖线，代表当前时间。
  首先一个DIV
  然后生成样式：
   getNowLine () {
        let styles = 'border:1px dashed green;height: 900px;width: 1px; position: fixed;z-index:999;'
        let now = this.getMinutes(Date.parse(new Date()))
        let m1 = parseInt(now.substring(0, 2))
        let s1 = parseInt(now.substring(now.indexOf(':') + 1, now.indexOf(':') + 3))
        let idx = this.getTimeIdx(m1)

        styles += 'margin-left:' + ((this.itemWidth * (idx + 1)) + (150 - (60 - this.getOffset(s1)))) + 'px'
        // console.log(idx)
        return styles
      }

  偏移量计算为 (单元格长度 * (小时数下标 + 1)) + (左侧固定栏宽度 - (60分钟 - 分钟数下标偏移量))


title: 菲律宾博彩内幕与见闻
intro: ​ 那时候我收到了一个用户的私信，说是招人，我看了下他们公司的简介，好家伙，东南亚双龙集团，上万人的大公司，我来了兴趣，就和他聊了起来。 ​ 和我聊的人是那边......
created_time: 2021-11-15 13:50:18.565036
html_text:
  <p><span style="font-size: 19px;">​ &nbsp;2019年，那时我在合肥刚和老板掀桌辞职不干，待业在家，同时也在找工作。</span></p><p><span style="font-size: 19px;">​ 那时候我收到了一个用户的私信，说是招人，我看了下他们公司的简介，东南亚双龙集团，上万人的大公司，我来了兴趣，就和他聊了起来。</span></p><p><span style="font-size: 19px;">​ 和我聊的人是那边公司的一个部门经理，说看到我的简历，问我有没有意向。我没有拒绝，和他了解了一下那边的情况，比如薪资，是否安全，上班地点之类的。我得到的回复是，薪资15K，上班地点在菲律宾马尼拉帕塞，至于是否安全这个问题，这个确实不太好回答，他给我发了两个视频，是他们公司吸烟区，很多中国人，也夹杂着一些听不懂的当地语言。</span></p><p><span style="font-size: 19px;">​ 对于当时19岁的我来说，15K那绝对是高薪资，而且包吃住，经理允诺的会给我一个单间，而且包签证和机票。</span></p><p><span style="font-size: 19px;">​ 我承认我心动了，和家里人说了这件事，当然，我家里人那是极力反对。</span></p><p><span style="font-size: 19px;">​ 我家里人也在用百度帮我搜了，拿给我看，都是什么被骗去就撕掉护照回不来，被绑架之类的。我自己也搜到了一些不太好的新闻，比如东方监狱，双龙妓院之类的。说是东方集团监狱式管理，双龙集团睡女下属(后来才知道，双龙妓院名不虚传)。</span></p><p><span style="font-size: 19px;">​ 接下来我去办了护照，将护照寄到厦门，然后过几天，我收到了我的护照和一张签证，该准备启程了。</span></p><p><span style="font-size: 19px;">​ 我家里人见我都办好了手续，也就没再说什么了。</span></p><p><span style="font-size: 19px;">​ 机票是那边的财务帮我订的，从合肥飞广州，再从广州到马尼拉。</span></p><p><span style="font-size: 19px;">​ 拎着包出了机场，看到了经理那一行人，他们来接的不止我一个，还有几个人。</span></p><p><span style="font-size: 19px;">​ 怀着忐忑的心上了车，被带到一个饭店，应该是接风洗尘。这时的我可不敢是在国内跟老板掀桌子大不了走人那种心态了，毕竟在这个地方，出了事可以说叫天天不应，叫地地不灵。所以我尽量装的人畜无害，酒场上见人就喊哥，他们倒也客气，就是能感觉出有种江湖气。</span></p><p><span style="font-size: 19px;">​ 经理叫安财，不用说肯定是假名字，他给了我一个小米手机和一张手机卡，让我换上，可以在这边上网和打电话，还认识了一个主管，我加他天哥，感觉上比较仗义。</span></p><p><span style="font-size: 19px;">​ 接下来我被带到了宿舍，确实给了我一个单间，当时已经凌晨3点左右，于是就赶紧睡觉了。</span></p><p><span style="font-size: 19px;">​ 第二天起来，被司机带到公司，公司是一个挺高的大厦，看上去周围都还是挺不错的，我没有工牌进不去。过了一会，财务人带我上楼，他叫做’朝鲜’，假名字，大家都这么叫。我被领上楼，收了我的护照，给了我一个工牌。这里的人有种调侃方式，叫工牌为狗牌，而那些做推广的，叫狗推。</span></p><p><span style="font-size: 19px;">​ 说下这边的待遇吧。</span></p><p><span style="font-size: 19px;">​ 每天10点到10点，10点去的时候可以吃顿饭，中午一顿，下午一顿，晚上一顿，夜宵一顿，然后如果到了12点还在加班，还能有一顿饭。</span></p><p><span style="font-size: 19px;">​ 而且伙食还不错，菜很多，荤素齐全，免费拿。</span></p><p><span style="font-size: 19px;">​ 交通方面，就是做班车。这里确实很多中国人，福建居多，基本各地都有，还有台湾人，韩国人，有些国人像个混混，言语粗鄙下流，也包括有些女生，所以这里鱼蛇混杂，什么都有。</span></p><p><img src="https://img2020.cnblogs.com/blog/1252682/202111/1252682-20211101133353386-783870851.png" alt=""></p><p><span style="font-size: 19px;">​ 正式开始工作了，我的工作是引流，就是通过写程序的方式来给国内发广告，比如我的第一个任务是做视频裂变，什么是视频裂变呢，就是前几年你可能会在朋友圈看到一些用低俗的标题和视频来吸引用户点击，比如：</span></p><p><span style="font-size: 19px;">​ ‘上市公司CEO伪装成保安被扇耳光’，‘密！老公不在家,XXXXXX’</span></p><p><span style="font-size: 19px;">​ 当你视频看到一半，会弹出对话框，让你分享N个好友才能够继续观看，可能你分享了N个好友，还会让你继续分享，这就是裂变，从而实现一种病毒式的传播。</span></p><p><span style="font-size: 19px;">​ 我负责这个项目的开发，其实那时候我看到微信关于分享的接口，已经被整改了，但我不敢说，先做出点东西再说。</span></p><p><span style="font-size: 19px;">​ 于是做了一个将视频分享到朋友圈的demo，跟经理提了微信封了分享接口的事，于是这个项目就流产了。</span></p><p><span style="font-size: 19px;">​ 接下来经理给了我一个discuz论坛，让我给这个论坛发所有用户发通知。</span></p><p><span style="font-size: 19px;">​ 开始写爬虫，discuz论坛简直太容易被搞了，cookie劫持一下，然后所有用户id都是自增的，只要一个循环去调用接口去发就行了。</span></p><p><span style="font-size: 19px;">​ 在这个公司终于能干上事了，其实那时候的discuz论坛已经没什么人用了，2019年很少有人上这种古老的论坛了，但咱也不敢说，说什么照做就是了。</span></p><p><span style="font-size: 19px;">​ 经理给了我很多这种论坛，基于这个论坛，我开发了专门针对乐视论坛的爬虫，和一个多线程爬虫系统，对功能进行了增强，比如某个用户刚注册论坛，就会收到一则通知:</span></p><p><span style="font-size: 19px;">​ ‘XXK.COM 充值送彩金，足彩XXX’</span></p><p><span style="font-size: 19px;">​ 其实没什么用，基本都是僵尸用户，发的论坛很多，用户很多，实际上效果不大。</span></p><p><span style="font-size: 19px;">​ 做到这里的时候我基本对这边的生活开始熟悉了，我圈子很小，在这里，沉默低调是我的生存法则。在来到这里没几天的时候，我在微信公众号上看到了一个视频，标题是中国程序员去菲律宾遭暴打！我点开视频一看那确实，然后我把视频拿给经理看，经理说这里不会的，这里是正规公司。</span></p><p><span style="font-size: 19px;">​ 那就好。</span></p><p><span style="font-size: 19px;">​ 这里有点枯燥，因为你基本不敢出去玩，这里本地人很穷，当时公司的保安，司机，和我们宿舍的保姆都是本地人。而且这边还有些恐怖的传言，比如从天哥哪里听来的：</span></p><p><span style="font-size: 19px;">​ ‘某女生晚上单独出门，第二天被脱光扔公司门口。’</span></p><p><span style="font-size: 19px;">​ ‘千人斩女HR，以陪睡引诱国人来菲律宾打工，然后转手将人卖到黑盘口，听说后来回国得了艾滋和癌症去世了’</span></p><p><span style="font-size: 19px;">​ ‘坐黑车被拉到荒郊野外’</span></p><p><span style="font-size: 19px;">​ 还有我有次下班，一群小孩马上围上我，很多双手在我口袋里进进出出，我可不敢怎么样，只拿紧了手机，因为不远处有两个虎视眈眈的本地人，最后还是有经验的同事来帮忙解围。</span></p><p><span style="font-size: 19px;">​ 菲律宾是个禁烟的国家，在这里吸烟被看到可能会面临罚款，我有次刚出公司门口点了根烟，马上就有保安来沉着脸拿走了我的工牌，这时候也幸好，我的经理安财用1000披索贿赂了一下，搞定了这件事。</span></p><p><span style="font-size: 19px;">​ 我在网上认识了一个网友，湖南人，我给他发了个定位，没想到他也给我回了个定位，好家伙，他也在菲律宾！他说他也是十几天前来的，但他做的是。。。忘了叫什么了，后来我的经理还让我请他一起吃饭，当然不会无缘无故吃饭，我们经理让我那个网友去窃取东方集团的客户资料，当然也是被我那个网友拒绝了，这要是被发现，后果不堪设想的啊。后来我回国后得知他还在菲律宾，现在他的微信注销了，最后一次聊天他说打算去日本，不知道他现在如何，希望他安好。</span></p><p><span style="font-size: 19px;">​ 我也经常被他们拉着参加饭局，或者是聚餐，当然在饭桌上，我只管吃饭，其他时候闭上嘴就可以了。这边的海鲜很棒，螃蟹很大，算是饱了口福。</span></p><p><span style="font-size: 19px;">​ 至于网赌是否靠谱，我这么说吧。据我的了解，分两种，一种杀猪盘，一种综合盘，杀猪盘那我想都知道，一开始给你点甜头，然后让你血本无归，还有就是我们这个部门的综合盘，里面很多赌博游戏，但是，你所有的数据都是可以被操纵的，比如某人在我们盘赌博赢了1K，这时候天哥就会说：</span></p><p><span style="font-size: 19px;">​ “有个用户打了1000块给不给出？”</span></p><p><span style="font-size: 19px;">​ 然后安财说：“给出，放长线钓大鱼”</span></p><p><span style="font-size: 19px;">​ 偶尔有国内冤大头被这边狗推忽悠输了几十万，然后整个部门就放下工作去KTV。</span></p><p><span style="font-size: 19px;">​ 好了，再说下我接下来的工作了，开始沾黄了。</span></p><p><span style="font-size: 19px;">​ 经理弄了一个色情论坛，然后给了我一些网址，让我去将其他网站的视频爬过来同步到他的论坛内，这个也好办，色情视频基本都是m3u8格式，链接赤裸裸的暴露，爬虫很好写，然后将爬到的数据生成sql同步到公司数据库就可以了。</span></p><p><span style="font-size: 19px;">​ 这时候经理还在twitter上找福利鸡谈商务合作，笑死我了，而且他有次气急败坏的说：</span></p><p><span style="font-size: 19px;">​ “他妈的，来我们网站的都是小学生，一点钱都没有。”</span></p><p><span style="font-size: 19px;">​ 色情论坛确实有蛮多用户注册，但基本都是些中小学生，一点钱都没有，又怎么能去赌呢。</span></p><p><span style="font-size: 19px;">​ 我被调到了群体宿舍，因为安财，也就是我的经理和公司新招的一个女员工搞起来了，霸占了我的单间。</span></p><p><span style="font-size: 19px;">​ 双龙妓院名不虚传。</span></p><p><span style="font-size: 19px;">​ 这时候公司还在招人，来了一个海哥，大概30多岁，人看上去挺淳朴，会把从家乡带来的特产分给我们，据他所说他网赌欠了2百万，来菲律宾躲一躲，而他看到这边要长时间坐着的时候，表示不能干，他有腰间盘突出，不久后他提了离职，给批了，他还给我们秀他的蚂蚁分，负的，笑死了。</span></p><p><span style="font-size: 19px;">​ 我一看给批了！那我也赶紧提了离职，然后经理找我谈了一轮话，好吧我应了下来，然后过两天，我又提了。</span></p><p><span style="font-size: 19px;">​ 这次谈了很久，他也是在留我，但我去意已决，谈话过程也是温和很多，如果在国内我完全可以说，你们公司太垃圾我不想干，但在这我觉得还是不要这样的好，我一直强调，我技术太垃圾了，干不了，你们可以招个技术牛逼的。</span></p><p><span style="font-size: 19px;">​ 好的，于是我也给批了。</span></p><p><span style="font-size: 19px;">​ 当晚我就用我所有的披索买了很多酒，到宿舍喝了一晚，然后几天后，我拿到了护照，公司安排司机送我去机场，成功回了国。正好我回国后不久疫情爆发了，真是巧合。</span></p><p><span style="font-size: 19px;">​ 后续，这个盘口倒闭了。</span></p>

markdown_text:
  ​  2019年，那时我在合肥刚和老板掀桌辞职不干，待业在家，同时也在找工作。
  ​ 那时候我收到了一个用户的私信，说是招人，我看了下他们公司的简介，东南亚双龙集团，上万人的大公司，我来了兴趣，就和他聊了起来。
  ​ 和我聊的人是那边公司的一个部门经理，说看到我的简历，问我有没有意向。我没有拒绝，和他了解了一下那边的情况，比如薪资，是否安全，上班地点之类的。我得到的回复是，薪资15K，上班地点在菲律宾马尼拉帕塞，至于是否安全这个问题，这个确实不太好回答，他给我发了两个视频，是他们公司吸烟区，很多中国人，也夹杂着一些听不懂的当地语言。
  ​ 对于当时19岁的我来说，15K那绝对是高薪资，而且包吃住，经理允诺的会给我一个单间，而且包签证和机票。
  ​ 我承认我心动了，和家里人说了这件事，当然，我家里人那是极力反对。
  ​ 我家里人也在用百度帮我搜了，拿给我看，都是什么被骗去就撕掉护照回不来，被绑架之类的。我自己也搜到了一些不太好的新闻，比如东方监狱，双龙妓院之类的。说是东方集团监狱式管理，双龙集团睡女下属(后来才知道，双龙妓院名不虚传)。
  ​ 接下来我去办了护照，将护照寄到厦门，然后过几天，我收到了我的护照和一张签证，该准备启程了。
  ​ 我家里人见我都办好了手续，也就没再说什么了。
  ​ 机票是那边的财务帮我订的，从合肥飞广州，再从广州到马尼拉。
  ​ 拎着包出了机场，看到了经理那一行人，他们来接的不止我一个，还有几个人。
  ​ 怀着忐忑的心上了车，被带到一个饭店，应该是接风洗尘。这时的我可不敢是在国内跟老板掀桌子大不了走人那种心态了，毕竟在这个地方，出了事可以说叫天天不应，叫地地不灵。所以我尽量装的人畜无害，酒场上见人就喊哥，他们倒也客气，就是能感觉出有种江湖气。
  ​ 经理叫安财，不用说肯定是假名字，他给了我一个小米手机和一张手机卡，让我换上，可以在这边上网和打电话，还认识了一个主管，我加他天哥，感觉上比较仗义。
  ​ 接下来我被带到了宿舍，确实给了我一个单间，当时已经凌晨3点左右，于是就赶紧睡觉了。
  ​ 第二天起来，被司机带到公司，公司是一个挺高的大厦，看上去周围都还是挺不错的，我没有工牌进不去。过了一会，财务人带我上楼，他叫做’朝鲜’，假名字，大家都这么叫。我被领上楼，收了我的护照，给了我一个工牌。这里的人有种调侃方式，叫工牌为狗牌，而那些做推广的，叫狗推。
  ​ 说下这边的待遇吧。
  ​ 每天10点到10点，10点去的时候可以吃顿饭，中午一顿，下午一顿，晚上一顿，夜宵一顿，然后如果到了12点还在加班，还能有一顿饭。
  ​ 而且伙食还不错，菜很多，荤素齐全，免费拿。
  ​ 交通方面，就是做班车。这里确实很多中国人，福建居多，基本各地都有，还有台湾人，韩国人，有些国人像个混混，言语粗鄙下流，也包括有些女生，所以这里鱼蛇混杂，什么都有。

  ​ 正式开始工作了，我的工作是引流，就是通过写程序的方式来给国内发广告，比如我的第一个任务是做视频裂变，什么是视频裂变呢，就是前几年你可能会在朋友圈看到一些用低俗的标题和视频来吸引用户点击，比如：
  ​ ‘上市公司CEO伪装成保安被扇耳光’，‘密！老公不在家,XXXXXX’
  ​ 当你视频看到一半，会弹出对话框，让你分享N个好友才能够继续观看，可能你分享了N个好友，还会让你继续分享，这就是裂变，从而实现一种病毒式的传播。
  ​ 我负责这个项目的开发，其实那时候我看到微信关于分享的接口，已经被整改了，但我不敢说，先做出点东西再说。
  ​ 于是做了一个将视频分享到朋友圈的demo，跟经理提了微信封了分享接口的事，于是这个项目就流产了。
  ​ 接下来经理给了我一个discuz论坛，让我给这个论坛发所有用户发通知。
  ​ 开始写爬虫，discuz论坛简直太容易被搞了，cookie劫持一下，然后所有用户id都是自增的，只要一个循环去调用接口去发就行了。
  ​ 在这个公司终于能干上事了，其实那时候的discuz论坛已经没什么人用了，2019年很少有人上这种古老的论坛了，但咱也不敢说，说什么照做就是了。
  ​ 经理给了我很多这种论坛，基于这个论坛，我开发了专门针对乐视论坛的爬虫，和一个多线程爬虫系统，对功能进行了增强，比如某个用户刚注册论坛，就会收到一则通知:
  ​ ‘XXK.COM 充值送彩金，足彩XXX’
  ​ 其实没什么用，基本都是僵尸用户，发的论坛很多，用户很多，实际上效果不大。
  ​ 做到这里的时候我基本对这边的生活开始熟悉了，我圈子很小，在这里，沉默低调是我的生存法则。在来到这里没几天的时候，我在微信公众号上看到了一个视频，标题是中国程序员去菲律宾遭暴打！我点开视频一看那确实，然后我把视频拿给经理看，经理说这里不会的，这里是正规公司。
  ​ 那就好。
  ​ 这里有点枯燥，因为你基本不敢出去玩，这里本地人很穷，当时公司的保安，司机，和我们宿舍的保姆都是本地人。而且这边还有些恐怖的传言，比如从天哥哪里听来的：
  ​ ‘某女生晚上单独出门，第二天被脱光扔公司门口。’
  ​ ‘千人斩女HR，以陪睡引诱国人来菲律宾打工，然后转手将人卖到黑盘口，听说后来回国得了艾滋和癌症去世了’
  ​ ‘坐黑车被拉到荒郊野外’
  ​ 还有我有次下班，一群小孩马上围上我，很多双手在我口袋里进进出出，我可不敢怎么样，只拿紧了手机，因为不远处有两个虎视眈眈的本地人，最后还是有经验的同事来帮忙解围。
  ​ 菲律宾是个禁烟的国家，在这里吸烟被看到可能会面临罚款，我有次刚出公司门口点了根烟，马上就有保安来沉着脸拿走了我的工牌，这时候也幸好，我的经理安财用1000披索贿赂了一下，搞定了这件事。
  ​ 我在网上认识了一个网友，湖南人，我给他发了个定位，没想到他也给我回了个定位，好家伙，他也在菲律宾！他说他也是十几天前来的，但他做的是。。。忘了叫什么了，后来我的经理还让我请他一起吃饭，当然不会无缘无故吃饭，我们经理让我那个网友去窃取东方集团的客户资料，当然也是被我那个网友拒绝了，这要是被发现，后果不堪设想的啊。后来我回国后得知他还在菲律宾，现在他的微信注销了，最后一次聊天他说打算去日本，不知道他现在如何，希望他安好。
  ​ 我也经常被他们拉着参加饭局，或者是聚餐，当然在饭桌上，我只管吃饭，其他时候闭上嘴就可以了。这边的海鲜很棒，螃蟹很大，算是饱了口福。
  ​ 至于网赌是否靠谱，我这么说吧。据我的了解，分两种，一种杀猪盘，一种综合盘，杀猪盘那我想都知道，一开始给你点甜头，然后让你血本无归，还有就是我们这个部门的综合盘，里面很多赌博游戏，但是，你所有的数据都是可以被操纵的，比如某人在我们盘赌博赢了1K，这时候天哥就会说：
  ​ “有个用户打了1000块给不给出？”
  ​ 然后安财说：“给出，放长线钓大鱼”
  ​ 偶尔有国内冤大头被这边狗推忽悠输了几十万，然后整个部门就放下工作去KTV。
  ​ 好了，再说下我接下来的工作了，开始沾黄了。
  ​ 经理弄了一个色情论坛，然后给了我一些网址，让我去将其他网站的视频爬过来同步到他的论坛内，这个也好办，色情视频基本都是m3u8格式，链接赤裸裸的暴露，爬虫很好写，然后将爬到的数据生成sql同步到公司数据库就可以了。
  ​ 这时候经理还在twitter上找福利鸡谈商务合作，笑死我了，而且他有次气急败坏的说：
  ​ “他妈的，来我们网站的都是小学生，一点钱都没有。”
  ​ 色情论坛确实有蛮多用户注册，但基本都是些中小学生，一点钱都没有，又怎么能去赌呢。
  ​ 我被调到了群体宿舍，因为安财，也就是我的经理和公司新招的一个女员工搞起来了，霸占了我的单间。
  ​ 双龙妓院名不虚传。
  ​ 这时候公司还在招人，来了一个海哥，大概30多岁，人看上去挺淳朴，会把从家乡带来的特产分给我们，据他所说他网赌欠了2百万，来菲律宾躲一躲，而他看到这边要长时间坐着的时候，表示不能干，他有腰间盘突出，不久后他提了离职，给批了，他还给我们秀他的蚂蚁分，负的，笑死了。
  ​ 我一看给批了！那我也赶紧提了离职，然后经理找我谈了一轮话，好吧我应了下来，然后过两天，我又提了。
  ​ 这次谈了很久，他也是在留我，但我去意已决，谈话过程也是温和很多，如果在国内我完全可以说，你们公司太垃圾我不想干，但在这我觉得还是不要这样的好，我一直强调，我技术太垃圾了，干不了，你们可以招个技术牛逼的。
  ​ 好的，于是我也给批了。
  ​ 当晚我就用我所有的披索买了很多酒，到宿舍喝了一晚，然后几天后，我拿到了护照，公司安排司机送我去机场，成功回了国。正好我回国后不久疫情爆发了，真是巧合。
  ​ 后续，这个盘口倒闭了。


title: Python Django 搭建博客[开源]
intro: 博客搭建简单说明
created_time: 2021-11-17 13:02:29.967363
html_text:
  <h5 id="h5-u5F00u6E90u5730u5740"><a name="开源地址" class="reference-link"></a><span class="header-link octicon octicon-link"></span>开源地址</h5><p><a href="https://gitee.com/yang_chaojie/django-blog">https://gitee.com/yang_chaojie/django-blog</a></p>
  <h5 id="h5-u529Fu80FDu4ECBu7ECD"><a name="功能介绍" class="reference-link"></a><span class="header-link octicon octicon-link"></span>功能介绍</h5><p><img src="/upload/files/png/c984eeeb25ef41361c35cc7e2c99993c.png" alt="">
  <p>在右上角登录，主要功能就是分类，标签，和文章上传和音乐上传，NAS云盘等。</p>
  <p>文章格式使用Markdown，看一下写文章界面。</p>
  <p><img src="/upload/files/png/6fa46daed8ca6ac8cc6c87a3d8237028.png" alt="">
  <p>写文章有两种类型可选，一种是文章，一种是随笔，随笔的意思就类似QQ的说说，不会有摘要，而是直接显示全部内容，而文章需要点进去查看详情。</p>
  <p><img src="/upload/files/png/13c3379a368b94008437edeee4f6b298.png" alt="">
  <p>登录账号后，会出现管理选项。</p>
  <p><img src="/upload/files/png/a32ed788db57bb4a8bdc235f7f707baa.png" alt="">
  <p><img src="/upload/files/png/58e87531ae5e9a9e109bf16191d5a2d0.png" alt="">
  <p>点击分类和标签和查询到当前分类或标签下的所有文章。</p>
  <p><img src="/upload/files/png/539bffce57407318d00fc5c81939991f.png" alt="">
  <p>在部署的机器上的Nginx配置路径映射，可以方便的上传下载文件，并提供了文件的管理和删除。</p>
  <p><img src="/upload/files/png/b8335619b64aa3089adc65675ee892f6.png" alt="">
  <h2 id="h2-u90E8u7F72u914Du7F6E"><a name="部署配置" class="reference-link"></a><span class="header-link octicon octicon-link"></span>部署配置</h2><h5 id="h5-u521Du59CBu5316u9879u76EE"><a name="初始化项目" class="reference-link"></a><span class="header-link octicon octicon-link"></span>初始化项目</h5><p>需求：python3 pip3</p>
  <p>git 克隆项目到本地</p>
  <pre><code class="lang-bash">git clone https://gitee.com/yang_chaojie/django-blog.git
  </code></pre>
  <p>安装依赖：</p>
  <pre><code class="lang-bash">pip install -r requirements.txt
  </code></pre>
  <p>初始化Sqlite数据库：</p>
  <pre><code class="lang-bash">python manage.py makemigrations
  </code></pre>
  <pre><code class="lang-bash">python manage.py migrate
  </code></pre>
  <p>创建管理员账号：</p>
  <pre><code class="lang-bash">python manage.py createsuperuser
  </code></pre>
  <p>按照提示输入相应的用户名，邮箱和密码即可</p>
  <h5 id="h5-u914Du7F6Eu9879u76EE"><a name="配置项目" class="reference-link"></a><span class="header-link octicon octicon-link"></span>配置项目</h5><p>打开 settings.py</p>
  <pre><code class="lang-python">UPLOAD_URL = '/upload/files' 

  UPLOAD_ROOT = '/opt/IBE/files' #这里是你的文件上传路径

  DOMAIN = ''

  X_FRAME_OPTIONS = 'SAMEORIGIN'
  SECURE_CONTENT_TYPE_NOSNIFF = False
  NAS_PATH = '/media/pi/disk/' #这里是你的本机NAS映射路径
  </code></pre>
  <p>就可以了。</p>
  <p>在 linux 上运行 start.sh</p>
  <pre><code class="lang-shell">#!/bin/bash
  nohup python3 manage.py runserver 0.0.0.0:8000 --insecure &amp;
  </code></pre>
  <p>端口号是8000，可任意填写端口。</p>
  <p>NAS 配置路径：</p>
  <p>打开 nas.html</p>
  <pre><code class="lang-html">&lt;iframe src="http://s3.z100.vip:28592" width="100%" height="500px"&gt;&lt;/iframe&gt;
  </code></pre>
  <p>这里的: <a href="http://s3.z100.vip:28592">http://s3.z100.vip:28592</a> 是我通过Nginx映射出去的路径，同时内网穿透绑定的地址，你根据自己的情况填写。</p>
  <h5 id="h5-u6CE8u610Fu4E8Bu9879"><a name="注意事项" class="reference-link"></a><span class="header-link octicon octicon-link"></span>注意事项</h5><p>启动项目可能会报错，一般就俩。</p>
  <ol>
  <li>如果错误报在 manage.py ，那你需要用 python3 来执行命令，而不是python。</li><li>如果报Sqlite3版本相关错误，那么降低django版本至 2.1.8</li></ol>

markdown_text:
  ##### 开源地址

  https://gitee.com/yang_chaojie/django-blog

  ##### 功能介绍
  ![](/upload/files/png/c984eeeb25ef41361c35cc7e2c99993c.png)

  在右上角登录，主要功能就是分类，标签，和文章上传和音乐上传，NAS云盘等。

  文章格式使用Markdown，看一下写文章界面。

  ![](/upload/files/png/6fa46daed8ca6ac8cc6c87a3d8237028.png)

  写文章有两种类型可选，一种是文章，一种是随笔，随笔的意思就类似QQ的说说，不会有摘要，而是直接显示全部内容，而文章需要点进去查看详情。

  ![](/upload/files/png/13c3379a368b94008437edeee4f6b298.png)

  登录账号后，会出现管理选项。

  ![](/upload/files/png/a32ed788db57bb4a8bdc235f7f707baa.png)

  ![](/upload/files/png/58e87531ae5e9a9e109bf16191d5a2d0.png)

  点击分类和标签和查询到当前分类或标签下的所有文章。

  ![](/upload/files/png/539bffce57407318d00fc5c81939991f.png)


  在部署的机器上的Nginx配置路径映射，可以方便的上传下载文件，并提供了文件的管理和删除。

  ![](/upload/files/png/b8335619b64aa3089adc65675ee892f6.png)

  ## 部署配置

  ##### 初始化项目

  需求：python3 pip3

  git 克隆项目到本地

  ```bash
  git clone https://gitee.com/yang_chaojie/django-blog.git
  ```

  安装依赖：

  ```bash
  pip install -r requirements.txt
  ```

  初始化Sqlite数据库：

  ```bash
  python manage.py makemigrations
  ```

  ```bash
  python manage.py migrate
  ```

  创建管理员账号：

  ```bash
  python manage.py createsuperuser
  ```

  按照提示输入相应的用户名，邮箱和密码即可

  ##### 配置项目

  打开 settings.py

  ```python
  UPLOAD_URL = '/upload/files' 

  UPLOAD_ROOT = '/opt/IBE/files' #这里是你的文件上传路径

  DOMAIN = ''

  X_FRAME_OPTIONS = 'SAMEORIGIN'
  SECURE_CONTENT_TYPE_NOSNIFF = False
  NAS_PATH = '/media/pi/disk/' #这里是你的本机NAS映射路径
  ```

  就可以了。

  在 linux 上运行 start.sh

  ```shell
  #!/bin/bash
  nohup python3 manage.py runserver 0.0.0.0:8000 --insecure &
  ```

  端口号是8000，可任意填写端口。

  NAS 配置路径：

  打开 nas.html

  ```html
  <iframe src="http://s3.z100.vip:28592" width="100%" height="500px"></iframe>
  ```

  这里的: http://s3.z100.vip:28592 是我通过Nginx映射出去的路径，同时内网穿透绑定的地址，你根据自己的情况填写。



  ##### 注意事项

  启动项目可能会报错，一般就俩。

  1. 如果错误报在 manage.py ，那你需要用 python3 来执行命令，而不是python。
  2. 如果报Sqlite3版本相关错误，那么降低django版本至 2.1.8


title: JavaFx 语言播报 MTTS 实现
intro: JavaFx 通信ITC数字广播 SAPI 使用NeoSpeech Liang包生成语音
created_time: 2021-11-18 14:54:11.435457
html_text:
  <h2>Java调用Windows SAPI.spVoice。</h2><p>Java作为跨平台语音对调用Windows是没有原生支持的，所以需要使用第三方框架来做中转实现目的。</p><blockquote>Jacob是一个Java库，可让Java应用程序与Microsoft Windows DLL或COM库进行通信。它通过使用定制的DLL来实现此目的，Jacob Java类通过JNI与之通信。Java库和dll将Java开发人员与基础Windows库隔离开来，因此Java开发人员不必编写自定义JNI代码。Jacob不用于创建ActiveX插件或Microsoft Windows应用程序内部的其他模块。</blockquote><p>项目地址：<a href="https://github.com/freemansoft/jacob-project" target="">https://github.com/freemansoft/jacob-project</a></p><h5>使用</h5><p>Jacob 1.8下载后里面拥有以下文件</p><p><img src="https://img2020.cnblogs.com/blog/1252682/202102/1252682-20210203115320467-332248459.png" alt=""></p><p>将对应当前Java版本的DLL文件放到Java/bin目录，将jacob.jar作为项目依赖引入。</p><h5>SAPI (Microsoft Speech API )</h5><p>文档：<a href="https://docs.microsoft.com/en-us/previous-versions/windows/desktop/ee125663(v=vs.85)?redirectedfrom=msdn" target="">https://docs.microsoft.com/en-us/previous-versions/windows/desktop/ee125663(v=vs.85)?redirectedfrom=msdn</a></p><p>使用 jacob 来调用SAPI。</p><p>关于如何使用这里的博客讲的很详细:<a href="https://blog.csdn.net/asuyunlong/article/details/50083421/" target="">https://blog.csdn.net/asuyunlong/article/details/50083421/</a></p><p>但是我是用的是第三方语音库，在切换语音包时会报错。</p><pre><code class="language-java"> @Test
      public void test2() {
          MSTTSSpeech speech = new MSTTSSpeech();
          String[] vs = speech.getVoices();
          System.out.println(Arrays.toString(vs));
          String text = "TTS TEST";

          speech.setFormatType(6);
          speech.changeVoice(1);
          // speech.setRate(-1);
          // speech.setRate(-1);
          System.out.println(getClass().getResource("mp3").getPath() + "\\" + UUID.randomUUID() + ".wav");
          String url = getClass().getResource("mp3").getPath();
          File wavFile = new File(url);
          if (!wavFile.exists()) {
              wavFile.getParentFile().mkdirs();
          }

          speech.saveToWav(text, url.substring(1) + "\\" + UUID.randomUUID() + ".wav");
      }
  </code></pre><p>运行测试后出错:</p><pre><code class="language-java">A COM exception has been encountered:
  At Invoke of: Voice
  Description: 80020003 / 找不到成员。

  com.jacob.com.ComFailException: A COM exception has been encountered:
  At Invoke of: Voice
  Description: 80020003 / 找不到成员。

      at com.jacob.com.Dispatch.invokev(Native Method)
      ...
  </code></pre><p><code>Description: 80020003 / 找不到成员。</code> 这个问题我找了很久，在我的电脑无法使用，但是可以使用控制面板项修改系统默认语音达到效果：</p><p>C:\Windows\SysWOW64\Speech\SpeechUX\sapi.cpl</p><p><img src="https://img2020.cnblogs.com/blog/1252682/202102/1252682-20210203115342594-921702312.png" alt=""></p><h2>Wav转Mp3重新编码并合并多个Mp3文件</h2><p><a href="https://lame.sourceforge.io/" target="">https://lame.sourceforge.io/</a></p><p>现在的需求是，我有一段这样的文本: [P]Call.mp3;[T] 呼叫 XXX 前往 SMT 3号线</p><p>其中 [P]Call.mp3;[T] 是现有mp3文件，而其他文本是要通过sapi生成的，而且文本中可能还会出现外部的mp3文件。</p><p>所以思路是将mp3文件名称和需要转语音的文本按顺序抽取出来，将文字转wav，wav转mp3，最后再合并mp3文件。</p><p>代码 : AudioUtil4.java</p><pre><code class="language-java">public class AudioUtil4 {
      public static String path = null;
      public static String lame = null;
      static MSTTSSpeech speech = new MSTTSSpeech();

      static Logger LOG = LoggerFactory.getLogger(AudioUtil4.class);
      //合并mp3
      public static File mergeMp3(List&lt;PlayModel&gt; playModels) throws IOException {
          Vector&lt;FileInputStream&gt; fileInputStreams = new Vector&lt;&gt;();
          for (PlayModel playModel : playModels) {
              if (playModel.isMp3()) {
                  playModel.setPath(path + "\\" + playModel.getContent());
              } else {
                  playModel.setPath(AudioUtil4.textToMp3(playModel.getContent()));
              }
              fileInputStreams.add(new FileInputStream(playModel.getPath()));
             LOG.info("合并文件:" + playModel.getPath());
          }
          SequenceInputStream sequenceInputStream = new SequenceInputStream(fileInputStreams.elements());
          String mergePath = path + "\\" + UUID.randomUUID() + ".mp3";
          File afterMergeMp3 = new File(mergePath);
          boolean isSuccess = afterMergeMp3.createNewFile();
          if (isSuccess) {
              FileOutputStream fileOutputStream = new FileOutputStream(afterMergeMp3);//destinationfile
              int temp;

              while ((temp = sequenceInputStream.read()) != -1) {
                  // System.out.print( (char) temp ); // to print at DOS prompt
                  fileOutputStream.write(temp);   // to write to file
              }
              for (FileInputStream f : fileInputStreams) {
                  f.close();
              }
              fileOutputStream.close();
              sequenceInputStream.close();
              playModels.forEach(playModel -&gt; {
                  if (!playModel.isMp3()) {
                      new File(playModel.getPath()).delete();
                  }
              });
          } else {
              throw new IOException("mp3创建失败! " + mergePath);
          }
          return new File(mergePath);
      }
      //获取播放列表 抽取
      public static List&lt;PlayModel&gt; getPlayList(String message) {
          int i = 0;
          char[] messageArr = message.toCharArray();
          List&lt;PlayModel&gt; playModels = new ArrayList&lt;&gt;();
          StringBuilder temp = new StringBuilder();
          while (i &lt; messageArr.length) {
              if (messageArr[i] == '[') {
                  if (temp.length() &gt; 0) {
                      playModels.add(new PlayModel(false, temp.toString(), null));
                      temp = new StringBuilder();
                  }
                  Map&lt;String, Object&gt; mp3 = getEndIndex(i, messageArr);
                  i = (int) mp3.get("endIndex");
                  playModels.add(new PlayModel(true, mp3.get("mp3").toString(), null));
              } else {
                  temp.append(messageArr[i]);
              }
              i += 1;
          }
          if (temp.length() &gt; 0) {
              playModels.add(new PlayModel(false, temp.toString(), null));
          }
          return playModels;
      }
      //抽取
      public static Map&lt;String, Object&gt; getEndIndex(int index, char[] chars) {
          StringBuilder mp3 = new StringBuilder();
          int mp3EndIndex = -1;
          int endIndex = -1;
          for (int i = index + 3; i &lt; chars.length; i++) {
              if (chars[i] == ']') {
                  endIndex = i;
                  break;
              } else if (chars[i] == ';') {
                  mp3EndIndex = i;
              } else {
                  if (mp3EndIndex == -1) {
                      mp3.append(chars[i]);
                  }
              }
          }
          Map&lt;String, Object&gt; result = new HashMap&lt;&gt;();
          result.put("endIndex", endIndex);
          result.put("mp3", mp3);
          return result;
      }
      //wav转mp3
      public static String textToMp3(String text) throws FileNotFoundException {
         LOG.info("转语音:" + text);
          File wavFile = new File(textToWav(text));
          if (!wavFile.exists()) {
              throw new FileNotFoundException("转语音失败!");
          } else {
              //to mp3 use lame
              String wavPathStr = wavFile.getPath();
              String mp3PathStr = wavFile.getPath().
                      replace(".wav", ".mp3");
              String cmds1 = lame +
                      String.format(" -b128 --resample 24 -m j  %s %s", wavPathStr, mp3PathStr);
  //                Arguments = string.Format("-b128 --resample 24 -m j \"{0}\" \"{1}\"", str3, str4)
              //                    String.format(" --silent -b 160 -m m -q 9-resample -tt  %s %s", wavPathStr, mp3PathStr);

              String cmds2 = lame +
                      String.format(" --scale 4 --mp3input %s %s", mp3PathStr, mp3PathStr.replace(".mp3", "L.mp3"));

             LOG.info("lame to mp3 : " + cmds1);
             LOG.info("large voice : " + cmds2);
              CommandUtil.exec(cmds1);
              CommandUtil.exec(cmds2);
              new File(wavPathStr).delete();
              new File(mp3PathStr).delete();
              return mp3PathStr.replace(".mp3", "L.mp3");
          }
      }

      //文本转wav
      private static String textToWav(String text) {
          speech.setFormatType(16);
          File wavFile = new File(path);
          if (!wavFile.exists()) {
              wavFile.getParentFile().mkdirs();
          }
         LOG.info(path);
          String filePath = path + "\\" + UUID.randomUUID() + ".wav";
          speech.saveToWav(text, filePath);
         LOG.info("wav文件保存到 : " + filePath);
          return filePath;
      }
  </code></pre><p>测试：</p><pre><code class="language-java"> @Test
      public void getPlayList() throws IOException {
          File directory = new File("");//设定为当前文件夹
          String mp3Path = directory.getAbsolutePath() + "\\mp3";
          String lamePath = directory.getAbsolutePath() + "\\lame.exe";

          AudioUtil4.path = mp3Path;
          AudioUtil4.lame = lamePath;


          List&lt;PlayModel&gt; playModels = AudioUtil4.getPlayList("[P]PromptRing.MP3;[T]请 袁振 速到 SMD 8号线 ");

          System.out.println("合并mp3:" + AudioUtil4.mergeMp3(playModels));
      }
  </code></pre><p>命令行代码：</p><p>CommandUtil.java</p><pre><code class="language-java">public class CommandUtil {
      static Logger LOG = LoggerFactory.getLogger(CommandUtil.class);

      public static int exec(String comm) {
          int finished = 0;
          try {
              Runtime rt = Runtime.getRuntime();
              Process proc = rt.exec(comm);
              InputStream stderr = proc.getErrorStream();
              InputStreamReader isr = new InputStreamReader(stderr);
              BufferedReader br = new BufferedReader(isr);
              String line = null;
              LOG.info("&lt;error&gt;&lt;/error&gt;");
              while ((line = br.readLine()) != null) {
                  String encoded = new String(line.getBytes("gbk"), "GB18030");//GB2312/CP936/GB18030
                  LOG.info(encoded);
                  LOG.info(line);
              }
              int exitVal = proc.waitFor();
              LOG.info("Process exitValue:" + exitVal);
              finished = exitVal;
          } catch (Exception e) {
              e.printStackTrace();
          }
          return finished;
      }
  }
  </code></pre><p>Call Service 核心：</p><pre><code class="language-java">package cn.ciemis.service;

  import cn.ciemis.controller.MainController;
  import cn.ciemis.db.VoiceJDBC;
  import cn.ciemis.entity.*;
  import cn.ciemis.log.Log;
  import cn.ciemis.log.Logger;
  import cn.ciemis.sdk.IPCastSDK;
  import cn.ciemis.util.AudioUtil4;
  import cn.ciemis.util.FxUtil;
  import javafx.application.Platform;
  import javafx.concurrent.Service;
  import javafx.concurrent.Task;
  import org.controlsfx.control.StatusBar;
  import org.slf4j.LoggerFactory;

  import java.io.File;
  import java.io.IOException;
  import java.io.UnsupportedEncodingException;
  import java.sql.SQLException;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.List;
  import java.util.Objects;

  import static cn.ciemis.controller.MainController.log;

  public class CallService extends Service&lt;String&gt; {

      Logger logger = new Logger(log, CallService.class.getName());
      org.slf4j.Logger LOG = LoggerFactory.getLogger(CallService.class);

      private StatusBar statusBar;

      private SettingModel settingModel;

      static IPCastSDK IPCastSDKObj = IPCastSDK.INSTANCE;
      public static IPCastSDK.IPCastCallBack ipcb;//回调函数

      //设置回调函数
      public static IPCastSDK.IPCastCallBack getIpcb() {
          return ipcb;
      }

      public static void setIpcb(IPCastSDK.IPCastCallBack ipcb) {
          CallService.ipcb = ipcb;
      }

      public static void SDKInit() {
          IPCastSDKObj.IPCAST_SDKInit();
      }

      public CallService(StatusBar statusBar) {
          this.statusBar = statusBar;
          try {
              this.settingModel = FxUtil.readIniFile();
          } catch (IOException | NoSuchFieldException | IllegalAccessException e) {
              logger.error("加载配置文件错误！");
              this.settingModel = null;
              e.printStackTrace();
          }
      }


      public void stop() {
          Platform.runLater(() -&gt; {
              statusBar.textProperty().unbind();
              statusBar.progressProperty().unbind();
              statusBar.progressProperty().setValue(0);
              statusBar.setText("已停止...");
              logger.warn("呼叫线程已停止");
              this.cancel();
              this.reset();
              statusBar.setText("正在重启...");
              this.restart();
          });
          if (IPCastSDKObj != null) {
              IPCastSDKObj.IPCAST_DisConnect();
          }

      }

      public void startSdk(SettingModel settingModel) {
          //设置回调函数
          try {
              SDKInit();
              boolean isConn = IPCastSDKObj.IPCAST_Connect(settingModel.getParamIp(), settingModel.getParamUsername(), settingModel.getParamPassword());
              if (!isConn) {
                  logger.error("ICT服务器连接失败，检查连接参数是否正确");
  //                IPCastSDKObj.IPCAST_ExitStillPlay(1);
                  LOG.error("服务器连接失败!");
                  stop();
              } else {
                  setIpcb((EventNo, ParamStr) -&gt; {
                      LOG.debug(EventNo + " " + ParamStr);
                      return 0;
                  });
                  IPCastSDKObj.IPCAST_SetCallBack(ipcb);
                  LOG.info("连接成功！");
              }
              LOG.info(settingModel.getParamIp());
              LOG.info(settingModel.getParamUsername());
              LOG.info(settingModel.getDbPassword());
          } catch (Exception e) {
              logger.error("初始化DLL错误，检查DLL路径是否正确");
              e.printStackTrace();
              stop();
          }
      }


      public void clearMp3File() {
          File[] playedMp3;
          File file = new File(AudioUtil4.path);
          playedMp3 = file.listFiles();
          if (!Objects.isNull(playedMp3)) {
              for (File f : playedMp3) {
                  if (f.getName().contains(AudioUtil4.tail)) {
                      if (f.delete()) {
                          LOG.info("删除成功 : " + f.getAbsolutePath());
                      } else {
                          LOG.warn("删除失败 : " + f.getAbsolutePath());
                      }
                  }
              }
          }
      }

      public void sendPlay(String path, int[] pTList) throws UnsupportedEncodingException, InterruptedException {
          IPCastSDK.PlayFile.ByReference[] files = new IPCastSDK.PlayFile.ByReference[1];
          IPCastSDK.PlayFile.ByReference byReference1 = new IPCastSDK.PlayFile.ByReference();
          byReference1.fid = 0;
          byReference1.fname = path.getBytes("gbk");
          byReference1.fvol = 10;
          byReference1.write();
          files[0] = byReference1;
          int sid = -1;
          LOG.info(path);
          LOG.info("播放终端列表: " + Arrays.toString(pTList));
          sid = IPCastSDKObj.IPCAST_FilePlayStart(files, files.length, pTList, pTList.length, 500, IPCastSDK.PLAY_CYC_DANORDE, 0, 0);
          LOG.debug("SID:" + String.valueOf(sid));
      }

      public boolean getTermStatusEx(int[] tids) {
          boolean isIdle = false;
          IPCastSDK.TermAttr.ByReference term = new IPCastSDK.TermAttr.ByReference();
          for (int tid : tids) {
              if (IPCastSDKObj.IPCAST_GetTermStatusEx(tid, term)) {
                  if (term.status == 0) {
                      isIdle = true;
                  } else {
                      isIdle = false;
                      break;
                  }
              } else {
                  isIdle = false;
                  break;
              }
          }
          return isIdle;
      }

      @Override
      protected void executeTask(Task&lt;String&gt; task) {
          super.executeTask(task);
      }


      @Override
      protected void ready() {
          super.ready();
          LOG.info("ready" + Platform.isFxApplicationThread());
      }

      @Override
      protected void scheduled() {
          super.scheduled();
          LOG.info("scheduled " + Platform.isFxApplicationThread());
      }

      @Override
      protected void running() {
          super.running();
          logger.info("启动呼叫线程");
          LOG.info("running " + Platform.isFxApplicationThread());
      }

      @Override
      protected void succeeded() {
          super.succeeded();
          LOG.info("succeeded " + Platform.isFxApplicationThread());
      }

      @Override
      protected void cancelled() {
          super.cancelled();
          LOG.info("cancelled " + Platform.isFxApplicationThread());
      }

      @Override
      protected void failed() {
          super.failed();
          logger.info("Call Thread Start Failed ! Please Check You Config!");
          stop();
          LOG.info("failed " + Platform.isFxApplicationThread());
      }

      @Override
      protected Task&lt;String&gt; createTask() {
          return new Task&lt;String&gt;() {
              @Override
              protected String call() {
                  try {
                      updateMessage("初始化...");
                      startSdk(settingModel);
                      updateProgress(-1, 100);
                      VoiceJDBC voiceJDBC = new VoiceJDBC(settingModel);
                      updateMessage("Run...");
                      while (!isCancelled()) {
                          List&lt;MesAndonPlay&gt; mesAndonIssueDets = voiceJDBC.getTop5Voice();
                          for (MesAndonPlay u : mesAndonIssueDets) {
                              String[] terminalIds = u.getTerminalId().split(",");
                              int[] terminalIdsInt = new int[terminalIds.length];
                              for (int i = 0; i &lt; terminalIds.length; i++) {
                                  terminalIdsInt[i] = Integer.parseInt(terminalIds[i]);
                              }
                              boolean idle = getTermStatusEx(terminalIdsInt);
                              LOG.info("idle : " + idle);
                              if (idle) {
                                  if (!IPCastSDKObj.IPCAST_ServerStatus()) {
                                      IPCastSDKObj.IPCAST_Connect(settingModel.getParamIp(), settingModel.getParamUsername(), settingModel.getParamPassword());
                                  }
                                  if (!IPCastSDKObj.IPCAST_ServerStatus()) {
                                      logger.info("无法连接广播 ");
                                      stop();
                                      break;
                                  }
                                  LOG.info(u.getPlayCommand());
                                  List&lt;PlayModel&gt; playModels = AudioUtil4.getPlayList(u.getPlayCommand());
                                  File path = AudioUtil4.mergeMp3(playModels);

                                  //播放
                                  sendPlay(path.getAbsolutePath(), terminalIdsInt);
                                  logger.warn(u.getPlayCommand());
                                  Thread.sleep(1000);
                                  voiceJDBC.finishDet(u.getId());
                              } else {
                                  updateProgress(-1, 100);
                                  updateMessage("正在呼叫...");
                              }
                          }
                          Thread.sleep(Integer.parseInt(settingModel.refreshTime.getValue()));
                          clearMp3File();
                          updateMessage("等待...");
                      }
                  } catch (SQLException | ClassNotFoundException throwables) {
                      logger.error(throwables.getMessage());
                      LOG.error(throwables.getMessage());
                      stop();
                      throwables.printStackTrace();
                  } catch (IOException ioException) {
                      logger.error("MP3生成失败!");
                      logger.error(ioException.getMessage());
                      LOG.error(ioException.getMessage());
                      stop();
                      ioException.printStackTrace();
                  } catch (Exception e) {
                      logger.error(e.getMessage());
                      LOG.error(e.getMessage());
                      stop();
                      e.printStackTrace();
                  }
                  //                catch (InterruptedException e) {
                  //                    logger.error("播放失败!");
                  //                    logger.error(e.getMessage());
                  //                    stop();
                  //                    e.printStackTrace();
                  //                }
                  return null;
              }
          };
      }
  }
  </code></pre><h5>JavaFx Log日志输出</h5><p>界面 :</p><p><img src="/upload/files/png/16934fdbc283e2f23b5daa126ae7d12f.png" alt=""></p><p>这个log日志的实现参考：<a href="https://stackoverflow.com/questions/24116858/most-efficient-way-to-log-messages-to-javafx-textarea-via-threads-with-simple-cu" target="">https://stackoverflow.com/questions/24116858/most-efficient-way-to-log-messages-to-javafx-textarea-via-threads-with-simple-cu</a></p><p>项目中使用了两个LOG，一个是GUI的LOG界面LogViewer，还有Log4j用来生成log文件。</p><p>完毕。</p><p><br></p><p><br></p><p><br></p><p><br></p><p><br></p><p><br></p><p><br></p><p><br></p><p><br></p><p><br></p>

markdown_text:
  Java调用Windows SAPI.spVoice。
  Java作为跨平台语音对调用Windows是没有原生支持的，所以需要使用第三方框架来做中转实现目的。
  Jacob是一个Java库，可让Java应用程序与Microsoft Windows DLL或COM库进行通信。它通过使用定制的DLL来实现此目的，Jacob Java类通过JNI与之通信。Java库和dll将Java开发人员与基础Windows库隔离开来，因此Java开发人员不必编写自定义JNI代码。Jacob不用于创建ActiveX插件或Microsoft Windows应用程序内部的其他模块。
  项目地址：https://github.com/freemansoft/jacob-project
  使用
  Jacob 1.8下载后里面拥有以下文件

  将对应当前Java版本的DLL文件放到Java/bin目录，将jacob.jar作为项目依赖引入。
  SAPI (Microsoft Speech API )
  文档：https://docs.microsoft.com/en-us/previous-versions/windows/desktop/ee125663(v=vs.85)?redirectedfrom=msdn
  使用 jacob 来调用SAPI。
  关于如何使用这里的博客讲的很详细:https://blog.csdn.net/asuyunlong/article/details/50083421/
  但是我是用的是第三方语音库，在切换语音包时会报错。
   @Test
      public void test2() {
          MSTTSSpeech speech = new MSTTSSpeech();
          String[] vs = speech.getVoices();
          System.out.println(Arrays.toString(vs));
          String text = "TTS TEST";

          speech.setFormatType(6);
          speech.changeVoice(1);
          // speech.setRate(-1);
          // speech.setRate(-1);
          System.out.println(getClass().getResource("mp3").getPath() + "\\" + UUID.randomUUID() + ".wav");
          String url = getClass().getResource("mp3").getPath();
          File wavFile = new File(url);
          if (!wavFile.exists()) {
              wavFile.getParentFile().mkdirs();
          }

          speech.saveToWav(text, url.substring(1) + "\\" + UUID.randomUUID() + ".wav");
      }

  运行测试后出错:
  A COM exception has been encountered:
  At Invoke of: Voice
  Description: 80020003 / 找不到成员。

  com.jacob.com.ComFailException: A COM exception has been encountered:
  At Invoke of: Voice
  Description: 80020003 / 找不到成员。

      at com.jacob.com.Dispatch.invokev(Native Method)
      ...

  Description: 80020003 / 找不到成员。 这个问题我找了很久，在我的电脑无法使用，但是可以使用控制面板项修改系统默认语音达到效果：
  C:\Windows\SysWOW64\Speech\SpeechUX\sapi.cpl

  Wav转Mp3重新编码并合并多个Mp3文件
  https://lame.sourceforge.io/
  现在的需求是，我有一段这样的文本: [P]Call.mp3;[T] 呼叫 XXX 前往 SMT 3号线
  其中 [P]Call.mp3;[T] 是现有mp3文件，而其他文本是要通过sapi生成的，而且文本中可能还会出现外部的mp3文件。
  所以思路是将mp3文件名称和需要转语音的文本按顺序抽取出来，将文字转wav，wav转mp3，最后再合并mp3文件。
  代码 : AudioUtil4.java
  public class AudioUtil4 {
      public static String path = null;
      public static String lame = null;
      static MSTTSSpeech speech = new MSTTSSpeech();

      static Logger LOG = LoggerFactory.getLogger(AudioUtil4.class);
      //合并mp3
      public static File mergeMp3(List<PlayModel> playModels) throws IOException {
          Vector<FileInputStream> fileInputStreams = new Vector<>();
          for (PlayModel playModel : playModels) {
              if (playModel.isMp3()) {
                  playModel.setPath(path + "\\" + playModel.getContent());
              } else {
                  playModel.setPath(AudioUtil4.textToMp3(playModel.getContent()));
              }
              fileInputStreams.add(new FileInputStream(playModel.getPath()));
             LOG.info("合并文件:" + playModel.getPath());
          }
          SequenceInputStream sequenceInputStream = new SequenceInputStream(fileInputStreams.elements());
          String mergePath = path + "\\" + UUID.randomUUID() + ".mp3";
          File afterMergeMp3 = new File(mergePath);
          boolean isSuccess = afterMergeMp3.createNewFile();
          if (isSuccess) {
              FileOutputStream fileOutputStream = new FileOutputStream(afterMergeMp3);//destinationfile
              int temp;

              while ((temp = sequenceInputStream.read()) != -1) {
                  // System.out.print( (char) temp ); // to print at DOS prompt
                  fileOutputStream.write(temp);   // to write to file
              }
              for (FileInputStream f : fileInputStreams) {
                  f.close();
              }
              fileOutputStream.close();
              sequenceInputStream.close();
              playModels.forEach(playModel -> {
                  if (!playModel.isMp3()) {
                      new File(playModel.getPath()).delete();
                  }
              });
          } else {
              throw new IOException("mp3创建失败! " + mergePath);
          }
          return new File(mergePath);
      }
      //获取播放列表 抽取
      public static List<PlayModel> getPlayList(String message) {
          int i = 0;
          char[] messageArr = message.toCharArray();
          List<PlayModel> playModels = new ArrayList<>();
          StringBuilder temp = new StringBuilder();
          while (i < messageArr.length) {
              if (messageArr[i] == '[') {
                  if (temp.length() > 0) {
                      playModels.add(new PlayModel(false, temp.toString(), null));
                      temp = new StringBuilder();
                  }
                  Map<String, Object> mp3 = getEndIndex(i, messageArr);
                  i = (int) mp3.get("endIndex");
                  playModels.add(new PlayModel(true, mp3.get("mp3").toString(), null));
              } else {
                  temp.append(messageArr[i]);
              }
              i += 1;
          }
          if (temp.length() > 0) {
              playModels.add(new PlayModel(false, temp.toString(), null));
          }
          return playModels;
      }
      //抽取
      public static Map<String, Object> getEndIndex(int index, char[] chars) {
          StringBuilder mp3 = new StringBuilder();
          int mp3EndIndex = -1;
          int endIndex = -1;
          for (int i = index + 3; i < chars.length; i++) {
              if (chars[i] == ']') {
                  endIndex = i;
                  break;
              } else if (chars[i] == ';') {
                  mp3EndIndex = i;
              } else {
                  if (mp3EndIndex == -1) {
                      mp3.append(chars[i]);
                  }
              }
          }
          Map<String, Object> result = new HashMap<>();
          result.put("endIndex", endIndex);
          result.put("mp3", mp3);
          return result;
      }
      //wav转mp3
      public static String textToMp3(String text) throws FileNotFoundException {
         LOG.info("转语音:" + text);
          File wavFile = new File(textToWav(text));
          if (!wavFile.exists()) {
              throw new FileNotFoundException("转语音失败!");
          } else {
              //to mp3 use lame
              String wavPathStr = wavFile.getPath();
              String mp3PathStr = wavFile.getPath().
                      replace(".wav", ".mp3");
              String cmds1 = lame +
                      String.format(" -b128 --resample 24 -m j  %s %s", wavPathStr, mp3PathStr);
  //                Arguments = string.Format("-b128 --resample 24 -m j \"{0}\" \"{1}\"", str3, str4)
              //                    String.format(" --silent -b 160 -m m -q 9-resample -tt  %s %s", wavPathStr, mp3PathStr);

              String cmds2 = lame +
                      String.format(" --scale 4 --mp3input %s %s", mp3PathStr, mp3PathStr.replace(".mp3", "L.mp3"));

             LOG.info("lame to mp3 : " + cmds1);
             LOG.info("large voice : " + cmds2);
              CommandUtil.exec(cmds1);
              CommandUtil.exec(cmds2);
              new File(wavPathStr).delete();
              new File(mp3PathStr).delete();
              return mp3PathStr.replace(".mp3", "L.mp3");
          }
      }

      //文本转wav
      private static String textToWav(String text) {
          speech.setFormatType(16);
          File wavFile = new File(path);
          if (!wavFile.exists()) {
              wavFile.getParentFile().mkdirs();
          }
         LOG.info(path);
          String filePath = path + "\\" + UUID.randomUUID() + ".wav";
          speech.saveToWav(text, filePath);
         LOG.info("wav文件保存到 : " + filePath);
          return filePath;
      }

  测试：
   @Test
      public void getPlayList() throws IOException {
          File directory = new File("");//设定为当前文件夹
          String mp3Path = directory.getAbsolutePath() + "\\mp3";
          String lamePath = directory.getAbsolutePath() + "\\lame.exe";

          AudioUtil4.path = mp3Path;
          AudioUtil4.lame = lamePath;


          List<PlayModel> playModels = AudioUtil4.getPlayList("[P]PromptRing.MP3;[T]请 袁振 速到 SMD 8号线 ");

          System.out.println("合并mp3:" + AudioUtil4.mergeMp3(playModels));
      }

  命令行代码：
  CommandUtil.java
  public class CommandUtil {
      static Logger LOG = LoggerFactory.getLogger(CommandUtil.class);

      public static int exec(String comm) {
          int finished = 0;
          try {
              Runtime rt = Runtime.getRuntime();
              Process proc = rt.exec(comm);
              InputStream stderr = proc.getErrorStream();
              InputStreamReader isr = new InputStreamReader(stderr);
              BufferedReader br = new BufferedReader(isr);
              String line = null;
              LOG.info("<error></error>");
              while ((line = br.readLine()) != null) {
                  String encoded = new String(line.getBytes("gbk"), "GB18030");//GB2312/CP936/GB18030
                  LOG.info(encoded);
                  LOG.info(line);
              }
              int exitVal = proc.waitFor();
              LOG.info("Process exitValue:" + exitVal);
              finished = exitVal;
          } catch (Exception e) {
              e.printStackTrace();
          }
          return finished;
      }
  }

  Call Service 核心：
  package cn.ciemis.service;

  import cn.ciemis.controller.MainController;
  import cn.ciemis.db.VoiceJDBC;
  import cn.ciemis.entity.*;
  import cn.ciemis.log.Log;
  import cn.ciemis.log.Logger;
  import cn.ciemis.sdk.IPCastSDK;
  import cn.ciemis.util.AudioUtil4;
  import cn.ciemis.util.FxUtil;
  import javafx.application.Platform;
  import javafx.concurrent.Service;
  import javafx.concurrent.Task;
  import org.controlsfx.control.StatusBar;
  import org.slf4j.LoggerFactory;

  import java.io.File;
  import java.io.IOException;
  import java.io.UnsupportedEncodingException;
  import java.sql.SQLException;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.List;
  import java.util.Objects;

  import static cn.ciemis.controller.MainController.log;

  public class CallService extends Service<String> {

      Logger logger = new Logger(log, CallService.class.getName());
      org.slf4j.Logger LOG = LoggerFactory.getLogger(CallService.class);

      private StatusBar statusBar;

      private SettingModel settingModel;

      static IPCastSDK IPCastSDKObj = IPCastSDK.INSTANCE;
      public static IPCastSDK.IPCastCallBack ipcb;//回调函数

      //设置回调函数
      public static IPCastSDK.IPCastCallBack getIpcb() {
          return ipcb;
      }

      public static void setIpcb(IPCastSDK.IPCastCallBack ipcb) {
          CallService.ipcb = ipcb;
      }

      public static void SDKInit() {
          IPCastSDKObj.IPCAST_SDKInit();
      }

      public CallService(StatusBar statusBar) {
          this.statusBar = statusBar;
          try {
              this.settingModel = FxUtil.readIniFile();
          } catch (IOException | NoSuchFieldException | IllegalAccessException e) {
              logger.error("加载配置文件错误！");
              this.settingModel = null;
              e.printStackTrace();
          }
      }


      public void stop() {
          Platform.runLater(() -> {
              statusBar.textProperty().unbind();
              statusBar.progressProperty().unbind();
              statusBar.progressProperty().setValue(0);
              statusBar.setText("已停止...");
              logger.warn("呼叫线程已停止");
              this.cancel();
              this.reset();
              statusBar.setText("正在重启...");
              this.restart();
          });
          if (IPCastSDKObj != null) {
              IPCastSDKObj.IPCAST_DisConnect();
          }

      }

      public void startSdk(SettingModel settingModel) {
          //设置回调函数
          try {
              SDKInit();
              boolean isConn = IPCastSDKObj.IPCAST_Connect(settingModel.getParamIp(), settingModel.getParamUsername(), settingModel.getParamPassword());
              if (!isConn) {
                  logger.error("ICT服务器连接失败，检查连接参数是否正确");
  //                IPCastSDKObj.IPCAST_ExitStillPlay(1);
                  LOG.error("服务器连接失败!");
                  stop();
              } else {
                  setIpcb((EventNo, ParamStr) -> {
                      LOG.debug(EventNo + " " + ParamStr);
                      return 0;
                  });
                  IPCastSDKObj.IPCAST_SetCallBack(ipcb);
                  LOG.info("连接成功！");
              }
              LOG.info(settingModel.getParamIp());
              LOG.info(settingModel.getParamUsername());
              LOG.info(settingModel.getDbPassword());
          } catch (Exception e) {
              logger.error("初始化DLL错误，检查DLL路径是否正确");
              e.printStackTrace();
              stop();
          }
      }


      public void clearMp3File() {
          File[] playedMp3;
          File file = new File(AudioUtil4.path);
          playedMp3 = file.listFiles();
          if (!Objects.isNull(playedMp3)) {
              for (File f : playedMp3) {
                  if (f.getName().contains(AudioUtil4.tail)) {
                      if (f.delete()) {
                          LOG.info("删除成功 : " + f.getAbsolutePath());
                      } else {
                          LOG.warn("删除失败 : " + f.getAbsolutePath());
                      }
                  }
              }
          }
      }

      public void sendPlay(String path, int[] pTList) throws UnsupportedEncodingException, InterruptedException {
          IPCastSDK.PlayFile.ByReference[] files = new IPCastSDK.PlayFile.ByReference[1];
          IPCastSDK.PlayFile.ByReference byReference1 = new IPCastSDK.PlayFile.ByReference();
          byReference1.fid = 0;
          byReference1.fname = path.getBytes("gbk");
          byReference1.fvol = 10;
          byReference1.write();
          files[0] = byReference1;
          int sid = -1;
          LOG.info(path);
          LOG.info("播放终端列表: " + Arrays.toString(pTList));
          sid = IPCastSDKObj.IPCAST_FilePlayStart(files, files.length, pTList, pTList.length, 500, IPCastSDK.PLAY_CYC_DANORDE, 0, 0);
          LOG.debug("SID:" + String.valueOf(sid));
      }

      public boolean getTermStatusEx(int[] tids) {
          boolean isIdle = false;
          IPCastSDK.TermAttr.ByReference term = new IPCastSDK.TermAttr.ByReference();
          for (int tid : tids) {
              if (IPCastSDKObj.IPCAST_GetTermStatusEx(tid, term)) {
                  if (term.status == 0) {
                      isIdle = true;
                  } else {
                      isIdle = false;
                      break;
                  }
              } else {
                  isIdle = false;
                  break;
              }
          }
          return isIdle;
      }

      @Override
      protected void executeTask(Task<String> task) {
          super.executeTask(task);
      }


      @Override
      protected void ready() {
          super.ready();
          LOG.info("ready" + Platform.isFxApplicationThread());
      }

      @Override
      protected void scheduled() {
          super.scheduled();
          LOG.info("scheduled " + Platform.isFxApplicationThread());
      }

      @Override
      protected void running() {
          super.running();
          logger.info("启动呼叫线程");
          LOG.info("running " + Platform.isFxApplicationThread());
      }

      @Override
      protected void succeeded() {
          super.succeeded();
          LOG.info("succeeded " + Platform.isFxApplicationThread());
      }

      @Override
      protected void cancelled() {
          super.cancelled();
          LOG.info("cancelled " + Platform.isFxApplicationThread());
      }

      @Override
      protected void failed() {
          super.failed();
          logger.info("Call Thread Start Failed ! Please Check You Config!");
          stop();
          LOG.info("failed " + Platform.isFxApplicationThread());
      }

      @Override
      protected Task<String> createTask() {
          return new Task<String>() {
              @Override
              protected String call() {
                  try {
                      updateMessage("初始化...");
                      startSdk(settingModel);
                      updateProgress(-1, 100);
                      VoiceJDBC voiceJDBC = new VoiceJDBC(settingModel);
                      updateMessage("Run...");
                      while (!isCancelled()) {
                          List<MesAndonPlay> mesAndonIssueDets = voiceJDBC.getTop5Voice();
                          for (MesAndonPlay u : mesAndonIssueDets) {
                              String[] terminalIds = u.getTerminalId().split(",");
                              int[] terminalIdsInt = new int[terminalIds.length];
                              for (int i = 0; i < terminalIds.length; i++) {
                                  terminalIdsInt[i] = Integer.parseInt(terminalIds[i]);
                              }
                              boolean idle = getTermStatusEx(terminalIdsInt);
                              LOG.info("idle : " + idle);
                              if (idle) {
                                  if (!IPCastSDKObj.IPCAST_ServerStatus()) {
                                      IPCastSDKObj.IPCAST_Connect(settingModel.getParamIp(), settingModel.getParamUsername(), settingModel.getParamPassword());
                                  }
                                  if (!IPCastSDKObj.IPCAST_ServerStatus()) {
                                      logger.info("无法连接广播 ");
                                      stop();
                                      break;
                                  }
                                  LOG.info(u.getPlayCommand());
                                  List<PlayModel> playModels = AudioUtil4.getPlayList(u.getPlayCommand());
                                  File path = AudioUtil4.mergeMp3(playModels);

                                  //播放
                                  sendPlay(path.getAbsolutePath(), terminalIdsInt);
                                  logger.warn(u.getPlayCommand());
                                  Thread.sleep(1000);
                                  voiceJDBC.finishDet(u.getId());
                              } else {
                                  updateProgress(-1, 100);
                                  updateMessage("正在呼叫...");
                              }
                          }
                          Thread.sleep(Integer.parseInt(settingModel.refreshTime.getValue()));
                          clearMp3File();
                          updateMessage("等待...");
                      }
                  } catch (SQLException | ClassNotFoundException throwables) {
                      logger.error(throwables.getMessage());
                      LOG.error(throwables.getMessage());
                      stop();
                      throwables.printStackTrace();
                  } catch (IOException ioException) {
                      logger.error("MP3生成失败!");
                      logger.error(ioException.getMessage());
                      LOG.error(ioException.getMessage());
                      stop();
                      ioException.printStackTrace();
                  } catch (Exception e) {
                      logger.error(e.getMessage());
                      LOG.error(e.getMessage());
                      stop();
                      e.printStackTrace();
                  }
                  //                catch (InterruptedException e) {
                  //                    logger.error("播放失败!");
                  //                    logger.error(e.getMessage());
                  //                    stop();
                  //                    e.printStackTrace();
                  //                }
                  return null;
              }
          };
      }
  }

  JavaFx Log日志输出
  界面 :

  这个log日志的实现参考：https://stackoverflow.com/questions/24116858/most-efficient-way-to-log-messages-to-javafx-textarea-via-threads-with-simple-cu
  项目中使用了两个LOG，一个是GUI的LOG界面LogViewer，还有Log4j用来生成log文件。
  完毕。


title: C# DotNetty TCP对接 松下扣料机
intro: TCP Socket Server ， DotNetty实现。
created_time: 2021-11-27 06:00:02.054425
html_text:
  <h5 id="h5-tcp-socket-server-dotnetty-"><a name="TCP Socket Server ， DotNetty实现。" class="reference-link"></a><span class="header-link octicon octicon-link"></span>TCP Socket Server ， DotNetty实现。</h5><p>松下扣料机的厂商人员只来一天半，也就是要在一天内完成对接，你只能通过以往的报文日志来分析大致的通讯情况，松下厂商并不提供模拟测试程序。</p>
  <p>闭门造车写了几天，在正式对接那天血崩，意料之中。</p>
  <p>说下崩在哪。</p>
  <p>松下报文格式是这样的:</p>
  <p>心跳PING:</p>
  <p>(文档版)：</p>
  <p><img src="https://img2020.cnblogs.com/blog/1252682/202102/1252682-20210211113632493-1478398478.png" alt="">
  <p>你以为是什么？</p>
  <p>XML字符串吗？我是这么想的，但是对接那天我的程序始终无法和扣料机发生心跳，一发生心跳就断开连接，一群人在我电脑和扣料机跑来跑去看日志服了。</p>
  <p>报文有问题。</p>
  <p><code>&lt;STX&gt;</code>8<code>&lt;SHO&gt;</code>PING_REQ<code>&lt;ETX&gt;</code></p>
  <p>不是字符串，不是XML，而是ASCII码。</p>
  <pre><code class="lang-c#">        public static byte STX =  0x02;
          public static byte SOH = 0x01;
          public static byte ETX = 0x03;
          public static char STX_CHAR = '\u0002';
          public static char SHO_CHAR = '\u0001';
          public static char ETX_CHAR = '\u0003';
  </code></pre>
  <p><img src="https://img2020.cnblogs.com/blog/1252682/202102/1252682-20210211113648295-207426978.png" alt="">
  <p>将0X02对应字符粘贴到notepad++上。</p>
  <p><img src="https://img2020.cnblogs.com/blog/1252682/202102/1252682-20210211113655871-1806930789.png" alt="">
  <p>无语了。</p>
  <p>然后改程序，算是勉强完成心跳，因为和Netty一样有自动弹射心跳所以不难。</p>
  <p>难得是解码器，在Socket通讯中要考虑以下情况。</p>
  <p>( tcp是流,没有界限.也就没所谓的包.这里包，算是约定)</p>
  <p>粘包、多包和少包, 断包</p>
  <pre><code class="lang-xml">&lt;message&gt;
   &lt;header messageClass="530" transactionID="1234567890" reply="0"&gt;
   &lt;location routeID="1001" routeName="LINE1" equipmentID="1000"
   equipmentName="equip1" zoneID="1001" zonePos="2" zoneName="SMT1" laneNo="0"
  controllerGuid="abcd12345678"/&gt;
   &lt;/header&gt;
   &lt;body&gt;
   &lt;result errorCode="0" errorText="" actionCode=""/&gt;
  &lt;/body&gt;
  &lt;/message&gt;
  &lt;message&gt;
   &lt;header messageClass="530" transactionID="1234567890" reply="0"&gt;
   &lt;location routeID="1001" routeName="LINE1" equipmentID="1000"
   equipmentName="equip1" zoneID="1001" zonePos="2" zoneName="SMT1" laneNo="0"
  controllerGuid="abcd12345678"/&gt;
   &lt;/header&gt;
   &lt;body&gt;
   &lt;result errorCode="0" errorText="" actionCode=""/&gt;
  &lt;/body&gt;
  &lt;/message&gt;
  </code></pre>
  <pre><code class="lang-xml">&lt;message&gt;
   &lt;header messageClass="530" transactionID="1234567890" reply="0"&gt;
   &lt;location routeID="10
  --第一次发送--
  01" routeName="LINE1" equipmentID="1000"
   equipmentName="equip1" zoneID="1001" zonePos="2" zoneName="SMT1" laneNo="0"
  controllerGuid="abcd12345678"/&gt;
   &lt;/header&gt;
   &lt;body&gt;
   &lt;result errorCode="0" errorText="" actionCode=""/&gt;
  &lt;/body&gt;
  &lt;/message&gt;
  --第二次发送--
  </code></pre>
  <p>总之有许多情况要考虑，报文提供有长度，我开始也是通过长度去读取报文，但是我程序算出来的长度和扣料机发过来的报文长度是不一样的，不清楚是什么原因。</p>
  <p>最后不用它的长度，定义缓冲区，当有消息进来解码器while处理，将所有情况走一遍，而且如果有多个连接，能够保证在同时粘包断包的情况下互相不影响。</p>
  <pre><code class="lang-c#">  /// &lt;summary&gt;
      /// 解码
      /// &lt;/summary&gt;
      public class DecoderHandler : ByteToMessageDecoder
      {
          IByteBuffer byteBuffer = ByteBufferUtil.DefaultAllocator.Buffer();
          public Int32 PipleLineId = 0;

          protected override void Decode(IChannelHandlerContext context, IByteBuffer input, List&lt;object&gt; output)
          {
              int len = input.ReadableBytes;
              byte[] bytes = new byte[len];
              input.GetBytes(0, bytes);
              SimpleLogHelper.LogInfo("PipleLineId = " + PipleLineId + "接收信息 : " + Encoding.Default.GetString(bytes));

              while (bytes.Length &gt; 0)
              {
               SimpleLogHelper.LogInfo("PipleLineId = " + PipleLineId + "本次处理 : " + Encoding.Default.GetString(bytes));
                  int stxIndex = Array.IndexOf(bytes, SocketHelper.STX);
                  int etxIndex = Array.IndexOf(bytes, SocketHelper.ETX);

                  //缓存没有
                  if (byteBuffer.ReadableBytes == 0)
                  {
                      //有头
                      if (stxIndex != -1)
                      {
                          //有尾
                          if (etxIndex != -1)
                          {
                              int shoIndex = Array.IndexOf(bytes, SocketHelper.SOH);
                              byte[] bodyLen = bytes.Skip(stxIndex + 1).Take(shoIndex - stxIndex - 1).ToArray();
                              byte[] message = bytes.Skip(shoIndex + 1).Take(etxIndex - shoIndex - 1 ).ToArray();
                              Package package = new Package();
                              package.length = int.Parse(Encoding.Default.GetString(bodyLen));
                              package.messageBody = Encoding.Default.GetString(message);
                              SocketHelper.SendPackage(package, output);
                              bytes = bytes.Skip(etxIndex + 1).ToArray();
                          }
                          else
                          {
                              //如果stxIndex 头部前有报文，丢弃前面的报文
                              byteBuffer.WriteBytes(bytes.Skip(stxIndex).ToArray());

                              int tmpLen = byteBuffer.ReadableBytes;
                              byte[] tmpBytes = new byte[tmpLen];
                              byteBuffer.GetBytes(0, tmpBytes);
                              SimpleLogHelper.LogInfoSuccess("PipleLineId = " + PipleLineId + "缓存1 : " + Encoding.Default.GetString(tmpBytes));

                              bytes = new byte[0];
                          }
                      }
                      else
                      {
                          //缓存为空，报文没头，跳过
                          bytes = new byte[0];
                      }
                  }
                  else
                  {
                      if (stxIndex != -1)
                      {
                          if (stxIndex &lt; etxIndex || etxIndex == -1)
                          {
                              int tmpLen = byteBuffer.ReadableBytes;
                              byte[] tmpBytes = new byte[tmpLen];
                              byteBuffer.GetBytes(0, tmpBytes);
                              SimpleLogHelper.LogError("PipleLineId = " + PipleLineId + "丢弃 : " + Encoding.Default.GetString(tmpBytes));

                              bytes = bytes.Skip(stxIndex).ToArray();
                              byteBuffer.Clear();
                              continue;
                          }
                      }

                      if (etxIndex != -1)
                      {
                          byteBuffer.WriteBytes(bytes.Take(etxIndex + 1).ToArray());

                          int tmpLen = byteBuffer.ReadableBytes;
                          byte[] tmpBytes = new byte[tmpLen];
                          byteBuffer.GetBytes(0, tmpBytes);
                          SimpleLogHelper.LogInfoSuccess("PipleLineId = " + PipleLineId + "缓存3 : " + Encoding.Default.GetString(tmpBytes));

                          int buffLen = byteBuffer.ReadableBytes;
                          byte[] currentBytes = new byte[buffLen];
                          byteBuffer.GetBytes(0, currentBytes);
                          int shoIndex = Array.IndexOf(currentBytes, SocketHelper.SOH);
                          byte[] bodyLen = currentBytes.Skip(1).Take(shoIndex - 1).ToArray();
                          byte[] message = currentBytes.Skip(shoIndex + 1).SkipLast(1).ToArray();

                          Package package = new Package();
                          package.length = int.Parse(Encoding.Default.GetString(bodyLen));
                          package.messageBody = Encoding.Default.GetString(message);
                          SocketHelper.SendPackage(package, output);

                          byteBuffer.Clear();
                          bytes = bytes.Skip(etxIndex + 1).ToArray();
                      }
                      else
                      {
                          //断包 进缓冲
                          byteBuffer.WriteBytes(bytes);
                          bytes = new byte[0];

                          int tmpLen = byteBuffer.ReadableBytes;
                          byte[] tmpBytes = new byte[tmpLen];
                          byteBuffer.GetBytes(0, tmpBytes);
                          SimpleLogHelper.LogInfoSuccess("PipleLineId = " + PipleLineId + "缓存2 : " + Encoding.Default.GetString(tmpBytes));
                      }
                  }
              }
              input.SkipBytes(len);
          }
      }
  </code></pre>

markdown_text:
  ##### TCP Socket Server ， DotNetty实现。

  松下扣料机的厂商人员只来一天半，也就是要在一天内完成对接，你只能通过以往的报文日志来分析大致的通讯情况，松下厂商并不提供模拟测试程序。

  闭门造车写了几天，在正式对接那天血崩，意料之中。

  说下崩在哪。

  松下报文格式是这样的:

  心跳PING:

  (文档版)：

  ![](https://img2020.cnblogs.com/blog/1252682/202102/1252682-20210211113632493-1478398478.png)


  你以为是什么？

  XML字符串吗？我是这么想的，但是对接那天我的程序始终无法和扣料机发生心跳，一发生心跳就断开连接，一群人在我电脑和扣料机跑来跑去看日志服了。

  报文有问题。

  `<STX>`8`<SHO>`PING_REQ`<ETX>`

  不是字符串，不是XML，而是ASCII码。

  ```c#
   	   public static byte STX =  0x02;
          public static byte SOH = 0x01;
          public static byte ETX = 0x03;
          public static char STX_CHAR = '\u0002';
          public static char SHO_CHAR = '\u0001';
          public static char ETX_CHAR = '\u0003';
  ```

  ![](https://img2020.cnblogs.com/blog/1252682/202102/1252682-20210211113648295-207426978.png)




  将0X02对应字符粘贴到notepad++上。

  ![](https://img2020.cnblogs.com/blog/1252682/202102/1252682-20210211113655871-1806930789.png)

  无语了。

  然后改程序，算是勉强完成心跳，因为和Netty一样有自动弹射心跳所以不难。

  难得是解码器，在Socket通讯中要考虑以下情况。

  ( tcp是流,没有界限.也就没所谓的包.这里包，算是约定)

  粘包、多包和少包, 断包

  ```xml
  <message>
   <header messageClass="530" transactionID="1234567890" reply="0">
   <location routeID="1001" routeName="LINE1" equipmentID="1000"
   equipmentName="equip1" zoneID="1001" zonePos="2" zoneName="SMT1" laneNo="0"
  controllerGuid="abcd12345678"/>
   </header>
   <body>
   <result errorCode="0" errorText="" actionCode=""/>
  </body>
  </message>
  <message>
   <header messageClass="530" transactionID="1234567890" reply="0">
   <location routeID="1001" routeName="LINE1" equipmentID="1000"
   equipmentName="equip1" zoneID="1001" zonePos="2" zoneName="SMT1" laneNo="0"
  controllerGuid="abcd12345678"/>
   </header>
   <body>
   <result errorCode="0" errorText="" actionCode=""/>
  </body>
  </message>
  ```

  ```xml
  <message>
   <header messageClass="530" transactionID="1234567890" reply="0">
   <location routeID="10
  --第一次发送--
  01" routeName="LINE1" equipmentID="1000"
   equipmentName="equip1" zoneID="1001" zonePos="2" zoneName="SMT1" laneNo="0"
  controllerGuid="abcd12345678"/>
   </header>
   <body>
   <result errorCode="0" errorText="" actionCode=""/>
  </body>
  </message>
  --第二次发送--
  ```

  总之有许多情况要考虑，报文提供有长度，我开始也是通过长度去读取报文，但是我程序算出来的长度和扣料机发过来的报文长度是不一样的，不清楚是什么原因。

  最后不用它的长度，定义缓冲区，当有消息进来解码器while处理，将所有情况走一遍，而且如果有多个连接，能够保证在同时粘包断包的情况下互相不影响。

  ```c#
    /// <summary>
      /// 解码
      /// </summary>
      public class DecoderHandler : ByteToMessageDecoder
      {
          IByteBuffer byteBuffer = ByteBufferUtil.DefaultAllocator.Buffer();
          public Int32 PipleLineId = 0;

          protected override void Decode(IChannelHandlerContext context, IByteBuffer input, List<object> output)
          {
              int len = input.ReadableBytes;
              byte[] bytes = new byte[len];
              input.GetBytes(0, bytes);
              SimpleLogHelper.LogInfo("PipleLineId = " + PipleLineId + "接收信息 : " + Encoding.Default.GetString(bytes));

              while (bytes.Length > 0)
              {
               SimpleLogHelper.LogInfo("PipleLineId = " + PipleLineId + "本次处理 : " + Encoding.Default.GetString(bytes));
                  int stxIndex = Array.IndexOf(bytes, SocketHelper.STX);
                  int etxIndex = Array.IndexOf(bytes, SocketHelper.ETX);

                  //缓存没有
                  if (byteBuffer.ReadableBytes == 0)
                  {
                      //有头
                      if (stxIndex != -1)
                      {
                          //有尾
                          if (etxIndex != -1)
                          {
                              int shoIndex = Array.IndexOf(bytes, SocketHelper.SOH);
                              byte[] bodyLen = bytes.Skip(stxIndex + 1).Take(shoIndex - stxIndex - 1).ToArray();
                              byte[] message = bytes.Skip(shoIndex + 1).Take(etxIndex - shoIndex - 1 ).ToArray();
                              Package package = new Package();
                              package.length = int.Parse(Encoding.Default.GetString(bodyLen));
                              package.messageBody = Encoding.Default.GetString(message);
                              SocketHelper.SendPackage(package, output);
                              bytes = bytes.Skip(etxIndex + 1).ToArray();
                          }
                          else
                          {
                              //如果stxIndex 头部前有报文，丢弃前面的报文
                              byteBuffer.WriteBytes(bytes.Skip(stxIndex).ToArray());

                              int tmpLen = byteBuffer.ReadableBytes;
                              byte[] tmpBytes = new byte[tmpLen];
                              byteBuffer.GetBytes(0, tmpBytes);
                              SimpleLogHelper.LogInfoSuccess("PipleLineId = " + PipleLineId + "缓存1 : " + Encoding.Default.GetString(tmpBytes));

                              bytes = new byte[0];
                          }
                      }
                      else
                      {
                          //缓存为空，报文没头，跳过
                          bytes = new byte[0];
                      }
                  }
                  else
                  {
                      if (stxIndex != -1)
                      {
                          if (stxIndex < etxIndex || etxIndex == -1)
                          {
                              int tmpLen = byteBuffer.ReadableBytes;
                              byte[] tmpBytes = new byte[tmpLen];
                              byteBuffer.GetBytes(0, tmpBytes);
                              SimpleLogHelper.LogError("PipleLineId = " + PipleLineId + "丢弃 : " + Encoding.Default.GetString(tmpBytes));

                              bytes = bytes.Skip(stxIndex).ToArray();
                              byteBuffer.Clear();
                              continue;
                          }
                      }

                      if (etxIndex != -1)
                      {
                          byteBuffer.WriteBytes(bytes.Take(etxIndex + 1).ToArray());

                          int tmpLen = byteBuffer.ReadableBytes;
                          byte[] tmpBytes = new byte[tmpLen];
                          byteBuffer.GetBytes(0, tmpBytes);
                          SimpleLogHelper.LogInfoSuccess("PipleLineId = " + PipleLineId + "缓存3 : " + Encoding.Default.GetString(tmpBytes));

                          int buffLen = byteBuffer.ReadableBytes;
                          byte[] currentBytes = new byte[buffLen];
                          byteBuffer.GetBytes(0, currentBytes);
                          int shoIndex = Array.IndexOf(currentBytes, SocketHelper.SOH);
                          byte[] bodyLen = currentBytes.Skip(1).Take(shoIndex - 1).ToArray();
                          byte[] message = currentBytes.Skip(shoIndex + 1).SkipLast(1).ToArray();

                          Package package = new Package();
                          package.length = int.Parse(Encoding.Default.GetString(bodyLen));
                          package.messageBody = Encoding.Default.GetString(message);
                          SocketHelper.SendPackage(package, output);

                          byteBuffer.Clear();
                          bytes = bytes.Skip(etxIndex + 1).ToArray();
                      }
                      else
                      {
                          //断包 进缓冲
                          byteBuffer.WriteBytes(bytes);
                          bytes = new byte[0];

                          int tmpLen = byteBuffer.ReadableBytes;
                          byte[] tmpBytes = new byte[tmpLen];
                          byteBuffer.GetBytes(0, tmpBytes);
                          SimpleLogHelper.LogInfoSuccess("PipleLineId = " + PipleLineId + "缓存2 : " + Encoding.Default.GetString(tmpBytes));
                      }
                  }
              }
              input.SkipBytes(len);
          }
      }
  ```


title: Qypt5 Excel 转 PDF 连接RabbitMQ自动打印
created_time: 2021-11-27 15:59:12.979020
intro:
  自动打印程序，Server端产生单子，Excel格式，将文件地址根据组发送到rabbitmq队列，程序订阅rabbitmq，抓取文件地址下载并调用打印机打印。 这里的订阅分组，每个组一个routing-key，程序可以自由订阅N个组。

html_text:
  <h2 id="h2-u4ECBu7ECD"><a name="介绍" class="reference-link"></a><span class="header-link octicon octicon-link"></span>介绍</h2><p>自动打印程序，Server端产生单子，Excel格式，将文件地址根据组发送到rabbitmq队列，程序订阅rabbitmq，抓取文件地址下载并调用打印机打印。<br>这里的订阅分组，每个组一个routing-key，程序可以自由订阅N个组。
  <p><img src="/upload/files/png/588e2c0d5ddce929c0bb6604e80ea750.png" alt="">
  <p>Python实现，GUI框架为Pyqt5。</p>
  <h4 id="h4-u8FDEu63A5u6253u5370u673A"><a name="连接打印机" class="reference-link"></a><span class="header-link octicon octicon-link"></span>连接打印机</h4><p>程序关闭会最小化到托盘，同时托盘拥有选择默认打印机功能。</p>
  <pre><code class="lang-python">
  class TrayIcon(QSystemTrayIcon):
      def __init__(self, parent=None, ):
          super(TrayIcon, self).__init__(parent)
          self.quit_action = QAction("退出", self, triggered=self.quit)
          self.default_printer = QAction('当前打印机:' + DEFAULT_PRINTER, self)
          self.refresh_action = QAction('刷新')
          self.refresh_action.triggered.connect(parent.init_data)
          self.printer_menus = QMenu()
          self.menu = QMenu()
          self.setToolTip("监控打印程序")

          self.show_menu()
          self.other()

      def show_menu(self):
          " 设计托盘的菜单，这里我实现了一个二级菜单 "
          print(DEFAULT_PRINTER)
          self.menu.addAction(self.default_printer)
          self.menu.addAction(self.refresh_action)
          self.menu.addSeparator()
          self.menu.addMenu(self.printer_menus, )

          self.menu.addAction(self.quit_action)
          self.printer_menus.setTitle("打印机列表")

          self.get_all_printer_actions()

          self.setContextMenu(self.menu)

      def get_all_printer_actions(self):
          for item in PRINTERS:
              action = QAction(item, self)
              action.triggered.connect(lambda: self.set_use_printer(self.sender()))
              if item == DEFAULT_PRINTER:
                  action.setCheckable(True)
                  action.setChecked(True)
              self.printer_menus.addAction(action)
              # self.printer_menus.addActions(printers)

      def set_use_printer(self, action):
          for item in self.printer_menus.actions():
              item.setCheckable(False)
              item.setChecked(False)
          action.setCheckable(True)
          action.setChecked(True)
          win32print.SetDefaultPrinterW(action.text())
          self.default_printer.setText('当前打印机:' + action.text())
          self.showMessage("信息", "当前打印机为:" + action.text(), self.icon)

      def other(self):
          self.activated.connect(self.iconClied)
          # 把鼠标点击图标的信号和槽连接
          # self.messageClicked.connect(self.mClied)
          # 把鼠标点击弹出消息的信号和槽连接
          self.setIcon(QIcon(":/images/logo.ico"))
          self.icon = self.MessageIcon()
          # 设置图标

      def iconClied(self, reason):
          "鼠标点击icon传递的信号会带有一个整形的值，1是表示单击右键，2是双击，3是单击左键，4是用鼠标中键点击"
          if reason == 2 or reason == 3:
              pw = self.parent()
              if pw.isVisible():
                  pw.hide()
              else:
                  pw.show()
          print(reason)


      def quit(self):
          "保险起见，为了完整的退出"
          self.setVisible(False)
          self.parent().exit()
          qApp.quit()
          sys.exit()
  </code></pre>
  <p>程序运行会获取所有本地打印机：</p>
  <pre><code class="lang-python">  if file.tryLock():  # 防止多开
          for it in win32print.EnumPrinters(2):
              PRINTERS.append(it[2])
          app.setQuitOnLastWindowClosed(False)
          w = window()
  </code></pre>
  <p><img src="/upload/files/png/9f9d1c93eef591be06da22c67942a8c9.png" alt="">
  <h4 id="h4-rabbitmq-"><a name="RabbitMQ 管理" class="reference-link"></a><span class="header-link octicon octicon-link"></span>RabbitMQ 管理</h4><p>RabbitMQ 订阅，取消订阅，文件下载，都在这了。</p>
  <pre><code class="lang-python">import logging
  import os
  import shutil
  import threading
  import time
  from urllib import request

  import pika
  import win32api
  import win32print
  import xlwings as xw

  logging.basicConfig(level=logging.DEBUG,  # 控制台打印的日志级别
                      filename='./printer.log',
                      filemode='a',
                      # a是追加模式，默认如果不写的话，就是追加模式
                      format=
                      '%(asctime)s - %(pathname)s[line:%(lineno)d] - %(levelname)s: %(message)s'
                      # 日志格式
                      )

  import pythoncom

  EXCHANGE_NAME = 'exchange-printOrder'
  SAVE_PATH = os.path.abspath('.') + '\\download'
  LOCK = 0

  import PDF_Util


  class MQ(threading.Thread):
      def __init__(self, keys, window):
          super().__init__()
          credentials = pika.PlainCredentials('rabbitmq', 'P@ssw0rd')  # mq用户名和密码，没有则需要自己创建
          self.conn = pika.BlockingConnection(
              pika.ConnectionParameters(host='10.86.13.83', virtual_host='/', credentials=credentials))
          self.channel = self.conn.channel()
          self.current_keys = keys
          self.log_area = window.log_area
          self.log_area.append('初始化RabbitMQ 成功')
          self.window = window
          self.window.statusbar.showMessage('已连接')
          self.lock = False
          # threading.Thread(target=self.clear_file).start()
          self.clear_file()

      def clear_file(self):
          try:
              if not os.path.exists(SAVE_PATH):
                  os.mkdir(SAVE_PATH)
              else:
                  shutil.rmtree(SAVE_PATH)
                  os.mkdir(SAVE_PATH)
              logging.info('清空文件')
              print('clear')
          except PermissionError as p:
              print('文件被占用,wait')
              logging.info('文件被占用,wait')
              pass

      def refresh(self, key, flag):
          if flag == 0:
              self.channel.queue_unbind(self.queue_name, exchange=EXCHANGE_NAME, routing_key=key)
              print('解绑:', key)
              logging.info('解绑:' + key)
              self.log_area.append('解绑:' + key + '成功')
          elif flag == 1:
              self.channel.queue_bind(self.queue_name, exchange=EXCHANGE_NAME, routing_key=key)
              logging.info('绑定:' + key)
              print('绑定:', key)
              self.log_area.append('绑定:' + key + '成功')
          else:
              pass
          pass

      def call_back(self, ch, method, properties, body):
          print(ch, method, properties)
          self.lock = True

          logging.info('收到来自: %s 的消息: %s' % (method.routing_key, body))
          logging.info('下载来自: %s 的文件: %s' % (method.routing_key, body))
          self.log_area.append('下载来自: %s 的文件: %s' % (method.routing_key, body))
          if not os.path.exists(SAVE_PATH):
              os.makedirs(SAVE_PATH)
          self.download(str(body)[2:][0:-1])

      def get_file_name(self, url: str):
          return url[url.rindex('/') + 1:]

      def download(self, URL):
          try:
              req = request.Request(URL)
              res = request.urlopen(url=req, timeout=5)
              get_img = res.read()
              with open(SAVE_PATH + '\\' + self.get_file_name(URL), 'wb') as f:
                  f.write(get_img)
              f.close()
              self.window.ti.showMessage('提示', "打印: " + self.get_file_name(URL), self.window.ti.icon)
              self.log_area.append('文件: %s 下载成功，开始打印' % (self.get_file_name(URL)))
              logging.info('文件: %s 下载成功，开始打印' % (self.get_file_name(URL)))
              currentPrinter = win32print.GetDefaultPrinterW()
              # hinstance = win32api.ShellExecute(0, "print", SAVE_PATH + '\\' + self.get_file_name(URL),
              #                                   '/d:"%s"' % currentPrinter,
              #                                   None, 0)
              # logging.info('打印返回值 %s' % hinstance)

              # order = '{0} {1} {2}'.format(GS_PRINT, pdf, currentPrinter)
              # os.system(order)
              pythoncom.CoInitialize()
              pdf_util = PDF_Util.PDFConverter(SAVE_PATH + '\\' + self.get_file_name(URL))
              pdf_util.run_conver()

              self.log_area.append('打印完成')
              logging.info('打印完成')
              self.lock = False
          except Exception as e:
              logging.error(str(e))
              self.log_area.append(str(e))

      def run(self):
          self.channel.exchange_declare(EXCHANGE_NAME, 'topic', True)
          result = self.channel.queue_declare('', exclusive=True)
          self.queue_name = result.method.queue
          logging.info('队列名称:{0}'.format(self.queue_name))
          print('队列名称:{0}'.format(self.queue_name))
          self.log_area.append('当前队列名称:' + self.queue_name)

          # routing_key = 'printOrder[06-15-KIT-B-R-01]'
          for key in self.current_keys:
              self.channel.queue_bind(exchange=EXCHANGE_NAME, queue=self.queue_name, routing_key=key)
          self.channel.basic_consume(queue=self.queue_name, on_message_callback=self.call_back, auto_ack=True)
          currentPrinter = win32print.GetDefaultPrinterW()
          self.log_area.append('当前默认打印机:' + currentPrinter)
          self.channel.start_consuming()


  if __name__ == '__main__':
      mq = MQ(['printOrder[06-15-KIT-B-R-01]', 'printOrder[06-15-KIT-B-R-02]', 'printOrder[06-15-KIT-B-R-03]'])
      mq.start()
      mq.refresh(['printOrder[06-15-KIT-B-R-01]', 'printOrder[06-15-KIT-B-R-02]'])
  </code></pre>
  <p>我这里还做了本地持久化，会把订阅的信息存在C盘User下的.monitor文件夹的setting.ini里面，也就是程序关闭后，再次打开会自动订阅上次已订阅的组。</p>
  <pre><code class="lang-python">class Config(object):

      def __init__(self):
          self.conf = ConfigParser()
          self.path = os.path.expanduser('~') + '\\' + '.monitor'
          self.name = "setting.ini"
          self.dir = self.path + '\\' + self.name
          if not os.path.isdir(self.path):
              os.makedirs(self.path)
          if not os.path.isfile(self.dir):
              fd = open(self.dir, mode="w", encoding="utf-8")
              fd.close()
              self.init_data()

      # 初始化数据
      def init_data(self):
          self.conf.add_section("TOPIC")
          self.conf.set("TOPIC", "DATA", json.dumps(GROUPS))
          self.conf.write(open(self.dir, 'a'))

      def refresh(self):
          self.conf.remove_section('TOPIC')
          self.conf.add_section('TOPIC')
          self.conf.set("TOPIC", "DATA", json.dumps(GROUPS))
          self.conf.write(open(self.dir, 'w'))

      def get_groups(self):
          self.conf.read(self.dir)
          data = self.conf.get('TOPIC', 'DATA')
          arr = json.loads(data)
          return arr
  </code></pre>
  <h4 id="h4--excel"><a name="打印Excel" class="reference-link"></a><span class="header-link octicon octicon-link"></span>打印Excel</h4><p>我本以为打印Excel没什么难的，可是客户机器上的Excel版本都是2016，太老了，在打印Excel的时候是要调用Excel程序的，而Excel打印，然后还会弹出 ‘当前文件已被修改，XXXXXX’，这就很不友好，需要人去点，就做不到静默打印的效果，<br>这问题无解，我总不能去动Excel，最后憋急了，将Excel转PDF，再用第三方工具实现静默打印。
  <pre><code class="lang-python"># -*- coding:utf-8 -*-
  import logging
  import os
  import subprocess

  import win32api
  import win32print
  from win32com.client import Dispatch, constants, gencache, DispatchEx

  SAVE_PATH = os.path.abspath('.') + '\\download'
  # GS_WIN32 = r'C:\Program Files (x86)\gs\gs9.55.0\bin\gswin32.exe'
  GS_PRINT = os.path.abspath('.') + '\\PDFtoPrinter.exe'
  logging.basicConfig(level=logging.DEBUG,  # 控制台打印的日志级别
                      filename='./printer.log',
                      filemode='a',
                      # a是追加模式，默认如果不写的话，就是追加模式
                      format=
                      '%(asctime)s - %(pathname)s[line:%(lineno)d] - %(levelname)s: %(message)s'
                      # 日志格式
                      )


  def print_pdf(export_file: str):
      currentPrinter = win32print.GetDefaultPrinterW()
      order = '{0} {1} {2}'.format(GS_PRINT, export_file, currentPrinter)
      res = subprocess.call(order, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
      print('打印返回值{0}'.format(res))
      logging.info('打印返回值{0}'.format(res))

  class PDFConverter:
      def __init__(self, pathname, export='.'):
          self._handle_postfix = ['doc', 'docx', 'ppt', 'pptx', 'xls', 'xlsx']
          self._filename_list = list()
          self._export_folder = SAVE_PATH
          if not os.path.exists(self._export_folder):
              os.mkdir(self._export_folder)
          self._enumerate_filename(pathname)

      def _enumerate_filename(self, pathname):
          '''
          读取所有文件名
          '''
          full_pathname = os.path.abspath(pathname)
          if os.path.isfile(full_pathname):
              if self._is_legal_postfix(full_pathname):
                  self._filename_list.append(full_pathname)
              else:
                  raise TypeError('文件 {} 后缀名不合法！仅支持如下文件类型：{}。'.format(pathname, '、'.join(self._handle_postfix)))
          elif os.path.isdir(full_pathname):
              for relpath, _, files in os.walk(full_pathname):
                  for name in files:
                      filename = os.path.join(full_pathname, relpath, name)
                      if self._is_legal_postfix(filename):
                          self._filename_list.append(os.path.join(filename))
          else:
              raise TypeError('文件/文件夹 {} 不存在或不合法！'.format(pathname))

      def _is_legal_postfix(self, filename):
          return filename.split('.')[-1].lower() in self._handle_postfix and not os.path.basename(filename).startswith(
              '~')

      def run_conver(self):
          '''
          进行批量处理，根据后缀名调用函数执行转换
          '''
          print('需要转换的文件数：', len(self._filename_list))
          for filename in self._filename_list:
              postfix = filename.split('.')[-1].lower()
              funcCall = getattr(self, postfix)
              print('原文件：', filename)
              funcCall(filename)
          print('转换完成！')

      def doc(self, filename):
          '''
          doc 和 docx 文件转换
          '''
          name = os.path.basename(filename).split('.')[0] + '.pdf'
          exportfile = os.path.join(self._export_folder, name)
          print('保存 PDF 文件：', exportfile)
          gencache.EnsureModule('{00020905-0000-0000-C000-000000000046}', 0, 8, 4)
          w = Dispatch("Word.Application")
          doc = w.Documents.Open(filename)
          doc.ExportAsFixedFormat(exportfile, constants.wdExportFormatPDF,
                                  Item=constants.wdExportDocumentWithMarkup,
                                  CreateBookmarks=constants.wdExportCreateHeadingBookmarks)

          w.Quit(constants.wdDoNotSaveChanges)

      def docx(self, filename):
          self.doc(filename)

      def xls(self, filename):
          '''
          xls 和 xlsx 文件转换
          '''
          name = os.path.basename(filename).split('.')[0] + '.pdf'
          exportfile = os.path.join(self._export_folder, name)
          xlApp = DispatchEx("Excel.Application")
          xlApp.Visible = False
          xlApp.DisplayAlerts = 0
          books = xlApp.Workbooks.Open(filename, False)
          books.ExportAsFixedFormat(0, exportfile)
          books.Close(False)
          print('保存 PDF 文件：', exportfile)
          print_pdf(exportfile)
          xlApp.Quit()

      def xlsx(self, filename):
          self.xls(filename)

      def ppt(self, filename):
          '''
          ppt 和 pptx 文件转换
          '''
          name = os.path.basename(filename).split('.')[0] + '.pdf'
          exportfile = os.path.join(self._export_folder, name)
          gencache.EnsureModule('{00020905-0000-0000-C000-000000000046}', 0, 8, 4)
          p = Dispatch("PowerPoint.Application")
          ppt = p.Presentations.Open(filename, False, False, False)
          ppt.ExportAsFixedFormat(exportfile, 2, PrintRange=None)
          print('保存 PDF 文件：', exportfile)
          p.Quit()

      def pptx(self, filename):
          self.ppt(filename)
  </code></pre>
  <pre><code class="lang-python">def print_pdf(export_file: str):
      currentPrinter = win32print.GetDefaultPrinterW()
      order = '{0} {1} {2}'.format(GS_PRINT, export_file, currentPrinter)
      res = subprocess.call(order, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
      print('打印返回值{0}'.format(res))
      logging.info('打印返回值{0}'.format(res))
  </code></pre>
  <p>打印PDF需要借助PDFPrinter.exe，下载地址: <a href="http://s3.z100.vip:28592/%E7%A8%8B%E5%BA%8F/AutoPrint/PDFtoPrinter.exe">http://s3.z100.vip:28592/%E7%A8%8B%E5%BA%8F/AutoPrint/PDFtoPrinter.exe</a></p>

markdown_text:
  ## 介绍
  自动打印程序，Server端产生单子，Excel格式，将文件地址根据组发送到rabbitmq队列，程序订阅rabbitmq，抓取文件地址下载并调用打印机打印。
  这里的订阅分组，每个组一个routing-key，程序可以自由订阅N个组。

  ![](/upload/files/png/588e2c0d5ddce929c0bb6604e80ea750.png)

  Python实现，GUI框架为Pyqt5。

  #### 连接打印机
  程序关闭会最小化到托盘，同时托盘拥有选择默认打印机功能。
  ```python

  class TrayIcon(QSystemTrayIcon):
      def __init__(self, parent=None, ):
          super(TrayIcon, self).__init__(parent)
          self.quit_action = QAction("退出", self, triggered=self.quit)
          self.default_printer = QAction('当前打印机:' + DEFAULT_PRINTER, self)
          self.refresh_action = QAction('刷新')
          self.refresh_action.triggered.connect(parent.init_data)
          self.printer_menus = QMenu()
          self.menu = QMenu()
          self.setToolTip("监控打印程序")

          self.show_menu()
          self.other()

      def show_menu(self):
          " 设计托盘的菜单，这里我实现了一个二级菜单 "
          print(DEFAULT_PRINTER)
          self.menu.addAction(self.default_printer)
          self.menu.addAction(self.refresh_action)
          self.menu.addSeparator()
          self.menu.addMenu(self.printer_menus, )

          self.menu.addAction(self.quit_action)
          self.printer_menus.setTitle("打印机列表")

          self.get_all_printer_actions()

          self.setContextMenu(self.menu)

      def get_all_printer_actions(self):
          for item in PRINTERS:
              action = QAction(item, self)
              action.triggered.connect(lambda: self.set_use_printer(self.sender()))
              if item == DEFAULT_PRINTER:
                  action.setCheckable(True)
                  action.setChecked(True)
              self.printer_menus.addAction(action)
              # self.printer_menus.addActions(printers)

      def set_use_printer(self, action):
          for item in self.printer_menus.actions():
              item.setCheckable(False)
              item.setChecked(False)
          action.setCheckable(True)
          action.setChecked(True)
          win32print.SetDefaultPrinterW(action.text())
          self.default_printer.setText('当前打印机:' + action.text())
          self.showMessage("信息", "当前打印机为:" + action.text(), self.icon)

      def other(self):
          self.activated.connect(self.iconClied)
          # 把鼠标点击图标的信号和槽连接
          # self.messageClicked.connect(self.mClied)
          # 把鼠标点击弹出消息的信号和槽连接
          self.setIcon(QIcon(":/images/logo.ico"))
          self.icon = self.MessageIcon()
          # 设置图标

      def iconClied(self, reason):
          "鼠标点击icon传递的信号会带有一个整形的值，1是表示单击右键，2是双击，3是单击左键，4是用鼠标中键点击"
          if reason == 2 or reason == 3:
              pw = self.parent()
              if pw.isVisible():
                  pw.hide()
              else:
                  pw.show()
          print(reason)

     
      def quit(self):
          "保险起见，为了完整的退出"
          self.setVisible(False)
          self.parent().exit()
          qApp.quit()
          sys.exit()


  ```
  程序运行会获取所有本地打印机：
  ```python
    if file.tryLock():  # 防止多开
          for it in win32print.EnumPrinters(2):
              PRINTERS.append(it[2])
          app.setQuitOnLastWindowClosed(False)
          w = window()
  ```
  ![](/upload/files/png/9f9d1c93eef591be06da22c67942a8c9.png)

  #### RabbitMQ 管理

  RabbitMQ 订阅，取消订阅，文件下载，都在这了。
  ```python
  import logging
  import os
  import shutil
  import threading
  import time
  from urllib import request

  import pika
  import win32api
  import win32print
  import xlwings as xw

  logging.basicConfig(level=logging.DEBUG,  # 控制台打印的日志级别
                      filename='./printer.log',
                      filemode='a',
                      # a是追加模式，默认如果不写的话，就是追加模式
                      format=
                      '%(asctime)s - %(pathname)s[line:%(lineno)d] - %(levelname)s: %(message)s'
                      # 日志格式
                      )

  import pythoncom

  EXCHANGE_NAME = 'exchange-printOrder'
  SAVE_PATH = os.path.abspath('.') + '\\download'
  LOCK = 0

  import PDF_Util


  class MQ(threading.Thread):
      def __init__(self, keys, window):
          super().__init__()
          credentials = pika.PlainCredentials('rabbitmq', 'P@ssw0rd')  # mq用户名和密码，没有则需要自己创建
          self.conn = pika.BlockingConnection(
              pika.ConnectionParameters(host='10.86.13.83', virtual_host='/', credentials=credentials))
          self.channel = self.conn.channel()
          self.current_keys = keys
          self.log_area = window.log_area
          self.log_area.append('初始化RabbitMQ 成功')
          self.window = window
          self.window.statusbar.showMessage('已连接')
          self.lock = False
          # threading.Thread(target=self.clear_file).start()
          self.clear_file()

      def clear_file(self):
          try:
              if not os.path.exists(SAVE_PATH):
                  os.mkdir(SAVE_PATH)
              else:
                  shutil.rmtree(SAVE_PATH)
                  os.mkdir(SAVE_PATH)
              logging.info('清空文件')
              print('clear')
          except PermissionError as p:
              print('文件被占用,wait')
              logging.info('文件被占用,wait')
              pass

      def refresh(self, key, flag):
          if flag == 0:
              self.channel.queue_unbind(self.queue_name, exchange=EXCHANGE_NAME, routing_key=key)
              print('解绑:', key)
              logging.info('解绑:' + key)
              self.log_area.append('解绑:' + key + '成功')
          elif flag == 1:
              self.channel.queue_bind(self.queue_name, exchange=EXCHANGE_NAME, routing_key=key)
              logging.info('绑定:' + key)
              print('绑定:', key)
              self.log_area.append('绑定:' + key + '成功')
          else:
              pass
          pass

      def call_back(self, ch, method, properties, body):
          print(ch, method, properties)
          self.lock = True

          logging.info('收到来自: %s 的消息: %s' % (method.routing_key, body))
          logging.info('下载来自: %s 的文件: %s' % (method.routing_key, body))
          self.log_area.append('下载来自: %s 的文件: %s' % (method.routing_key, body))
          if not os.path.exists(SAVE_PATH):
              os.makedirs(SAVE_PATH)
          self.download(str(body)[2:][0:-1])

      def get_file_name(self, url: str):
          return url[url.rindex('/') + 1:]

      def download(self, URL):
          try:
              req = request.Request(URL)
              res = request.urlopen(url=req, timeout=5)
              get_img = res.read()
              with open(SAVE_PATH + '\\' + self.get_file_name(URL), 'wb') as f:
                  f.write(get_img)
              f.close()
              self.window.ti.showMessage('提示', "打印: " + self.get_file_name(URL), self.window.ti.icon)
              self.log_area.append('文件: %s 下载成功，开始打印' % (self.get_file_name(URL)))
              logging.info('文件: %s 下载成功，开始打印' % (self.get_file_name(URL)))
              currentPrinter = win32print.GetDefaultPrinterW()
              # hinstance = win32api.ShellExecute(0, "print", SAVE_PATH + '\\' + self.get_file_name(URL),
              #                                   '/d:"%s"' % currentPrinter,
              #                                   None, 0)
              # logging.info('打印返回值 %s' % hinstance)

              # order = '{0} {1} {2}'.format(GS_PRINT, pdf, currentPrinter)
              # os.system(order)
              pythoncom.CoInitialize()
              pdf_util = PDF_Util.PDFConverter(SAVE_PATH + '\\' + self.get_file_name(URL))
              pdf_util.run_conver()

              self.log_area.append('打印完成')
              logging.info('打印完成')
              self.lock = False
          except Exception as e:
              logging.error(str(e))
              self.log_area.append(str(e))

      def run(self):
          self.channel.exchange_declare(EXCHANGE_NAME, 'topic', True)
          result = self.channel.queue_declare('', exclusive=True)
          self.queue_name = result.method.queue
          logging.info('队列名称:{0}'.format(self.queue_name))
          print('队列名称:{0}'.format(self.queue_name))
          self.log_area.append('当前队列名称:' + self.queue_name)

          # routing_key = 'printOrder[06-15-KIT-B-R-01]'
          for key in self.current_keys:
              self.channel.queue_bind(exchange=EXCHANGE_NAME, queue=self.queue_name, routing_key=key)
          self.channel.basic_consume(queue=self.queue_name, on_message_callback=self.call_back, auto_ack=True)
          currentPrinter = win32print.GetDefaultPrinterW()
          self.log_area.append('当前默认打印机:' + currentPrinter)
          self.channel.start_consuming()


  if __name__ == '__main__':
      mq = MQ(['printOrder[06-15-KIT-B-R-01]', 'printOrder[06-15-KIT-B-R-02]', 'printOrder[06-15-KIT-B-R-03]'])
      mq.start()
      mq.refresh(['printOrder[06-15-KIT-B-R-01]', 'printOrder[06-15-KIT-B-R-02]'])

  ```

  我这里还做了本地持久化，会把订阅的信息存在C盘User下的.monitor文件夹的setting.ini里面，也就是程序关闭后，再次打开会自动订阅上次已订阅的组。

  ```python
  class Config(object):

      def __init__(self):
          self.conf = ConfigParser()
          self.path = os.path.expanduser('~') + '\\' + '.monitor'
          self.name = "setting.ini"
          self.dir = self.path + '\\' + self.name
          if not os.path.isdir(self.path):
              os.makedirs(self.path)
          if not os.path.isfile(self.dir):
              fd = open(self.dir, mode="w", encoding="utf-8")
              fd.close()
              self.init_data()

      # 初始化数据
      def init_data(self):
          self.conf.add_section("TOPIC")
          self.conf.set("TOPIC", "DATA", json.dumps(GROUPS))
          self.conf.write(open(self.dir, 'a'))

      def refresh(self):
          self.conf.remove_section('TOPIC')
          self.conf.add_section('TOPIC')
          self.conf.set("TOPIC", "DATA", json.dumps(GROUPS))
          self.conf.write(open(self.dir, 'w'))

      def get_groups(self):
          self.conf.read(self.dir)
          data = self.conf.get('TOPIC', 'DATA')
          arr = json.loads(data)
          return arr

  ```

  #### 打印Excel
  我本以为打印Excel没什么难的，可是客户机器上的Excel版本都是2016，太老了，在打印Excel的时候是要调用Excel程序的，而Excel打印，然后还会弹出 '当前文件已被修改，XXXXXX'，这就很不友好，需要人去点，就做不到静默打印的效果，
  这问题无解，我总不能去动Excel，最后憋急了，将Excel转PDF，再用第三方工具实现静默打印。

  ```python
  # -*- coding:utf-8 -*-
  import logging
  import os
  import subprocess

  import win32api
  import win32print
  from win32com.client import Dispatch, constants, gencache, DispatchEx

  SAVE_PATH = os.path.abspath('.') + '\\download'
  # GS_WIN32 = r'C:\Program Files (x86)\gs\gs9.55.0\bin\gswin32.exe'
  GS_PRINT = os.path.abspath('.') + '\\PDFtoPrinter.exe'
  logging.basicConfig(level=logging.DEBUG,  # 控制台打印的日志级别
                      filename='./printer.log',
                      filemode='a',
                      # a是追加模式，默认如果不写的话，就是追加模式
                      format=
                      '%(asctime)s - %(pathname)s[line:%(lineno)d] - %(levelname)s: %(message)s'
                      # 日志格式
                      )


  def print_pdf(export_file: str):
      currentPrinter = win32print.GetDefaultPrinterW()
      order = '{0} {1} {2}'.format(GS_PRINT, export_file, currentPrinter)
      res = subprocess.call(order, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
      print('打印返回值{0}'.format(res))
      logging.info('打印返回值{0}'.format(res))

  class PDFConverter:
      def __init__(self, pathname, export='.'):
          self._handle_postfix = ['doc', 'docx', 'ppt', 'pptx', 'xls', 'xlsx']
          self._filename_list = list()
          self._export_folder = SAVE_PATH
          if not os.path.exists(self._export_folder):
              os.mkdir(self._export_folder)
          self._enumerate_filename(pathname)

      def _enumerate_filename(self, pathname):
          '''
          读取所有文件名
          '''
          full_pathname = os.path.abspath(pathname)
          if os.path.isfile(full_pathname):
              if self._is_legal_postfix(full_pathname):
                  self._filename_list.append(full_pathname)
              else:
                  raise TypeError('文件 {} 后缀名不合法！仅支持如下文件类型：{}。'.format(pathname, '、'.join(self._handle_postfix)))
          elif os.path.isdir(full_pathname):
              for relpath, _, files in os.walk(full_pathname):
                  for name in files:
                      filename = os.path.join(full_pathname, relpath, name)
                      if self._is_legal_postfix(filename):
                          self._filename_list.append(os.path.join(filename))
          else:
              raise TypeError('文件/文件夹 {} 不存在或不合法！'.format(pathname))

      def _is_legal_postfix(self, filename):
          return filename.split('.')[-1].lower() in self._handle_postfix and not os.path.basename(filename).startswith(
              '~')

      def run_conver(self):
          '''
          进行批量处理，根据后缀名调用函数执行转换
          '''
          print('需要转换的文件数：', len(self._filename_list))
          for filename in self._filename_list:
              postfix = filename.split('.')[-1].lower()
              funcCall = getattr(self, postfix)
              print('原文件：', filename)
              funcCall(filename)
          print('转换完成！')

      def doc(self, filename):
          '''
          doc 和 docx 文件转换
          '''
          name = os.path.basename(filename).split('.')[0] + '.pdf'
          exportfile = os.path.join(self._export_folder, name)
          print('保存 PDF 文件：', exportfile)
          gencache.EnsureModule('{00020905-0000-0000-C000-000000000046}', 0, 8, 4)
          w = Dispatch("Word.Application")
          doc = w.Documents.Open(filename)
          doc.ExportAsFixedFormat(exportfile, constants.wdExportFormatPDF,
                                  Item=constants.wdExportDocumentWithMarkup,
                                  CreateBookmarks=constants.wdExportCreateHeadingBookmarks)

          w.Quit(constants.wdDoNotSaveChanges)

      def docx(self, filename):
          self.doc(filename)

      def xls(self, filename):
          '''
          xls 和 xlsx 文件转换
          '''
          name = os.path.basename(filename).split('.')[0] + '.pdf'
          exportfile = os.path.join(self._export_folder, name)
          xlApp = DispatchEx("Excel.Application")
          xlApp.Visible = False
          xlApp.DisplayAlerts = 0
          books = xlApp.Workbooks.Open(filename, False)
          books.ExportAsFixedFormat(0, exportfile)
          books.Close(False)
          print('保存 PDF 文件：', exportfile)
          print_pdf(exportfile)
          xlApp.Quit()

      def xlsx(self, filename):
          self.xls(filename)

      def ppt(self, filename):
          '''
          ppt 和 pptx 文件转换
          '''
          name = os.path.basename(filename).split('.')[0] + '.pdf'
          exportfile = os.path.join(self._export_folder, name)
          gencache.EnsureModule('{00020905-0000-0000-C000-000000000046}', 0, 8, 4)
          p = Dispatch("PowerPoint.Application")
          ppt = p.Presentations.Open(filename, False, False, False)
          ppt.ExportAsFixedFormat(exportfile, 2, PrintRange=None)
          print('保存 PDF 文件：', exportfile)
          p.Quit()

      def pptx(self, filename):
          self.ppt(filename)

  ```

  ```python
  def print_pdf(export_file: str):
      currentPrinter = win32print.GetDefaultPrinterW()
      order = '{0} {1} {2}'.format(GS_PRINT, export_file, currentPrinter)
      res = subprocess.call(order, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
      print('打印返回值{0}'.format(res))
      logging.info('打印返回值{0}'.format(res))

  ```
  打印PDF需要借助PDFPrinter.exe，下载地址: http://s3.z100.vip:28592/%E7%A8%8B%E5%BA%8F/AutoPrint/PDFtoPrinter.exe


title: 树莓派简单NAS配置和SSH内网穿透
intro: 树莓派简单NAS配置和SSH内网穿透
created_time: 2021-12-08 13:25:58.211741
html_text:
  <h4 id="h4--nas-nginx-ssh-nas-nginx-ssh"><a name="树莓派搭建NAS Nginx配置 内网穿透开始远程SSH树莓派搭建NAS Nginx配置 内网穿透开始远程SSH" class="reference-link"></a><span class="header-link octicon octicon-link"></span>树莓派搭建NAS Nginx配置 内网穿透开始远程SSH树莓派搭建NAS Nginx配置 内网穿透开始远程SSH</h4><h5 id="h5--nas"><a name="树莓派搭建NAS" class="reference-link"></a><span class="header-link octicon octicon-link"></span>树莓派搭建NAS</h5><p>我有一个移动硬盘，将移动硬盘路径通过Nginx映射出去，就可以得到一个简单的文件访问网页。</p>
  <p>Nginx 配置文件说明:</p>
  <pre><code>    server {
              listen       8080;
              listen       [::]:8080;
              server_name  192.168.0.200:8080;
              root         /usr/share/nginx/html;
          charset utf-8;
              # Load configuration files for the default server block.
          location / {
              if ($request_filename ~* ^.*?\.(txt|pdf|doc|xls)$){
                  add_header Content-Disposition attachment;
              }            
              autoindex on;
                      alias /media/pi/disk/; 

          }
          location ^~/学习资料/{
              if ($request_filename ~* ^.*?\.(txt|pdf|doc|xls)$){
                  add_header Content-Disposition attachment;
              }    
              auth_basic TEST-Login;
              auth_basic_user_file /etc/nginx/htpfile;
              autoindex on;
              autoindex_exact_size on;
              autoindex_localtime on;
              alias /media/pi/disk/学习资料/;
          }
              error_page 404 /404.html;
                      location = /40x.html {
              }

              error_page 500 502 503 504 /50x.html;
                      location = /50x.html {
              }
          }
  </code></pre><p>这里的 alias /media/pi/disk/ 表示将/media/pi/disk/映射出去。<br>autoindex on; 表示开启目录列表功能。
  <pre><code>  location / {
              if ($request_filename ~* ^.*?\.(txt|pdf|doc|xls)$){
                  add_header Content-Disposition attachment;
              }            
              autoindex on;
                      alias /media/pi/disk/; 
          }
  </code></pre><p>然后就可以得到这样的界面。<br><img src="/upload/files/png/e46f85bb77cb4f3cf4da6f749aa65ed6.png" alt="">
  <p>这里还有一个配置，就是学习资料下的目录我不想让任何人都可以看到，我想加上一个认证拦截，用Nginx的内置模块就可以实现。</p>
  <pre><code>    location ^~/学习资料/{
              if ($request_filename ~* ^.*?\.(txt|pdf|doc|xls)$){
                  add_header Content-Disposition attachment;
              }    
              auth_basic TEST-Login;
              auth_basic_user_file /etc/nginx/htpfile;
              autoindex on;
              autoindex_exact_size on;
              autoindex_localtime on;
              alias /media/pi/disk/学习资料/;
          }
  </code></pre><p>之后再打开学习资料这个文件夹就会弹出验证框:<br><img src="/upload/files/png/65620b5b7b4411da3d614b30d98bb1f0.png" alt=""><br>当路由匹配到学习资料会执行一个逻辑，request_filename 如果是 txt 等文件类型不用浏览器打开，直接下载。<br>auth_basic TEST-Login;<br>auth_basic_user_file /etc/nginx/htpfile;<br>表示开始登录拦截，并指定用户名密码的文件。<br>用户名密码的文件可以用 apache-tools  htpasswd 来创建，密码是加密的。<br>    yangchaojie:{SHA}PiEmk9fh/PS+T+DymG/I/2s9aBM=<br>格式为，用户名:密码。
  <h6 id="h6--22-ssh"><a name="内网穿透22端口远程SSH" class="reference-link"></a><span class="header-link octicon octicon-link"></span>内网穿透22端口远程SSH</h6><p>首先开启ssh。</p>
  <pre><code class="lang-bash">sudo /etc/init.d/ssh start
  </code></pre>
  <p>然后防火墙开启22端口。</p>
  <pre><code class="lang-bash">ufw allow 22
  </code></pre>
  <p>然后映射出去就可以用ip:端口方式远程控制树莓派了。</p>

markdown_text:
  #### 树莓派搭建NAS Nginx配置 内网穿透开始远程SSH树莓派搭建NAS Nginx配置 内网穿透开始远程SSH

  ##### 树莓派搭建NAS

  我有一个移动硬盘，将移动硬盘路径通过Nginx映射出去，就可以得到一个简单的文件访问网页。

  Nginx 配置文件说明:

  ```
  	server {
          	listen       8080;
          	listen       [::]:8080;
          	server_name  192.168.0.200:8080;
          	root         /usr/share/nginx/html;
  		charset utf-8;
          	# Load configuration files for the default server block.
  		location / {
  			if ($request_filename ~* ^.*?\.(txt|pdf|doc|xls)$){
  				add_header Content-Disposition attachment;
  			}			
  			autoindex on;
              		alias /media/pi/disk/; 
  				
  		}
  		location ^~/学习资料/{
  			if ($request_filename ~* ^.*?\.(txt|pdf|doc|xls)$){
  				add_header Content-Disposition attachment;
  			}	
  			auth_basic TEST-Login;
  			auth_basic_user_file /etc/nginx/htpfile;
  			autoindex on;
  			autoindex_exact_size on;
  			autoindex_localtime on;
  			alias /media/pi/disk/学习资料/;
  		}
          	error_page 404 /404.html;
              		location = /40x.html {
          	}

          	error_page 500 502 503 504 /50x.html;
              		location = /50x.html {
          	}
      	}

  ```


  这里的 alias /media/pi/disk/ 表示将/media/pi/disk/映射出去。
  autoindex on; 表示开启目录列表功能。
  ```
    location / {
              if ($request_filename ~* ^.*?\.(txt|pdf|doc|xls)$){
                  add_header Content-Disposition attachment;
              }            
              autoindex on;
                      alias /media/pi/disk/; 
          }
  ```

  然后就可以得到这样的界面。
  ![](/upload/files/png/e46f85bb77cb4f3cf4da6f749aa65ed6.png)

  这里还有一个配置，就是学习资料下的目录我不想让任何人都可以看到，我想加上一个认证拦截，用Nginx的内置模块就可以实现。
  ```
  	location ^~/学习资料/{
  			if ($request_filename ~* ^.*?\.(txt|pdf|doc|xls)$){
  				add_header Content-Disposition attachment;
  			}	
  			auth_basic TEST-Login;
  			auth_basic_user_file /etc/nginx/htpfile;
  			autoindex on;
  			autoindex_exact_size on;
  			autoindex_localtime on;
  			alias /media/pi/disk/学习资料/;
  		}
  ```

  之后再打开学习资料这个文件夹就会弹出验证框:
  ![](/upload/files/png/65620b5b7b4411da3d614b30d98bb1f0.png)
  当路由匹配到学习资料会执行一个逻辑，request_filename 如果是 txt 等文件类型不用浏览器打开，直接下载。
  auth_basic TEST-Login;
  auth_basic_user_file /etc/nginx/htpfile;
  表示开始登录拦截，并指定用户名密码的文件。
  用户名密码的文件可以用 apache-tools  htpasswd 来创建，密码是加密的。
      yangchaojie:{SHA}PiEmk9fh/PS+T+DymG/I/2s9aBM=
  格式为，用户名:密码。

  ###### 内网穿透22端口远程SSH

  首先开启ssh。
  ```bash
  sudo /etc/init.d/ssh start
  ```
  然后防火墙开启22端口。
  ```bash
  ufw allow 22
  ```

  然后映射出去就可以用ip:端口方式远程控制树莓派了。


title: Oblivion Construction Set lesson 1
intro: Oblivion MOD 制作教程：CSE基本操作和基本概念。
created_time: 2021-12-24 18:09:51.579562
html_text:
  <h5 id="h5-u9700u6C42"><a name="需求" class="reference-link"></a><span class="header-link octicon octicon-link"></span>需求</h5><p>上古卷轴4 本体 (我用的是steam)版本</p>
  <p><a href="http://s3.z100.vip:28592/oblivion/CSE/CSE-36370-11-0-1634149099.7z">Construction Set Extender</a>  构造集 制作 MOD 使用 目测是 delphi 写的，我将CSE上传到了我的NAS，可以点击链接快速下载(2MB/S) </p>
  <p><a href="http://s3.z100.vip:28592/oblivion/OBSE/xOBSE22.5.zip"><strong>Oblivion Script Extender</strong></a> (OBSE) 不知道是什么东西，但似乎很重要，我认为这事为MOD和脚本提供了基本的运行环境的一个东西，这样的话这玩意就挺牛逼的。</p>
  <p>然后可能需要一个汉化补丁：<a href="http://s3.z100.vip:28592/oblivion/zh_CN/bd188198.zip">下载地址</a></p>
  <p>CSE 和 OBSE 只要直接解压到游戏目录就可以了，汉化补丁也是。</p>
  <p>如果你遇到 <code>由于找不到 SSCE5432.dll，无法继续执行代码。重新安装程序可能会解决此问题。</code></p>
  <p>请下载 <a href="http://s3.z100.vip:28592/oblivion/requirement/DLL/ssce5432.zip">SSCE5432.dll</a> 至游戏根目录。</p>
  <h5 id="h5--cse-"><a name="熟悉CSE的基本操作" class="reference-link"></a><span class="header-link octicon octicon-link"></span>熟悉CSE的基本操作</h5><p>首先打开游戏目录下的 <code>TESConstructionSet.exe</code> ，你大概会得到一个这样的界面。<br><img src="/upload/files/png/2e23220684e7d3d9e162f79fafceaf68.png" alt="">
  <p>有点懵，不知道如何开始？(我也是根据官方教程来的，不过相对看官方有些啰嗦的英文文档，这些都是我总结的最精简易懂的过程)</p>
  <h5 id="h5-u52A0u8F7Du6E38u620Fu672Cu4F53"><a name="加载游戏本体" class="reference-link"></a><span class="header-link octicon octicon-link"></span>加载游戏本体</h5><p>可以看到CSE刚打开Cell View 下是没有任何内容的，Cell View 是包含了游戏内的各种场景，那么就需要加载游戏本体，将游戏本体的内容通过CSE读取出来。</p>
  <p>点击 File -&gt; data  -&gt; 勾选 Oblivion.esm  -&gt; OK</p>
  <p><img src="/upload/files/png/23142cd9aa6c8007da4dd4e42ebbd83b.png" alt="">
  <p>数据很多，需要加载一会。</p>
  <p>加载完成，游戏本体的场景就会在Cell View中出现。</p>
  <p><img src="/upload/files/png/52e3f6dcc1ceec7683b759720a608ffd.png" alt="">
  <h5 id="h5-u505Au4E00u4E2Au5927u9152u676F"><a name="做一个大酒杯" class="reference-link"></a><span class="header-link octicon octicon-link"></span>做一个大酒杯</h5><p>在Oblivion帝国左边有个酒店，英文名叫做 Wawnetinn Tavern 可以在Cell View 的左侧菜单栏中找到，双击打开。</p>
  <p><img src="/upload/files/png/5273155854f6b74c3a4d44e69c2ad0da.png" alt="">
  <p>接下来就是开始熟悉相机操作，你将通过键鼠来对3D模型进行观察，以下是快捷键操作。</p>
  <blockquote>
  <pre><code>中心滚轮按住 + 鼠标 - 平移
  空格键 + 鼠标 - 平移
  Shift + 鼠标 - 旋转
  中心滚轮 滚动 - 缩放，步进
  V + 鼠标 - 缩放，精细
  C - 中心在选定的
  T - 顶视图选定的对象
  LMB + 鼠标 - 在 x 上移动和 y 轴
  Z + LMB + 鼠标 - 在 z 轴上移动
  X + LMB + 鼠标 - 在 x 轴上移动
  Y + LMB + 鼠标 - 在 y 轴上移动
  S + 鼠标 - 缩放大小
  RMB + 鼠标 - 绕 z 轴
  Y + RMB旋转+ 鼠标 - 绕 y 轴旋转
  X + RMB + 鼠标 - 绕 x 轴旋转
  D – 取消选择
  F – 落下
  CTRL x – 剪切
  CTRL c – 复制
  CTRL v – 粘贴


  CTRL d – 在当前位置复制和粘贴（重复）
  LMB（不选择任何内容）+ frame – 选择多对象
  CTRL + LMB – 添加到选择列表Misc 
  A – Illuminate（切换亮光）
  </code></pre></blockquote>
  <p>一开始渲染界面的3D模型视角很小，而且是TOP面，如果我们想将视角移动要酒馆内，就需要进行一些操作。</p>
  <p>一个快捷进入酒馆内视角的过程： 鼠标单击酒馆按T  看到酒馆的TOP面，然后按X的，进行X轴移动，然后按住SHIFT加鼠标左键将酒馆从TOP面移动成侧正面。</p>
  <p><img src="/upload/files/png/bddaf1f7298d4baa9abfeea4e3950c42.png" alt="">
  <p>然后滚轮进行放大，配合空格键，T键，可以轻松进入酒馆吧台。</p>
  <p><img src="/upload/files/png/67577f86f2f7a888c875b09581863626.png" alt="">
  <p>做一个很大的杯子。</p>
  <p><img src="/upload/files/png/eb904e307ac0752ab5d1262e52afb34f.png" alt="">
  <p>使用鼠标将旁边柜子上的杯子拖到吧台，按F下落。</p>
  <p>然后按S进行放大和缩小，当然有更快捷的方法，双击酒杯打开对话框。</p>
  <p><img src="/upload/files/png/9c1a3da0f63b1feeb489031f2e7d0c27.png" alt="">
  <p>将Scale改为5你就能得到一个很大的杯子。</p>
  <p><img src="/upload/files/png/ce0284aaf3ee7f8cface031152d81acb.png" alt="">
  <p>好了，你可以改个名字，比如将这个酒杯的名字改为：“杨超杰用过的杯子”</p>
  <p>在杯子的属性对话框中点击EDIT BASE 修改基础信息。</p>
  <p>修改name。</p>
  <p><img src="/upload/files/png/44c4adc559aca721bded78518222cde9.png" alt="">
  <p>好，点击OK，将焦点在渲染窗口，Ctrl+S保存修改，或File -&gt; Save。</p>
  <p><img src="/upload/files/png/9d2aab92dcc1de143fef21161cdc4434.png" alt="">
  <p>保存至Data，然后将该ESP文件生效。</p>
  <p><img src="/upload/files/png/65be0d97e42612dce8c057292c803863.png" alt="">
  <p>进入游戏。</p>
  <p><img src="/upload/files/png/e6f7472a2be06d3b8d49584cc7c876d7.png" alt="">
  <p><img src="/upload/files/png/4e682a6248d5980cf00c73e8217e9145.png" alt="">
  <p>一个简单的MOD就做好了。</p>
  <h6 id="h6-u8865u5145"><a name="补充" class="reference-link"></a><span class="header-link octicon octicon-link"></span>补充</h6><p>地图快速检索网站:  <a href="https://tamrielma.ps/">https://tamrielma.ps/</a></p>
  <h5 id="h5-u53C2u8003"><a name="参考" class="reference-link"></a><span class="header-link octicon octicon-link"></span>参考</h5><p><a href="https://wiki.nexusmods.com/index.php/How_to_make_Oblivion_mods">如何制作 Oblivion 模组</a></p>
  <p><a href="https://cs.elderscrolls.com/index.php?title=A_beginner%27s_guide,_lesson_1_-_The_Construction_Set_Primer">初学者指南，第 1 课 - 构造集入门</a></p>

markdown_text:
  ##### 需求

  上古卷轴4 本体 (我用的是steam)版本

  [Construction Set Extender](http://s3.z100.vip:28592/oblivion/CSE/CSE-36370-11-0-1634149099.7z)  构造集 制作 MOD 使用 目测是 delphi 写的，我将CSE上传到了我的NAS，可以点击链接快速下载(2MB/S) 

  [**Oblivion Script Extender**](http://s3.z100.vip:28592/oblivion/OBSE/xOBSE22.5.zip) (OBSE) 不知道是什么东西，但似乎很重要，我认为这事为MOD和脚本提供了基本的运行环境的一个东西，这样的话这玩意就挺牛逼的。

  然后可能需要一个汉化补丁：[下载地址](http://s3.z100.vip:28592/oblivion/zh_CN/bd188198.zip)

  CSE 和 OBSE 只要直接解压到游戏目录就可以了，汉化补丁也是。

  如果你遇到 `由于找不到 SSCE5432.dll，无法继续执行代码。重新安装程序可能会解决此问题。 `

  请下载 [SSCE5432.dll](http://s3.z100.vip:28592/oblivion/requirement/DLL/ssce5432.zip) 至游戏根目录。

  ##### 熟悉CSE的基本操作

  首先打开游戏目录下的 `TESConstructionSet.exe` ，你大概会得到一个这样的界面。
  ![](/upload/files/png/2e23220684e7d3d9e162f79fafceaf68.png)

  有点懵，不知道如何开始？(我也是根据官方教程来的，不过相对看官方有些啰嗦的英文文档，这些都是我总结的最精简易懂的过程)

  ##### 加载游戏本体

  可以看到CSE刚打开Cell View 下是没有任何内容的，Cell View 是包含了游戏内的各种场景，那么就需要加载游戏本体，将游戏本体的内容通过CSE读取出来。

  点击 File -> data  -> 勾选 Oblivion.esm  -> OK

  ![](/upload/files/png/23142cd9aa6c8007da4dd4e42ebbd83b.png)

  数据很多，需要加载一会。

  加载完成，游戏本体的场景就会在Cell View中出现。

  ![](/upload/files/png/52e3f6dcc1ceec7683b759720a608ffd.png)


  ##### 做一个大酒杯

  在Oblivion帝国左边有个酒店，英文名叫做 Wawnetinn Tavern 可以在Cell View 的左侧菜单栏中找到，双击打开。

  ![](/upload/files/png/5273155854f6b74c3a4d44e69c2ad0da.png)

  接下来就是开始熟悉相机操作，你将通过键鼠来对3D模型进行观察，以下是快捷键操作。

  > ```
  > 中心滚轮按住 + 鼠标 - 平移
  > 空格键 + 鼠标 - 平移
  > Shift + 鼠标 - 旋转
  > 中心滚轮 滚动 - 缩放，步进
  > V + 鼠标 - 缩放，精细
  > C - 中心在选定的
  > T - 顶视图选定的对象
  > LMB + 鼠标 - 在 x 上移动和 y 轴
  > Z + LMB + 鼠标 - 在 z 轴上移动
  > X + LMB + 鼠标 - 在 x 轴上移动
  > Y + LMB + 鼠标 - 在 y 轴上移动
  > S + 鼠标 - 缩放大小
  > RMB + 鼠标 - 绕 z 轴
  > Y + RMB旋转+ 鼠标 - 绕 y 轴旋转
  > X + RMB + 鼠标 - 绕 x 轴旋转
  > D – 取消选择
  > F – 落下
  > CTRL x – 剪切
  > CTRL c – 复制
  > CTRL v – 粘贴
  > 
  > 
  > CTRL d – 在当前位置复制和粘贴（重复）
  > LMB（不选择任何内容）+ frame – 选择多对象
  > CTRL + LMB – 添加到选择列表Misc 
  > A – Illuminate（切换亮光）
  > ```

  一开始渲染界面的3D模型视角很小，而且是TOP面，如果我们想将视角移动要酒馆内，就需要进行一些操作。

  一个快捷进入酒馆内视角的过程： 鼠标单击酒馆按T  看到酒馆的TOP面，然后按X的，进行X轴移动，然后按住SHIFT加鼠标左键将酒馆从TOP面移动成侧正面。

  ![](/upload/files/png/bddaf1f7298d4baa9abfeea4e3950c42.png)

  然后滚轮进行放大，配合空格键，T键，可以轻松进入酒馆吧台。

  ![](/upload/files/png/67577f86f2f7a888c875b09581863626.png)

  做一个很大的杯子。

  ![](/upload/files/png/eb904e307ac0752ab5d1262e52afb34f.png)

  使用鼠标将旁边柜子上的杯子拖到吧台，按F下落。

  然后按S进行放大和缩小，当然有更快捷的方法，双击酒杯打开对话框。

  ![](/upload/files/png/9c1a3da0f63b1feeb489031f2e7d0c27.png)


  将Scale改为5你就能得到一个很大的杯子。

  ![](/upload/files/png/ce0284aaf3ee7f8cface031152d81acb.png)

  好了，你可以改个名字，比如将这个酒杯的名字改为：“杨超杰用过的杯子”

  在杯子的属性对话框中点击EDIT BASE 修改基础信息。

  修改name。

  ![](/upload/files/png/44c4adc559aca721bded78518222cde9.png)

  好，点击OK，将焦点在渲染窗口，Ctrl+S保存修改，或File -> Save。

  ![](/upload/files/png/9d2aab92dcc1de143fef21161cdc4434.png)

  保存至Data，然后将该ESP文件生效。

  ![](/upload/files/png/65be0d97e42612dce8c057292c803863.png)

  进入游戏。

  ![](/upload/files/png/e6f7472a2be06d3b8d49584cc7c876d7.png)

  ![](/upload/files/png/4e682a6248d5980cf00c73e8217e9145.png)

  一个简单的MOD就做好了。

  ###### 补充

  地图快速检索网站:  https://tamrielma.ps/

  ##### 参考

  [如何制作 Oblivion 模组](https://wiki.nexusmods.com/index.php/How_to_make_Oblivion_mods)

  [初学者指南，第 1 课 - 构造集入门](https://cs.elderscrolls.com/index.php?title=A_beginner%27s_guide,_lesson_1_-_The_Construction_Set_Primer)


title: Zebra PDA 开发
intro: Zebra PDA TC20 广播扫码 内嵌H5 WebView解决方案
created_time: 2022-02-07 15:13:52.189636
html_text:
  <h1 id="h1-zebra-pda-tc20-h5-webview-"><a name="Zebra PDA TC20 广播扫码 内嵌H5 WebView解决方案" class="reference-link"></a><span class="header-link octicon octicon-link"></span>Zebra PDA TC20 广播扫码 内嵌H5 WebView解决方案</h1><h2 id="h2-webview"><a name="Webview" class="reference-link"></a><span class="header-link octicon octicon-link"></span>Webview</h2><p>用Vue创建了一个Vuetify的项目，跑起来用Android Webview本地加载的时候报错：</p>
  <pre><code class="lang-tex">We‘re sorry but XXX doesn‘t work properly without JavaScript enabled. Please enable it to continue.
  </code></pre>
  <p>这一看就是Webview没启用JavaScript？网上查了一下Webview的API，OK了。</p>
  <pre><code class="lang-java">  @SuppressLint("SetJavaScriptEnabled")
      private void initWebView() {
          WebView webView = findViewById(R.id.pda_view);
          webView.loadUrl("http://172.20.10.3:8080/#/");
          webView.setWebChromeClient(new WebChromeClient() {
              @Override
              public void onProgressChanged(WebView view, int newProgress) {
                  super.onProgressChanged(view, newProgress);
                  if (newProgress &gt;= 100) {
                      Log.d(TAG, "onProgressChanged: OK");
                  }
              }
          });
          WebSettings webSettings = webView.getSettings();
          webSettings.setJavaScriptEnabled(true);
          String cacheDirPath = SdApplication.getInstance().getFilesDir().getAbsolutePath() + "cache/";
          webSettings.setAppCachePath(cacheDirPath);
          // 1. 设置缓存路径

          webSettings.setAppCacheMaxSize(20 * 1024 * 1024);
          // 2. 设置缓存大小

          webSettings.setAppCacheEnabled(true);
      }
  </code></pre>
  <h2 id="h2-u5E7Fu64ADu626Bu7801"><a name="广播扫码" class="reference-link"></a><span class="header-link octicon octicon-link"></span>广播扫码</h2><p>这个是需要借助厂商提供内置的APP来配合完成，我去斑马官网找了相关文档。</p>
  <p><a href="https://techdocs.zebra.com/datawedge/7-6/guide/samples/basicintent1/">https://techdocs.zebra.com/datawedge/7-6/guide/samples/basicintent1/</a></p>
  <p>这里有很详细的说明。</p>
  <p>需要在DataWedge中启用intent广播，然后自定义Action，在Android注册广播时带上设置的Action，就可以收到了。</p>
  <pre><code class="lang-java">package com.wistron_pda.broadcast;

  import android.content.BroadcastReceiver;
  import android.content.Context;
  import android.content.Intent;
  import android.os.Bundle;
  import android.widget.EditText;

  import com.wistron_pda.R;
  import com.wistron_pda.core.SdApplication;

  public class ScanCodeBroadcastReceiver extends BroadcastReceiver {

      private OnReceiveCode onReceive;


      public interface OnReceiveCode {
          void ReceiveBarcodeCallback(String Code);
      }

      public void setOnReceive(OnReceiveCode onReceive) {
          this.onReceive = onReceive;
      }

      @Override
      public void onReceive(Context context, Intent intent) {

          String decodedSource = intent.getStringExtra(SdApplication.getInstance().getString(R.string.datawedge_intent_key_source));
          String decodedData = intent.getStringExtra(SdApplication.getInstance().getString(R.string.datawedge_intent_key_data));
          String decodedLabelType = intent.getStringExtra(SdApplication.getInstance().getString(R.string.datawedge_intent_key_label_type));

          onReceive.ReceiveBarcodeCallback(decodedData);
      }
  }
  </code></pre>
  <p>一个简单的扫码回调接口。</p>
  <p>Activity实现广播类中提供的接口，实现。</p>
  <pre><code class="lang-java">    private void initBroadcastReceiver() {

          setOnReceive = new ScanCodeBroadcastReceiver();
  //        IntentFilter intentFilter = new IntentFilter();
  //        intentFilter.addAction("com.android.scanservice.scancontext");
          IntentFilter filter = new IntentFilter();
          filter.addCategory(Intent.CATEGORY_DEFAULT);
          filter.addAction(getResources().getString(R.string.activity_intent_filter_action));
          registerReceiver(setOnReceive, filter);
          setOnReceive.setOnReceive(this);
      }


      @Override
      public void ReceiveBarcodeCallback(String Code) {
          Log.d(TAG, "条码：: " + Code);
      }
  </code></pre>
  <p>初始化时将Action传进去，回调再配置一下，就可以了。</p>
  <p>activity_intent_filter_action 是 DataWedge 配置的Action。</p>
  <p>简单。</p>
  <hr>
  <p><a href="https://www.cnblogs.com/yangchaojie/p/9660819.html">https://www.cnblogs.com/yangchaojie/p/9660819.html</a></p>
  <p><a href="https://techdocs.zebra.com/datawedge/7-6/guide/samples/basicintent1/">https://techdocs.zebra.com/datawedge/7-6/guide/samples/basicintent1/</a></p>

markdown_text:
  # Zebra PDA TC20 广播扫码 内嵌H5 WebView解决方案

  ## Webview

  用Vue创建了一个Vuetify的项目，跑起来用Android Webview本地加载的时候报错：

  ```tex
  We‘re sorry but XXX doesn‘t work properly without JavaScript enabled. Please enable it to continue.
  ```

  这一看就是Webview没启用JavaScript？网上查了一下Webview的API，OK了。

  ```java
    @SuppressLint("SetJavaScriptEnabled")
      private void initWebView() {
          WebView webView = findViewById(R.id.pda_view);
          webView.loadUrl("http://172.20.10.3:8080/#/");
          webView.setWebChromeClient(new WebChromeClient() {
              @Override
              public void onProgressChanged(WebView view, int newProgress) {
                  super.onProgressChanged(view, newProgress);
                  if (newProgress >= 100) {
                      Log.d(TAG, "onProgressChanged: OK");
                  }
              }
          });
          WebSettings webSettings = webView.getSettings();
          webSettings.setJavaScriptEnabled(true);
          String cacheDirPath = SdApplication.getInstance().getFilesDir().getAbsolutePath() + "cache/";
          webSettings.setAppCachePath(cacheDirPath);
          // 1. 设置缓存路径

          webSettings.setAppCacheMaxSize(20 * 1024 * 1024);
          // 2. 设置缓存大小

          webSettings.setAppCacheEnabled(true);
      }

  ```

  ## 广播扫码

  这个是需要借助厂商提供内置的APP来配合完成，我去斑马官网找了相关文档。

  https://techdocs.zebra.com/datawedge/7-6/guide/samples/basicintent1/

  这里有很详细的说明。

  需要在DataWedge中启用intent广播，然后自定义Action，在Android注册广播时带上设置的Action，就可以收到了。

  ```java
  package com.wistron_pda.broadcast;

  import android.content.BroadcastReceiver;
  import android.content.Context;
  import android.content.Intent;
  import android.os.Bundle;
  import android.widget.EditText;

  import com.wistron_pda.R;
  import com.wistron_pda.core.SdApplication;

  public class ScanCodeBroadcastReceiver extends BroadcastReceiver {

      private OnReceiveCode onReceive;


      public interface OnReceiveCode {
          void ReceiveBarcodeCallback(String Code);
      }

      public void setOnReceive(OnReceiveCode onReceive) {
          this.onReceive = onReceive;
      }

      @Override
      public void onReceive(Context context, Intent intent) {

          String decodedSource = intent.getStringExtra(SdApplication.getInstance().getString(R.string.datawedge_intent_key_source));
          String decodedData = intent.getStringExtra(SdApplication.getInstance().getString(R.string.datawedge_intent_key_data));
          String decodedLabelType = intent.getStringExtra(SdApplication.getInstance().getString(R.string.datawedge_intent_key_label_type));

          onReceive.ReceiveBarcodeCallback(decodedData);
      }
  }

  ```

  一个简单的扫码回调接口。

  Activity实现广播类中提供的接口，实现。

  ```java
      private void initBroadcastReceiver() {

          setOnReceive = new ScanCodeBroadcastReceiver();
  //        IntentFilter intentFilter = new IntentFilter();
  //        intentFilter.addAction("com.android.scanservice.scancontext");
          IntentFilter filter = new IntentFilter();
          filter.addCategory(Intent.CATEGORY_DEFAULT);
          filter.addAction(getResources().getString(R.string.activity_intent_filter_action));
          registerReceiver(setOnReceive, filter);
          setOnReceive.setOnReceive(this);
      }


      @Override
      public void ReceiveBarcodeCallback(String Code) {
          Log.d(TAG, "条码：: " + Code);
      }
  ```

  初始化时将Action传进去，回调再配置一下，就可以了。

  activity_intent_filter_action 是 DataWedge 配置的Action。



  简单。

  ------

  https://www.cnblogs.com/yangchaojie/p/9660819.html



  https://techdocs.zebra.com/datawedge/7-6/guide/samples/basicintent1/


title: 纬创服务器项目部署操作文档
intro: 软件安装 纬创服务器无法接入互联网，如需安装软件包，前往：https://centos.pkgs.org/ 下载上传至服务器。 已安装运行环境说明 Nginx .....
created_time: 2022-02-17 06:13:15.486678
html_text:
  <h2 id="h2-u8F6Fu4EF6u5B89u88C5"><a name="软件安装" class="reference-link"></a><span class="header-link octicon octicon-link"></span>软件安装</h2><p>纬创服务器无法接入互联网，如需安装软件包，前往：<a href="https://centos.pkgs.org/">https://centos.pkgs.org/</a> 下载上传至服务器。</p>
  <h2 id="h2-u5DF2u5B89u88C5u8FD0u884Cu73AFu5883u8BF4u660E"><a name="已安装运行环境说明" class="reference-link"></a><span class="header-link octicon octicon-link"></span>已安装运行环境说明</h2><h3 id="h3-nginx"><a name="Nginx" class="reference-link"></a><span class="header-link octicon octicon-link"></span>Nginx</h3><p>安装路径：/usr/local/nginx</p>
  <p>配置文件路径: /usr/local/nginx/conf</p>
  <p>配置文件修改后，运行：</p>
  <pre><code class="lang-bash">/usr/local/nginx/sbin/nginx -s reload
  </code></pre>
  <p>使其生效。</p>
  <h2 id="h2-rabbitmq"><a name="RabbitMQ" class="reference-link"></a><span class="header-link octicon octicon-link"></span>RabbitMQ</h2><p>安装路径：/usr/local/rabbitmq</p>
  <p>RabbitMQ常用命令：</p>
  <pre><code class="lang-bash">rabbitmqctl status //查看mq状态
  rabbitmq-server -detached //启动rabbitmq
  rebbitmq-stop //停止rabbitmq进程
  </code></pre>
  <p>查看rabbitmq是否启动：</p>
  <pre><code class="lang-bash">lsof -i:15672
  </code></pre>
  <p>15672端口被COMMAND进程使用表示启动</p>
  <h2 id="h2-dotnet-"><a name="Dotnet 项目运行" class="reference-link"></a><span class="header-link octicon octicon-link"></span>Dotnet 项目运行</h2><p>纬创项目路径：/opt/wistron/wistron-web-publish</p>
  <p>运行项目：</p>
  <pre><code class="lang-bash">/opt/wistron/start.sh
  </code></pre>
  <p>运行日志输出到 /opt/wistron/nohub.out 文件</p>
  <p>实时查看运行日志：</p>
  <pre><code class="lang-bash">tail -f nohub.out
  </code></pre>
  <p>日志查找：</p>
  <pre><code class="lang-bash">grep 'PLC' nohub.out //查询输出nohub.out日志文件中有关PLC字符串的信息
  </code></pre>
  <p>查看项目是否运行：</p>
  <pre><code class="lang-bash">lsof -i:5000
  </code></pre>
  <p>查看5000端口是否在使用或者查看nohub.out文件是否在实时输出日志。</p>
  <h2 id="h2-u4E0Au4F20u9879u76EEu81F3u670Du52A1u5668u5E76u90E8u7F72"><a name="上传项目至服务器并部署" class="reference-link"></a><span class="header-link octicon octicon-link"></span>上传项目至服务器并部署</h2><h3 id="h3-xshell"><a name="Xshell" class="reference-link"></a><span class="header-link octicon octicon-link"></span>Xshell</h3><p>服务器已安装lrzsz工具包，进入目录下输入 <strong>rz</strong> 即可打开windows文件选择器，选择需要上传的文件即可，文件将会上传至当前linux终端所在路径。</p>
  <p>使用 <strong>sz</strong> 文件名将会打开windows文件选择器，选择保存目录即可将文件下载至windows本机。</p>
  <h4 id="h4-u624Bu52A8u90E8u7F72"><a name="手动部署" class="reference-link"></a><span class="header-link octicon octicon-link"></span>手动部署</h4><p>进入 /opt/wistron</p>
  <pre><code class="lang-bash">lsof -i:5000 //查看pid
  kill -9 pid //结束项目进程
  rm -rf wistron-web-piblish //删除项目文件夹
  unzip wistron-web-piblish.zip //解压上传的项目压缩包
  ./start.sh //启动项目
  tail -f nohub.out // 查看项目是否成功运行
  </code></pre>
  <h4 id="h4-u81EAu52A8u90E8u7F72"><a name="自动部署" class="reference-link"></a><span class="header-link octicon octicon-link"></span>自动部署</h4><p>进入 /opt/wistron</p>
  <p>将wistron-web-piblish.zip 上传至 /opt/wistron</p>
  <p>./start.sh </p>
  <p>即可完成自动部署。</p>
  <h2 id="h2-scp"><a name="SCP" class="reference-link"></a><span class="header-link octicon octicon-link"></span>SCP</h2><p>打开windows命令行：</p>
  <pre><code class="lang-bash">scp -r -P 40001 E:\Project\Wistron\wistron-vue\dist\* root@10.41.241.29:/opt/wistron/vue
  </code></pre>
  <h2 id="h2-vue-"><a name="Vue项目" class="reference-link"></a><span class="header-link octicon octicon-link"></span>Vue项目</h2><p>路径：/opt/wistron/vue</p>
  <h2 id="h2-start-sh"><a name="Start.sh" class="reference-link"></a><span class="header-link octicon octicon-link"></span>Start.sh</h2><pre><code class="lang-shell">#!/bin/bash

  rm -rf /opt/wistron/wistron-web-publish

  if [ ! -f "/opt/wistron/wistron-web-publish.zip" ];then
      echo "zip文件不存在"
  else    
      echo "===================开始部署纬创项目================"
      pgrep dotnet | xargs kill -s 9
      rm -rf /opt/wistron/wistron-web-publish
      unzip /opt/wistron/wistron-web-publish.zip
      nohup dotnet /opt/wistron/wistron-web-publish/SD.Wistron.WebApi.dll  &amp;
  fi
  echo "===================部署成功!================"
  </code></pre>
  <h2 id="h2-mysql"><a name="MySql" class="reference-link"></a><span class="header-link octicon octicon-link"></span>MySql</h2><p>进入MySql</p>
  <pre><code class="lang-bash">mysql -uss -p
  </code></pre>
  <p>输入密码：2CFXyJcU<a href="https://github.com/Ks0" title="@Ks0" class="at-link">@Ks0</a></p>
  <p>quit 退出。</p>
  <h2 id="h2-u5E38u7528u547Du4EE4"><a name="常用命令" class="reference-link"></a><span class="header-link octicon octicon-link"></span>常用命令</h2><pre><code class="lang-bash">find / -name '' //查找文件
  ./configuare --prefix=路径 //编译软件包 make &amp;&amp; make install
  systemctl status xxx.service //查看服务运行状态 start stop
  firewall-cmd --zone=public --list-ports //查看所有开放端口
  firewall-cmd --zone=public --add-port=80/tcp --permanent //开放80端口
  firewall-cmd --reload //重载生效
  lsof -i:端口号 //查看端口使用情况
  find / -not -name 'xx' -exec rm -rf {} /; //删除当前除了该文件的所有文件
  pgrep 名称 | xargs kill -s 9 //查找所有进程名称并全部杀死
  rpm -ivh rpm文件名称 //安装rpm软件包
  rpm -qa | grep 名称 //根据名称查找已安装的软件包
  fdisk - //查看磁盘 带*可以使用 mnt 命令挂载 example: mnt /dev/scdd /mnt/usb
  rm -rf ./* //删除当前目录所有文件
  vi 文件名 //编辑文件 在vi中按i进入编辑模式，esc退出，输入:号，/查找 wq保存 q!强制退出不保存
  cp //复制
  mv //移动
  ls //列出所有文件
  touch 创建文件
  mkdir 创建文件夹
  tar -zxvf xxx.tar.gz //解压tar.gz
  vi /etc/profile 编辑系统环境变量 source /etc/profile 生效
  chmod +x 文件|文件夹 //提权
  </code></pre>

markdown_text:
  ## 软件安装

  纬创服务器无法接入互联网，如需安装软件包，前往：https://centos.pkgs.org/ 下载上传至服务器。

  ## 已安装运行环境说明

  ### Nginx

  安装路径：/usr/local/nginx

  配置文件路径: /usr/local/nginx/conf

  配置文件修改后，运行：

  ```bash
  /usr/local/nginx/sbin/nginx -s reload
  ```

  使其生效。

  ## RabbitMQ

  安装路径：/usr/local/rabbitmq

  RabbitMQ常用命令：

  ```bash
  rabbitmqctl status //查看mq状态
  rabbitmq-server -detached //启动rabbitmq
  rebbitmq-stop //停止rabbitmq进程
  ```

  查看rabbitmq是否启动：

  ```bash
  lsof -i:15672
  ```

  15672端口被COMMAND进程使用表示启动

  ## Dotnet 项目运行

  纬创项目路径：/opt/wistron/wistron-web-publish

  运行项目：

  ```bash
  /opt/wistron/start.sh
  ```

  运行日志输出到 /opt/wistron/nohub.out 文件

  实时查看运行日志：

  ```bash
  tail -f nohub.out
  ```

  日志查找：

  ```bash
  grep 'PLC' nohub.out //查询输出nohub.out日志文件中有关PLC字符串的信息 
  ```

  查看项目是否运行：

  ```bash
  lsof -i:5000
  ```

  查看5000端口是否在使用或者查看nohub.out文件是否在实时输出日志。



  ## 上传项目至服务器并部署

  ### Xshell

  服务器已安装lrzsz工具包，进入目录下输入 **rz** 即可打开windows文件选择器，选择需要上传的文件即可，文件将会上传至当前linux终端所在路径。

  使用 **sz** 文件名将会打开windows文件选择器，选择保存目录即可将文件下载至windows本机。

  #### 手动部署

  进入 /opt/wistron

  ```bash
  lsof -i:5000 //查看pid
  kill -9 pid //结束项目进程
  rm -rf wistron-web-piblish //删除项目文件夹
  unzip wistron-web-piblish.zip //解压上传的项目压缩包
  ./start.sh //启动项目
  tail -f nohub.out // 查看项目是否成功运行
  ```

  #### 自动部署

  进入 /opt/wistron

  将wistron-web-piblish.zip 上传至 /opt/wistron

  ./start.sh 

  即可完成自动部署。

  ## SCP

  打开windows命令行：

  ```bash
  scp -r -P 40001 E:\Project\Wistron\wistron-vue\dist\* root@10.41.241.29:/opt/wistron/vue 
  ```



  ## Vue项目

  路径：/opt/wistron/vue



  ## Start.sh

  ```shell
  #!/bin/bash

  rm -rf /opt/wistron/wistron-web-publish

  if [ ! -f "/opt/wistron/wistron-web-publish.zip" ];then
  	echo "zip文件不存在"
  else	
  	echo "===================开始部署纬创项目================"
  	pgrep dotnet | xargs kill -s 9
  	rm -rf /opt/wistron/wistron-web-publish
  	unzip /opt/wistron/wistron-web-publish.zip
  	nohup dotnet /opt/wistron/wistron-web-publish/SD.Wistron.WebApi.dll  &
  fi
  echo "===================部署成功!================"

  ```



  ## MySql

  进入MySql

  ```bash
  mysql -uss -p
  ```

  输入密码：2CFXyJcU@Ks0

  quit 退出。



  ## 常用命令

  ```bash
  find / -name '' //查找文件
  ./configuare --prefix=路径 //编译软件包 make && make install
  systemctl status xxx.service //查看服务运行状态 start stop
  firewall-cmd --zone=public --list-ports //查看所有开放端口
  firewall-cmd --zone=public --add-port=80/tcp --permanent //开放80端口
  firewall-cmd --reload //重载生效
  lsof -i:端口号 //查看端口使用情况
  find / -not -name 'xx' -exec rm -rf {} /; //删除当前除了该文件的所有文件
  pgrep 名称 | xargs kill -s 9 //查找所有进程名称并全部杀死
  rpm -ivh rpm文件名称 //安装rpm软件包
  rpm -qa | grep 名称 //根据名称查找已安装的软件包
  fdisk - //查看磁盘 带*可以使用 mnt 命令挂载 example: mnt /dev/scdd /mnt/usb
  rm -rf ./* //删除当前目录所有文件
  vi 文件名 //编辑文件 在vi中按i进入编辑模式，esc退出，输入:号，/查找 wq保存 q!强制退出不保存
  cp //复制
  mv //移动
  ls //列出所有文件
  touch 创建文件
  mkdir 创建文件夹
  tar -zxvf xxx.tar.gz //解压tar.gz
  vi /etc/profile 编辑系统环境变量 source /etc/profile 生效
  chmod +x 文件|文件夹 //提权
  ```


title: 求和算法
intro: 给定一个数组，一个目标值，算出所有和等于目标值的组合，组合中会出现重复值，且每个重复值只能在每个组合出现一次。包含小数。
created_time: 2022-03-29 05:34:23.345410
html_text:
  <h3 id="h3-u8981u6C42"><a name="要求" class="reference-link"></a><span class="header-link octicon octicon-link"></span>要求</h3><p>给定一个数组，一个目标值，算出所有和等于目标值的组合，组合中会出现重复值，且每个重复值只能在每个组合出现一次。</p>
  <p>包含小数。</p>
  <pre><code class="lang-java">import java.util.*;
  import java.util.stream.Collectors;


  public class Sum {

      public static void main(String[] args) {

          Double[] nums = { 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 5.5,0.1,  0.4, 0.5, 0.5,0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1,};

          List&lt;Double&gt; nlist = Arrays.asList(nums);
          Double target = 3.5;

          List&lt;List&lt;Double&gt;&gt; result = sum(nlist, target, null);
          for (List&lt;Double&gt; doubles : result) {
              System.out.println(Arrays.toString(new List[]{doubles}));
          }
      }


      public static List&lt;List&lt;Double&gt;&gt; sum(List&lt;Double&gt; nums, Double target, List&lt;List&lt;Double&gt;&gt; currentMatch) {
          int len = nums.size();
          if (Objects.isNull(currentMatch)) currentMatch = new ArrayList&lt;&gt;();

          for (int i = 0; i &lt; len; i++) {
              if (target &lt; nums.get(i)) continue;
              for (int j = i + 1; j &lt; nums.size(); j++) {
                  if (target &lt; nums.get(j)) continue;
                  if (nums.get(i) + nums.get(j) == target) {
                      currentMatch.add(Arrays.asList(nums.get(i), nums.get(j)));
                  } else if ((nums.get(i) + nums.get(j)) &lt; target) {
                      List&lt;Double&gt; p1 = new ArrayList&lt;&gt;();
                      p1.add(nums.get(i));
                      p1.add(nums.get(j));
                      List&lt;Double&gt; tempNums = new ArrayList&lt;&gt;(nums);
                      getMatch(p1, tempNums, j + 1, target, currentMatch, null,null);
                  }
              }
          }
          return currentMatch;
      }


      public static void getMatch(List&lt;Double&gt; p1, List&lt;Double&gt; nums, int i, Double target, List&lt;List&lt;Double&gt;&gt; currentMatch, List&lt;Integer&gt; exclude,List&lt;Integer&gt; skips) {
          if (Objects.isNull(exclude)) exclude = new ArrayList&lt;&gt;();
          if (Objects.isNull(skips)) skips = new ArrayList&lt;&gt;();
          int len = nums.size();
          for (; i &lt; len; i++) {
              int finalI = i;
              if (exclude.stream().anyMatch((e) -&gt; e == finalI))continue;
              if (skips.stream().anyMatch((e) -&gt; e == finalI)) continue;
              if ((getSum(p1) + nums.get(i)) == target) {
                  p1.add(nums.get(i));
                  currentMatch.add(p1);
                  exclude.clear();
                  skips.clear();
              } else if ((getSum(p1) + nums.get(i)) &lt; target) {
                  p1.add(nums.get(i));
                  exclude.add(i);
                  getMatch(p1, nums, 0, target, currentMatch, exclude,skips);
              }else if ((getSum(p1) + nums.get(i)) &gt; target) {
                  skips.add(i);
                  getMatch(p1, nums, 0, target, currentMatch, exclude,skips);
              }
          }
      }


      static Double getSum(List&lt;Double&gt; r) {
          if (r.isEmpty()) {
              return 0.0;
          } else {
              return r.stream().mapToDouble(Double::doubleValue).sum();
          }
      }
  }
  </code></pre>
  <p>大概思路是，<strong>O(n^2)</strong> 循环内先看前后两数的和是否为目标值，如果这两个数相加小于目标值，那么进入递归算出组合。</p>
  <p>将已选出的组合数字在递归循环中排除。</p>
  <p>可以确保值只被使用一次。</p>
  <p>如果追加数字大于目标值，将该数下表记录，将下次递归循环重置，遇到该下标跳过。</p>
  <p>优化空间还是有的，只不过我懒得看了。</p>

markdown_text:
  ### 要求

  给定一个数组，一个目标值，算出所有和等于目标值的组合，组合中会出现重复值，且每个重复值只能在每个组合出现一次。

  包含小数。



  ```java
  import java.util.*;
  import java.util.stream.Collectors;


  public class Sum {

      public static void main(String[] args) {

          Double[] nums = { 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 5.5,0.1,  0.4, 0.5, 0.5,0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1,};

          List<Double> nlist = Arrays.asList(nums);
          Double target = 3.5;

          List<List<Double>> result = sum(nlist, target, null);
          for (List<Double> doubles : result) {
              System.out.println(Arrays.toString(new List[]{doubles}));
          }
      }


      public static List<List<Double>> sum(List<Double> nums, Double target, List<List<Double>> currentMatch) {
          int len = nums.size();
          if (Objects.isNull(currentMatch)) currentMatch = new ArrayList<>();

          for (int i = 0; i < len; i++) {
              if (target < nums.get(i)) continue;
              for (int j = i + 1; j < nums.size(); j++) {
                  if (target < nums.get(j)) continue;
                  if (nums.get(i) + nums.get(j) == target) {
                      currentMatch.add(Arrays.asList(nums.get(i), nums.get(j)));
                  } else if ((nums.get(i) + nums.get(j)) < target) {
                      List<Double> p1 = new ArrayList<>();
                      p1.add(nums.get(i));
                      p1.add(nums.get(j));
                      List<Double> tempNums = new ArrayList<>(nums);
                      getMatch(p1, tempNums, j + 1, target, currentMatch, null,null);
                  }
              }
          }
          return currentMatch;
      }


      public static void getMatch(List<Double> p1, List<Double> nums, int i, Double target, List<List<Double>> currentMatch, List<Integer> exclude,List<Integer> skips) {
          if (Objects.isNull(exclude)) exclude = new ArrayList<>();
          if (Objects.isNull(skips)) skips = new ArrayList<>();
          int len = nums.size();
          for (; i < len; i++) {
              int finalI = i;
              if (exclude.stream().anyMatch((e) -> e == finalI))continue;
              if (skips.stream().anyMatch((e) -> e == finalI)) continue;
              if ((getSum(p1) + nums.get(i)) == target) {
                  p1.add(nums.get(i));
                  currentMatch.add(p1);
                  exclude.clear();
                  skips.clear();
              } else if ((getSum(p1) + nums.get(i)) < target) {
                  p1.add(nums.get(i));
                  exclude.add(i);
                  getMatch(p1, nums, 0, target, currentMatch, exclude,skips);
              }else if ((getSum(p1) + nums.get(i)) > target) {
                  skips.add(i);
                  getMatch(p1, nums, 0, target, currentMatch, exclude,skips);
              }
          }
      }


      static Double getSum(List<Double> r) {
          if (r.isEmpty()) {
              return 0.0;
          } else {
              return r.stream().mapToDouble(Double::doubleValue).sum();
          }
      }
  }
  ```



  大概思路是，**O(n^2)** 循环内先看前后两数的和是否为目标值，如果这两个数相加小于目标值，那么进入递归算出组合。

  将已选出的组合数字在递归循环中排除。

  可以确保值只被使用一次。

  如果追加数字大于目标值，将该数下表记录，将下次递归循环重置，遇到该下标跳过。

  优化空间还是有的，只不过我懒得看了。


title: Vue 和 Zebra 打印机连接直接打印条码
created_time: 2022-04-21 09:41:34.023366
intro:
  首先，Vue是无法调用Windows系统功能的，那么打印只能通过浏览器的打印功能来实现，这样显然不行，效率太低，而且斑马打印机是通过ZPL指令进行打印的，用浏览器打印非常不方便。  两个办法：  1. 换技术，使用能够调用Windows Api的语言进行开发功能。 2. 转手给后台程序打印。  我继续用Vue，用C#写了一个后台打印程序，C#开启websocket服务，Vue连接websocket进行通讯，如果Vue接收到ZPL指令就发送后台打印程序，由后台打印程序完成条码打印。

html_text:
  <p>首先，Vue是无法调用Windows系统功能的，那么打印只能通过浏览器的打印功能来实现，这样显然不行，效率太低，而且斑马打印机是通过ZPL指令进行打印的，用浏览器打印非常不方便。</p>
  <p>两个办法：</p>
  <ol>
  <li>换技术，使用能够调用Windows Api的语言进行开发功能。</li><li>转手给后台程序打印。</li></ol>
  <p>我继续用Vue，用C#写了一个后台打印程序，C#开启websocket服务，Vue连接websocket进行通讯，如果Vue接收到ZPL指令就发送后台打印程序，由后台打印程序完成条码打印。</p>
  <p>后台打印程序Websocket，保持心跳。</p>
  <pre><code class="lang-c#">using Fleck;
  using log4net;
  using System;
  using System.Collections.Generic;
  using System.Text;

  namespace CiemisPrintService
  {
      class WebSocketBootStrap
      {

          public static void Start()
          {
              ILog logger = LogManager.GetLogger(typeof(FleckLog));

              FleckLog.LogAction = (level, message, ex) =&gt;
              {
                  switch (level)
                  {
                      case LogLevel.Debug:
                          SimpleLogHelper.LogInfo(message);
                          break;
                      case LogLevel.Error:
                          SimpleLogHelper.LogError(message);
                          break;
                      case LogLevel.Warn:
                          SimpleLogHelper.LogError(message);
                          break;
                      default:
                          SimpleLogHelper.LogInfoSuccess(message);
                          break;
                  }
              };

              var server = new WebSocketServer("ws://0.0.0.0:8181");
              server.SupportedSubProtocols = new[] { "v10.stomp", "v11.stomp" };
              server.RestartAfterListenError = true;
              server.Start(socket =&gt;
              {
                  socket.OnOpen = () =&gt;
                  {
                      string clientUrl = socket.ConnectionInfo.ClientIpAddress + ":" + socket.ConnectionInfo.ClientPort;
                      SimpleLogHelper.LogInfoSuccess("连接进入:"+ clientUrl);
                  };
                  socket.OnClose = () =&gt; SimpleLogHelper.LogError("服务关闭!"); ;
                  socket.OnMessage = message =&gt; {
                      if (message.Equals("ping"))
                      {
                          socket.Send("pong");
                      }
                      else {
                          ZebraLibrary.ZebraPrintHelper.PrinterType = ZebraLibrary.DeviceType.DRV;
                          ZebraLibrary.ZebraPrintHelper.PrinterName = MainForm.mainWindow.GetDefaultPrinterName();
                          ZebraLibrary.ZebraPrintHelper.PrintCommand(message);
                      }
                      SimpleLogHelper.LogInfoSuccess(message);
                  };
                  socket.OnError = e =&gt;
                  {
                      SimpleLogHelper.LogError(e.Message);
                  };
                  socket.OnPing = ping =&gt;
                  {
                      socket.SendPong(Encoding.Default.GetBytes("pong"));
                      SimpleLogHelper.LogInfoSuccess(Encoding.Default.GetString(ping));
                  };

              });

          }

      }
  }
  </code></pre>
  <p>代码很少，很简单的功能。</p>
  <p>Vue Socket进行连接。</p>
  <pre><code class="lang-js">  initSocket () {
        // 实例化socket
        // 监听socket连接
        this.socket.onopen = this.open
        // 监听socket错误信息
        this.socket.onerror = this.error
        // 监听socket消息
        this.socket.onmessage = this.getMessage
        this.socket.onclose = this.close
      },
      reInitSocket () {
        // 实例化socket
        // 监听socket连接
        this.socket = new WebSocket(MQTT_SERVICE)
        this.socket.onopen = this.open
        // 监听socket错误信息
        this.socket.onerror = this.error
        // 监听socket消息
        this.socket.onmessage = this.getMessage
        this.socket.onclose = this.close
      },
      countDownChanged (dismissCountDown) {
        this.dismissCountDown = dismissCountDown
      },
      showAlert () {
        this.dismissCountDown = this.dismissSecs
      },
      keepAlive () {
        var timeout = 1000
        if (this.socket.readyState === WebSocket.OPEN) {
          this.$store.commit('PING', true)
          this.socket.send('ping')
        }
        this.timeId = setTimeout(this.keepAlive, timeout)
      },
      cancelKeepAlive () {
        if (this.timeId) {
          clearTimeout(this.timeId)
        }
      },
      open: function () {
        console.log('socket连接成功')
        this.$store.commit('PING', true)
        this.$global.setWs(this.socket)
        JSON.parse(localStorage.getItem('zpl')).forEach((item, i) =&gt; {
          this.socket.send(item)
        })
        localStorage.setItem('zpl', '')
        this.keepAlive()
      },
      error: function () {
        console.log('连接错误')
      },
      getMessage: function (msg) {
      },
      send: function (msg) {
        this.socket.send(msg)
      },
      close: function () {
        window._VMA.$emit('PRINTER_ERROR', {
          time: 0,
          show: true,
          text: '打印机已断开连接',
          type: 'danger'
        })
        this.$store.commit('PING', false)
        console.log('socket已经关闭')
        setTimeout(() =&gt; {
          this.reInitSocket()
        }, 5000)
      }
    }
  </code></pre>

markdown_text:
  首先，Vue是无法调用Windows系统功能的，那么打印只能通过浏览器的打印功能来实现，这样显然不行，效率太低，而且斑马打印机是通过ZPL指令进行打印的，用浏览器打印非常不方便。

  两个办法：

  1. 换技术，使用能够调用Windows Api的语言进行开发功能。
  2. 转手给后台程序打印。

  我继续用Vue，用C#写了一个后台打印程序，C#开启websocket服务，Vue连接websocket进行通讯，如果Vue接收到ZPL指令就发送后台打印程序，由后台打印程序完成条码打印。

  后台打印程序Websocket，保持心跳。

  ```c#
  using Fleck;
  using log4net;
  using System;
  using System.Collections.Generic;
  using System.Text;

  namespace CiemisPrintService
  {
      class WebSocketBootStrap
      {

          public static void Start()
          {
              ILog logger = LogManager.GetLogger(typeof(FleckLog));

              FleckLog.LogAction = (level, message, ex) =>
              {
                  switch (level)
                  {
                      case LogLevel.Debug:
                          SimpleLogHelper.LogInfo(message);
                          break;
                      case LogLevel.Error:
                          SimpleLogHelper.LogError(message);
                          break;
                      case LogLevel.Warn:
                          SimpleLogHelper.LogError(message);
                          break;
                      default:
                          SimpleLogHelper.LogInfoSuccess(message);
                          break;
                  }
              };

              var server = new WebSocketServer("ws://0.0.0.0:8181");
              server.SupportedSubProtocols = new[] { "v10.stomp", "v11.stomp" };
              server.RestartAfterListenError = true;
              server.Start(socket =>
              {
                  socket.OnOpen = () =>
                  {
                      string clientUrl = socket.ConnectionInfo.ClientIpAddress + ":" + socket.ConnectionInfo.ClientPort;
                      SimpleLogHelper.LogInfoSuccess("连接进入:"+ clientUrl);
                  };
                  socket.OnClose = () => SimpleLogHelper.LogError("服务关闭!"); ;
                  socket.OnMessage = message => {
                      if (message.Equals("ping"))
                      {
                          socket.Send("pong");
                      }
                      else {
                          ZebraLibrary.ZebraPrintHelper.PrinterType = ZebraLibrary.DeviceType.DRV;
                          ZebraLibrary.ZebraPrintHelper.PrinterName = MainForm.mainWindow.GetDefaultPrinterName();
                          ZebraLibrary.ZebraPrintHelper.PrintCommand(message);
                      }
                      SimpleLogHelper.LogInfoSuccess(message);
                  };
                  socket.OnError = e =>
                  {
                      SimpleLogHelper.LogError(e.Message);
                  };
                  socket.OnPing = ping =>
                  {
                      socket.SendPong(Encoding.Default.GetBytes("pong"));
                      SimpleLogHelper.LogInfoSuccess(Encoding.Default.GetString(ping));
                  };

              });

          }

      }
  }
  ```

  代码很少，很简单的功能。

  Vue Socket进行连接。

  ```js
    initSocket () {
        // 实例化socket
        // 监听socket连接
        this.socket.onopen = this.open
        // 监听socket错误信息
        this.socket.onerror = this.error
        // 监听socket消息
        this.socket.onmessage = this.getMessage
        this.socket.onclose = this.close
      },
      reInitSocket () {
        // 实例化socket
        // 监听socket连接
        this.socket = new WebSocket(MQTT_SERVICE)
        this.socket.onopen = this.open
        // 监听socket错误信息
        this.socket.onerror = this.error
        // 监听socket消息
        this.socket.onmessage = this.getMessage
        this.socket.onclose = this.close
      },
      countDownChanged (dismissCountDown) {
        this.dismissCountDown = dismissCountDown
      },
      showAlert () {
        this.dismissCountDown = this.dismissSecs
      },
      keepAlive () {
        var timeout = 1000
        if (this.socket.readyState === WebSocket.OPEN) {
          this.$store.commit('PING', true)
          this.socket.send('ping')
        }
        this.timeId = setTimeout(this.keepAlive, timeout)
      },
      cancelKeepAlive () {
        if (this.timeId) {
          clearTimeout(this.timeId)
        }
      },
      open: function () {
        console.log('socket连接成功')
        this.$store.commit('PING', true)
        this.$global.setWs(this.socket)
        JSON.parse(localStorage.getItem('zpl')).forEach((item, i) => {
          this.socket.send(item)
        })
        localStorage.setItem('zpl', '')
        this.keepAlive()
      },
      error: function () {
        console.log('连接错误')
      },
      getMessage: function (msg) {
      },
      send: function (msg) {
        this.socket.send(msg)
      },
      close: function () {
        window._VMA.$emit('PRINTER_ERROR', {
          time: 0,
          show: true,
          text: '打印机已断开连接',
          type: 'danger'
        })
        this.$store.commit('PING', false)
        console.log('socket已经关闭')
        setTimeout(() => {
          this.reInitSocket()
        }, 5000)
      }
    }
  ```


title: Gateway接口转发维护
created_time: 2022-05-18 06:08:09.406457
intro:
  api dispatch   在开发BI系统的时候，出现了这样一个不方便的地方。  BI报表所展示的数据是从WMS系统通过API获取的，而BI系统也有自己的后台实现了用户权限之类的。  那么现在如果WMS开发了一个新的接口，我就要在BI系统手动写一个接口，实在很不方便。解决的方法也很简单，BI系统开发一个API管理的功能，将WMS接口手动维护进去，包括URL Method Params 等。

html_text:
  <h1>接口转发调用</h1><h2>问题描述</h2><p>在开发BI系统的时候，出现了这样一个不方便的地方。</p><p>BI报表所展示的数据是从WMS系统通过API获取的，而BI系统也有自己的后台实现了用户权限之类的。</p><p>那么现在如果WMS开发了一个新的接口，我就要在BI系统手动写一个接口，实在很不方便。解决的方法也很简单，BI系统开发一个API管理的功能，将WMS接口手动维护进去，包括URL Method Params 等。</p><p><img src="/upload/files/png/997635b6330303e7ec78134f56f86ccb.png" alt=""></p><p>BI系统提供一个统一的WMS接口入口，我叫gateway。</p><p><img src="/upload/files/png/143c0fce20054c3ea5fcbe6738207d24.png" alt=""></p><p>前端只请求gateway接口，带上接口代码和公司代码。</p><h2>Vue Gateway 代码</h2><pre><code class="language-lang-javascript">const api = {
    gateway (api, params) {
      const formData = new FormData()
      formData.append('params', JSON.stringify(params))
      return new Promise((resolve, reject) =&gt; {
        request({
          url: '/gateway/dispatch/' + store.getters.getCompanyCode + '/' + store.getters.getCompanyCode + '-' + api,
          method: 'post',
          data: formData
        })
          .then((resp) =&gt; {
            // eslint-disable-next-line no-prototype-builtins
            if (resp.data.hasOwnProperty('ErrorMessages')) {
              window.CIEMIS.$emit('SERVER_ERROR', {
                text: resp.data.ErrorMessages[0]
              })
            }
            resolve(resp.data)
          })
          .catch((err) =&gt; {
            // eslint-disable-next-line no-prototype-builtins
            reject(err)
          })
      })
    }
  }
  </code></pre><p>BI不止为一个公司服务，因此公司代码是动态获取的，不同的公司可以动态切换。</p><h2>BI 后台实现</h2><pre><code class="language-lang-java">    @RequestMapping("/dispatch/{companyCode}/{api}")
      @ResponseBody
      public ResponseData gateway(@PathVariable String companyCode, @PathVariable String api, String params) {
          Company company = companyRepository.findCompanyByCode(companyCode);
          Gateway gateway = gatewayRepository.findGatewayByCode(api);
          if (Objects.isNull(company)) {
              throw new NullPointerException("公司异常");
          }
          if (Objects.isNull(gateway)) {
              throw new NullPointerException("没有维护API");
          }

          String url = company.getWebAddress() + gateway.getUrl();
          Map&lt;String, String&gt; paramsMap = new HashMap&lt;&gt;();
          // 验证接口参数一致性
          JSONArray array = JSONArray.parseArray(gateway.getParams());
          JSONObject paramsObj = JSONObject.parseObject(params);
          for (int i = 0; i &lt; array.size(); i++) {
              String paramName = array.getJSONObject(i).getString("name");
              if (Objects.isNull(paramsObj.getString(paramName))) {
                  throw new ServiceException(500, "gateway参数校验失败");
              }
              paramsMap.put(paramName, paramsObj.getString(paramName));
          }
          String result;
          try {
              if (gateway.getMethod().equals("POST")) {
                  result = OkHttpUtil.sendPostRequest(url, paramsMap).body().string();
              } else {
                  result = OkHttpUtil.get(url, paramsMap).body().string();
              }
          } catch (IOException e) {
              throw new ServiceException(500, "gateway IO Exception: " + e.getMessage());
          }
          Object o = JSON.parse(result);
          if (o instanceof JSONArray) {
              return ResponseData.success(200, "OK", JSON.parseArray(result));
          } else {
              return ResponseData.success(200, "OK", JSON.parseObject(result));
          }
      }
  </code></pre><h2>实际使用</h2><pre><code class="language-lang-javascript">api.gateway('BIScrapActions', {
    PlantCode: 'P01',
    RptType: 'Scrap'
  }).then((resp) =&gt; {
    this.data = resp
    this.initCharts()
  })
  </code></pre><p>这样就完成了。</p><h3>POSTMAN 参数自动格式化</h3><pre><code class="language-lang-javascript">  this.params_table = val.split(' ').map((item) =&gt; {
      return {
        name: item.split(':')[0],
        value: item.split(':')[1]
      }
    })
  },
  </code></pre><h2>提供接口测试</h2><p>在接口维护的时候会填写参数KV，值为测试值，可以在网页测试接口是否通过。在实际调用中会对参数进行匹配。</p><p><img src="/upload/files/png/f850f500e07fca398180c3dfd38bd513.png" alt=""></p><p><br></p>

markdown_text:
  接口转发调用
  问题描述
  在开发BI系统的时候，出现了这样一个不方便的地方。
  BI报表所展示的数据是从WMS系统通过API获取的，而BI系统也有自己的后台实现了用户权限之类的。
  那么现在如果WMS开发了一个新的接口，我就要在BI系统手动写一个接口，实在很不方便。解决的方法也很简单，BI系统开发一个API管理的功能，将WMS接口手动维护进去，包括URL Method Params 等。

  BI系统提供一个统一的WMS接口入口，我叫gateway。

  前端只请求gateway接口，带上接口代码和公司代码。
  Vue Gateway 代码
  const api = {
    gateway (api, params) {
      const formData = new FormData()
      formData.append('params', JSON.stringify(params))
      return new Promise((resolve, reject) => {
        request({
          url: '/gateway/dispatch/' + store.getters.getCompanyCode + '/' + store.getters.getCompanyCode + '-' + api,
          method: 'post',
          data: formData
        })
          .then((resp) => {
            // eslint-disable-next-line no-prototype-builtins
            if (resp.data.hasOwnProperty('ErrorMessages')) {
              window.CIEMIS.$emit('SERVER_ERROR', {
                text: resp.data.ErrorMessages[0]
              })
            }
            resolve(resp.data)
          })
          .catch((err) => {
            // eslint-disable-next-line no-prototype-builtins
            reject(err)
          })
      })
    }
  }

  BI不止为一个公司服务，因此公司代码是动态获取的，不同的公司可以动态切换。
  BI 后台实现
      @RequestMapping("/dispatch/{companyCode}/{api}")
      @ResponseBody
      public ResponseData gateway(@PathVariable String companyCode, @PathVariable String api, String params) {
          Company company = companyRepository.findCompanyByCode(companyCode);
          Gateway gateway = gatewayRepository.findGatewayByCode(api);
          if (Objects.isNull(company)) {
              throw new NullPointerException("公司异常");
          }
          if (Objects.isNull(gateway)) {
              throw new NullPointerException("没有维护API");
          }

          String url = company.getWebAddress() + gateway.getUrl();
          Map<String, String> paramsMap = new HashMap<>();
          // 验证接口参数一致性
          JSONArray array = JSONArray.parseArray(gateway.getParams());
          JSONObject paramsObj = JSONObject.parseObject(params);
          for (int i = 0; i < array.size(); i++) {
              String paramName = array.getJSONObject(i).getString("name");
              if (Objects.isNull(paramsObj.getString(paramName))) {
                  throw new ServiceException(500, "gateway参数校验失败");
              }
              paramsMap.put(paramName, paramsObj.getString(paramName));
          }
          String result;
          try {
              if (gateway.getMethod().equals("POST")) {
                  result = OkHttpUtil.sendPostRequest(url, paramsMap).body().string();
              } else {
                  result = OkHttpUtil.get(url, paramsMap).body().string();
              }
          } catch (IOException e) {
              throw new ServiceException(500, "gateway IO Exception: " + e.getMessage());
          }
          Object o = JSON.parse(result);
          if (o instanceof JSONArray) {
              return ResponseData.success(200, "OK", JSON.parseArray(result));
          } else {
              return ResponseData.success(200, "OK", JSON.parseObject(result));
          }
      }

  实际使用
  api.gateway('BIScrapActions', {
    PlantCode: 'P01',
    RptType: 'Scrap'
  }).then((resp) => {
    this.data = resp
    this.initCharts()
  })

  这样就完成了。
  POSTMAN 参数自动格式化
    this.params_table = val.split(' ').map((item) => {
      return {
        name: item.split(':')[0],
        value: item.split(':')[1]
      }
    })
  },

  提供接口测试
  在接口维护的时候会填写参数KV，值为测试值，可以在网页测试接口是否通过。在实际调用中会对参数进行匹配。


title: Incremental data ETL
intro: Incremental data ETL
created_time: 2022-08-30 08:10:40.677986
html_text:
  <p>MongoDb增量数据读取，最后一条数据根据 ObjectId + ISODate 作为 标记，下次读取 &gt; ObjectId &gt;= ISODate 即可继续读取。<br><img src="/upload/files/png/ccb0ec18b42272371fb002ca0fc45df5.png" alt="">
  <pre><code class="lang-python">import datetime
  import os
  import dateutil.parser
  from bson import ObjectId

  import LoggerLocal as log
  import DBCollection

  name = os.path.basename(__file__)
  # 120,MesTestDetail.py,"",2022-08-30 14:30:35,mes_mongodb,MesTestDetail,Angle45°_AngleCheck

  filter_item = ['Angle45°_AngleCheck']

  MAX_HANDLE_LINE = 100

  mssql_db = DBCollection.get_dbobj("mes_mssql", "mssql")
  mongodb = DBCollection.get_dbobj("mes_mongodb", "mongodb")


  def MesTestDetail():
      global mssql_db
      global mongodb
      try:
          log.info(f"======={datetime.datetime.now()} {name} 执行=================")
          mssql_cursor = mssql_db.cursor(as_dict=True)
          for item in filter_item:
              point = DBCollection.get_point(name, 'mes_mongodb', 'MesTestDetail', item)
              client = mongodb['hasco_mes']
              tb = client['MES_MesTestDetail']
              if point[0][2] == '':
                  myquery = {"TestItem": item, "ItemNo": {"$exists": True}}
              else:
                  id_str = str(point[0][2]).split("#")[0]
                  log_date = str(point[0][2]).split("#")[1]
                  obj_id = ObjectId(id_str)
                  date = dateutil.parser.isoparse(log_date)
                  myquery = {"TestItem": item, "ItemNo": {"$exists": True}, "LogDate": {"$gte": date},
                             "_id": {'$gt': obj_id}}
              cols = tb.find(myquery).sort("LogDate").limit(MAX_HANDLE_LINE)
              point_obj_id = ''
              point_log_date = ''
              for data in cols:
                  print(data)
                  mssql_cursor.executemany(
                      "INSERT INTO dbo.Mes_XRTestDetail VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)",
                      [(data["Barcode"], data["ItemNo"], data['StartTime'], data['EndTime'], data['TestItem'],
                        data['Value01'], data['Value02'], data['Value03'], data['Value04'], data['Value05'],
                        data['LogDate'], data['LogUser'])])
                  point_obj_id = data['_id']
                  point_log_date = data['LogDate']
              log.info(f"item:{item} insert success!")
              log.info( f"{point_obj_id}#{point_log_date}")
              DBCollection.set_point(name, 'mes_mongodb', 'MesTestDetail', f"{point_obj_id}#{point_log_date}", item_key=item)
              mssql_db.commit()

              log.info("执行完成。")
              DBCollection.set_result(name, True, log.get_buffer())
      except Exception as e:
          mssql_db.rollback()
          DBCollection.job_db.rollback()
          log.info(f"事务已回滚")
          log.info(f"执行[{name}]失败 异常:{e}")
          DBCollection.set_result(script=name, result=False, log_text=log.get_buffer())
          print(f"执行[{name}]失败,  异常:{e}")


  if __name__ == '__main__':
      MesTestDetail()
  </code></pre>

markdown_text:
  MongoDb增量数据读取，最后一条数据根据 ObjectId + ISODate 作为 标记，下次读取 > ObjectId >= ISODate 即可继续读取。
  ![](/upload/files/png/ccb0ec18b42272371fb002ca0fc45df5.png)
  ```python
  import datetime
  import os
  import dateutil.parser
  from bson import ObjectId

  import LoggerLocal as log
  import DBCollection

  name = os.path.basename(__file__)
  # 120,MesTestDetail.py,"",2022-08-30 14:30:35,mes_mongodb,MesTestDetail,Angle45°_AngleCheck

  filter_item = ['Angle45°_AngleCheck']

  MAX_HANDLE_LINE = 100

  mssql_db = DBCollection.get_dbobj("mes_mssql", "mssql")
  mongodb = DBCollection.get_dbobj("mes_mongodb", "mongodb")


  def MesTestDetail():
      global mssql_db
      global mongodb
      try:
          log.info(f"======={datetime.datetime.now()} {name} 执行=================")
          mssql_cursor = mssql_db.cursor(as_dict=True)
          for item in filter_item:
              point = DBCollection.get_point(name, 'mes_mongodb', 'MesTestDetail', item)
              client = mongodb['hasco_mes']
              tb = client['MES_MesTestDetail']
              if point[0][2] == '':
                  myquery = {"TestItem": item, "ItemNo": {"$exists": True}}
              else:
                  id_str = str(point[0][2]).split("#")[0]
                  log_date = str(point[0][2]).split("#")[1]
                  obj_id = ObjectId(id_str)
                  date = dateutil.parser.isoparse(log_date)
                  myquery = {"TestItem": item, "ItemNo": {"$exists": True}, "LogDate": {"$gte": date},
                             "_id": {'$gt': obj_id}}
              cols = tb.find(myquery).sort("LogDate").limit(MAX_HANDLE_LINE)
              point_obj_id = ''
              point_log_date = ''
              for data in cols:
                  print(data)
                  mssql_cursor.executemany(
                      "INSERT INTO dbo.Mes_XRTestDetail VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)",
                      [(data["Barcode"], data["ItemNo"], data['StartTime'], data['EndTime'], data['TestItem'],
                        data['Value01'], data['Value02'], data['Value03'], data['Value04'], data['Value05'],
                        data['LogDate'], data['LogUser'])])
                  point_obj_id = data['_id']
                  point_log_date = data['LogDate']
              log.info(f"item:{item} insert success!")
              log.info( f"{point_obj_id}#{point_log_date}")
              DBCollection.set_point(name, 'mes_mongodb', 'MesTestDetail', f"{point_obj_id}#{point_log_date}", item_key=item)
              mssql_db.commit()

              log.info("执行完成。")
              DBCollection.set_result(name, True, log.get_buffer())
      except Exception as e:
          mssql_db.rollback()
          DBCollection.job_db.rollback()
          log.info(f"事务已回滚")
          log.info(f"执行[{name}]失败 异常:{e}")
          DBCollection.set_result(script=name, result=False, log_text=log.get_buffer())
          print(f"执行[{name}]失败,  异常:{e}")


  if __name__ == '__main__':
      MesTestDetail()

  ```


title: Linux 操作记录 OpenVpn RabbitMQ Docker Nginx
intro: Linux 操作记录 OpenVpn RabbitMQ Docker Nginx
created_time: 2022-11-25 08:00:20.372608
html_text:
  <h2 id="h2-openvpn"><a name="Openvpn" class="reference-link"></a><span class="header-link octicon octicon-link"></span>Openvpn</h2><pre><code class="lang-shell">sudo apt update
  sudo apt install openvpn -y
  mkdir /opt/openvpn-client
  sudo apt install screen
  cd /opt/openvpn-client
  #xshell
  sudo apt-get install lrzsz
  rz #upload ovpn
  screen -S openvpnclient
  screen -r openvpnclient
  openvpn --config ciemis.ovpn --auth-user-pass pwd
  </code></pre>
  <p><a href="https://www.ivpn.net/knowledgebase/linux/linux-autostart-openvpn-in-systemd-ubuntu/">https://www.ivpn.net/knowledgebase/linux/linux-autostart-openvpn-in-systemd-ubuntu/</a></p>
  <h2 id="h2-docker"><a name="Docker" class="reference-link"></a><span class="header-link octicon octicon-link"></span>Docker</h2><p><a href="https://docs.docker.com/engine/install/ubuntu/">https://docs.docker.com/engine/install/ubuntu/</a></p>
  <p>Docker0 网卡 172网段与openvpn冲突。</p>
  <blockquote>
  <ul>
  <li>Edit /lib/systemd/system/docker.service before start Docker. Add <code>--bip "192.168.1.1/24"</code> at the end of line <code>ExecStart=/usr/bin/dockerd</code>.</li><li><code>systemctl daemon-reload</code></li><li><code>systemctl start docker</code></li></ul>
  </blockquote>
  <h2 id="h2-nginx"><a name="Nginx" class="reference-link"></a><span class="header-link octicon octicon-link"></span>Nginx</h2><pre><code class="lang-shell">sudo apt install nginx
  </code></pre>
  <h2 id="h2-rabbitmq"><a name="RabbitMQ" class="reference-link"></a><span class="header-link octicon octicon-link"></span>RabbitMQ</h2><pre><code class="lang-shell">docker pull docker.io/library/rabbitmq:3.7.7-management
  docker run -d --hostname rabbitmq_server --name mes_mq -p 5673:5672  15672:15672   -p 5674:5674  rabbitmq:3.7.7-management 
  root@FriendlyELEC:~# docker update  --restart=always 1d7f29f8601c
  </code></pre>

markdown_text:
  ## Openvpn 

  ```shell
  sudo apt update
  sudo apt install openvpn -y
  mkdir /opt/openvpn-client
  sudo apt install screen
  cd /opt/openvpn-client
  #xshell
  sudo apt-get install lrzsz
  rz #upload ovpn
  screen -S openvpnclient
  screen -r openvpnclient
  openvpn --config ciemis.ovpn --auth-user-pass pwd
  ```

  https://www.ivpn.net/knowledgebase/linux/linux-autostart-openvpn-in-systemd-ubuntu/

  ## Docker

  https://docs.docker.com/engine/install/ubuntu/

  Docker0 网卡 172网段与openvpn冲突。

  > - Edit /lib/systemd/system/docker.service before start Docker. Add `--bip "192.168.1.1/24"` at the end of line `ExecStart=/usr/bin/dockerd`.
  > - `systemctl daemon-reload`
  > - `systemctl start docker`



  ## Nginx

  ```shell
  sudo apt install nginx
  ```



  ## RabbitMQ

  ```shell
  docker pull docker.io/library/rabbitmq:3.7.7-management
  docker run -d --hostname rabbitmq_server --name mes_mq -p 5673:5672  15672:15672   -p 5674:5674  rabbitmq:3.7.7-management 
  root@FriendlyELEC:~# docker update  --restart=always 1d7f29f8601c
  ```


title: Python 函数动态调用
created_time: 2023-03-15 02:00:43.364515
intro:
  Requirement  Http -> Aliyun FC -> Python Handle -> dynamic condition -> Template  发票文件OCR解析识别后从JSON文件从中提取需要的信息。  一个供应商一个发票，有多个供应商，根据供应商名称来识别，我这里的做法是一个供应商一个py文件，文件名用供应商代码来命名。

html_text:
  <h3 id="h3-requirement"><a name="Requirement" class="reference-link"></a><span class="header-link octicon octicon-link"></span>Requirement</h3><p>Http -&gt; Aliyun FC -&gt; Python Handle -&gt; dynamic condition -&gt; Template</p>
  <p>发票文件OCR解析识别后从JSON文件从中提取需要的信息。</p>
  <p>一个供应商一个发票，有多个供应商，根据供应商名称来识别，我这里的做法是一个供应商一个py文件，文件名用供应商代码来命名。</p>
  <p>在写的时候我想避免以下代码结构：</p>
  <pre><code class="lang-python">import 供应商1.py as s1
  import 供应商2.py as s2
  if supplier_code == '供应商1':
      s1.parse()
  elif: supplier_code == '供应商2':
      s2.parse()
  ....
  </code></pre>
  <p>这种方式明显显得不够灵活和动态，于是经过查阅，发现Python可以动态导入模块 于是有了下面代码：</p>
  <p><img src="/upload/files/png/d654b66f9c3d3d2c2fed2351b94ea38b.png" alt="">
  <pre><code class="lang-python">import importlib
  import common as util
  import traceback

  def dispatch(plant:str,supplier:str,file_extension:str,ocr_result_obj):
      try:
          dynamic_module = importlib.import_module('template.' + supplier)
          func_call = getattr(dynamic_module, "parse")
          return func_call(plant,supplier,file_extension,ocr_result_obj,util)
      except Exception as e:
          traceback.print_exc()
      except AttributeError as ae:
          raise ModuleNotFoundError("调用失败: {0}".format(ae))
      except TypeError as te:
          raise ModuleNotFoundError("类型异常: {0}".format(te))
      except ModuleNotFoundError as e1:
          raise ModuleNotFoundError("没有找到模块: {0}".format(e1))
  </code></pre>
  <p>import_module 动态导入，getattr 动态调用模块函数，以此方法达到如果新增供应商解析模板，只要在template目录下新增一个名称对应的py文件，定义parse方法即可，其他的都不用管。</p>
  <pre><code class="lang-python">import re

  def get_ln(text:str):
      pattern = re.compile(r'-?[1-9]\d*')
      return pattern.search(text).group()

  def get_po(text:str):
      pattern = re.compile(r'P\s*X\s*Z\s*[0-9]{5}')
      result = pattern.match(text)
      if result is not None:
          return True
      else:
          return False

  def get_invoice_amount(text:str):
      pattern = re.compile(r'00\s*USD$')
      result = pattern.search(text)
      if result is not None:
          return True
      else:
          return False

  def get_ip_no(text:str):
      pattern = re.compile(r'^\bde\s*.*\s*[0-9]$', re.IGNORECASE)
      result = pattern.search(text)
      if result is not None:
          return True
      else:
          return False

  def parse(plant,supplier,file_extension,ocr_result_obj,util):
      # print("收到参数:{0} {1}".format(plant,str(ocr_result_obj['Headers'])))
      PEOPERTY = ""

      results = None
      if file_extension == '.pdf':
          PEOPERTY = "Text"
          results = ocr_result_obj['Body']['Data']['Results']
      else:
          PEOPERTY = "Word"
          results = ocr_result_obj['Body']['Data']['WordsInfo']

      po = util.find_match_by(results,get_po,PEOPERTY)
      invoice_amount =  util.find_match_by(results,get_invoice_amount,PEOPERTY)
      lp_no = util.find_match_by(results,get_ip_no,PEOPERTY)
      ln = util.find_result_by_text('Invoice number',results,get_ln,PEOPERTY)

      if po is not None:
          po = po.replace(' ','')
      if lp_no is not None:
          lp_no = util.get_match_number(lp_no)
      if invoice_amount is not None:
          invoice_amount = invoice_amount.replace(' ','').replace('，','').replace(',','').replace('USD','')

      # return [ln,po,invoice_amount,lp_no]
      return util.wrapper(ln,invoice_amount,lp_no,po,None,None)
  </code></pre>
  <p>弊端：</p>
  <p>被动态调用的函数不能再当前文件引入其他类库，会报错，我通过函数回调来解决，但增加了一些代码的复杂度。</p>
  <p>​        </p>

markdown_text:
  ### Requirement

  Http -> Aliyun FC -> Python Handle -> dynamic condition -> Template

  发票文件OCR解析识别后从JSON文件从中提取需要的信息。

  一个供应商一个发票，有多个供应商，根据供应商名称来识别，我这里的做法是一个供应商一个py文件，文件名用供应商代码来命名。

  在写的时候我想避免以下代码结构：

  ```python
  import 供应商1.py as s1
  import 供应商2.py as s2
  if supplier_code == '供应商1':
      s1.parse()
  elif: supplier_code == '供应商2':
      s2.parse()
  ....
  ```

  这种方式明显显得不够灵活和动态，于是经过查阅，发现Python可以动态导入模块 于是有了下面代码：

  ![](/upload/files/png/d654b66f9c3d3d2c2fed2351b94ea38b.png)

  ```python
  import importlib
  import common as util
  import traceback

  def dispatch(plant:str,supplier:str,file_extension:str,ocr_result_obj):
      try:
          dynamic_module = importlib.import_module('template.' + supplier)
          func_call = getattr(dynamic_module, "parse")
          return func_call(plant,supplier,file_extension,ocr_result_obj,util)
      except Exception as e:
          traceback.print_exc()
      except AttributeError as ae:
          raise ModuleNotFoundError("调用失败: {0}".format(ae))
      except TypeError as te:
          raise ModuleNotFoundError("类型异常: {0}".format(te))
      except ModuleNotFoundError as e1:
          raise ModuleNotFoundError("没有找到模块: {0}".format(e1))


  ```

  import_module 动态导入，getattr 动态调用模块函数，以此方法达到如果新增供应商解析模板，只要在template目录下新增一个名称对应的py文件，定义parse方法即可，其他的都不用管。

  ```python
  import re

  def get_ln(text:str):
      pattern = re.compile(r'-?[1-9]\d*')
      return pattern.search(text).group()

  def get_po(text:str):
      pattern = re.compile(r'P\s*X\s*Z\s*[0-9]{5}')
      result = pattern.match(text)
      if result is not None:
          return True
      else:
          return False

  def get_invoice_amount(text:str):
      pattern = re.compile(r'00\s*USD$')
      result = pattern.search(text)
      if result is not None:
          return True
      else:
          return False

  def get_ip_no(text:str):
      pattern = re.compile(r'^\bde\s*.*\s*[0-9]$', re.IGNORECASE)
      result = pattern.search(text)
      if result is not None:
          return True
      else:
          return False

  def parse(plant,supplier,file_extension,ocr_result_obj,util):
      # print("收到参数:{0} {1}".format(plant,str(ocr_result_obj['Headers'])))
      PEOPERTY = ""

      results = None
      if file_extension == '.pdf':
          PEOPERTY = "Text"
          results = ocr_result_obj['Body']['Data']['Results']
      else:
          PEOPERTY = "Word"
          results = ocr_result_obj['Body']['Data']['WordsInfo']

      po = util.find_match_by(results,get_po,PEOPERTY)
      invoice_amount =  util.find_match_by(results,get_invoice_amount,PEOPERTY)
      lp_no = util.find_match_by(results,get_ip_no,PEOPERTY)
      ln = util.find_result_by_text('Invoice number',results,get_ln,PEOPERTY)

      if po is not None:
          po = po.replace(' ','')
      if lp_no is not None:
          lp_no = util.get_match_number(lp_no)
      if invoice_amount is not None:
          invoice_amount = invoice_amount.replace(' ','').replace('，','').replace(',','').replace('USD','')
      
      # return [ln,po,invoice_amount,lp_no]
      return util.wrapper(ln,invoice_amount,lp_no,po,None,None)
  ```

  弊端：

  被动态调用的函数不能再当前文件引入其他类库，会报错，我通过函数回调来解决，但增加了一些代码的复杂度。

  ​		


title: Openvpn 重续证书
intro: Openvpn 重续证书
created_time: 2023-03-17 07:53:54.921854
html_text:
  <pre><code class="lang-bash">Xshell 7 (Build 0093)
  Copyright (c) 2020 NetSarang Computer, Inc. All rights reserved.

  Type `help' to learn how to use Xshell prompt.
  [C:\~]$ 

  Connecting to 172.17.21.25:22...
  Could not connect to '172.17.21.25' (port 22): Connection failed.

  Type `help' to learn how to use Xshell prompt.
  [C:\~]$ 

  Connecting to 172.17.21.25:22...
  Could not connect to '172.17.21.25' (port 22): Connection failed.

  Type `help' to learn how to use Xshell prompt.
  [C:\~]$ 

  Connecting to 172.17.21.26:22...
  Connection established.
  To escape to local shell, press Ctrl+Alt+].

  WARNING! The remote SSH server rejected X11 forwarding request.

  Welcome to Alibaba Cloud Elastic Compute Service !

  Last login: Wed Mar 15 16:39:34 2023 from 172.17.21.25
  [yangchaojie@t3 ~]$ ssh -p 22 root@172.17.21.25
  The authenticity of host '172.17.21.25 (172.17.21.25)' can't be established.
  ECDSA key fingerprint is SHA256:VY15Lar9HjcUt2OwYClxK4L2AnLJBAh/GheCVG3iep4.
  Are you sure you want to continue connecting (yes/no/[fingerprint])? 
  Host key verification failed.
  [yangchaojie@t3 ~]$ ssh -p 22 root@172.17.21.25
  The authenticity of host '172.17.21.25 (172.17.21.25)' can't be established.
  ECDSA key fingerprint is SHA256:VY15Lar9HjcUt2OwYClxK4L2AnLJBAh/GheCVG3iep4.
  Are you sure you want to continue connecting (yes/no/[fingerprint])? y
  Please type 'yes', 'no' or the fingerprint: y
  Please type 'yes', 'no' or the fingerprint: yes
  Warning: Permanently added '172.17.21.25' (ECDSA) to the list of known hosts.
  root@172.17.21.25's password: 
  Last login: Fri Nov 25 15:00:03 2022 from 192.168.112.30

  Welcome to Alibaba Cloud Elastic Compute Service !

  [root@t2 ~]# ls
  centos7-vpn.sh  client.tar.gz   epel-release-latest-7.noarch.rpm  influxdb-1.7.6.x86_64.rpm  master.zip  telegraf-1.15.3-1.x86_64.rpm
  client          controlsfx.jar  grafana-7.1.0-1.x86_64.rpm        influxdb-1.8.3.x86_64.rpm  nginx
  [root@t2 ~]# cd /opt/
  [root@t2 opt]# ls
  project
  [root@t2 opt]# cd project/
  [root@t2 project]# ls
  db.sqlite3  manage.py  nohup.out  Pipfile  Pipfile.lock  requirements.txt  start.sh  static  templates  vpn  vpn_ms
  [root@t2 project]# cd vpn
  [root@t2 vpn]# ls
  admin.py  apps.py  context_processors.py  __init__.py  migrations  models.py  __pycache__  tests.py  views.py
  [root@t2 vpn]# cd /etc/
  [root@t2 etc]# ls
  adjtime            cron.weekly              gdbinit.d    issue.net       motd                       polkit-1          rsyslog.conf         sysctl.d
  adjtime.rpmsave    crypttab                 GeoIP.conf   kernel          mtab                       popt.d            rsyslog.d            systemd
  aliases            csh.cshrc                gnupg        krb5.conf       my.cnf                     postfix           rwtab                system-lsb
  aliases.db         csh.login                grafana      krb5.conf.d     my.cnf.d                   ppp               rwtab.d              system-release
  alinux-release     dbus-1                   GREP_COLORS  ld.so.cache     netconfig                  prelink.conf.d    sasl2                telegraf
  alternatives       default                  groff        ld.so.conf      NetworkManager             printcap          securetty            terminfo
  anacrontab         depmod.d                 group        ld.so.conf.d    networks                   profile           security             tmpfiles.d
  anolis-release     dhcp                     group-       libaudit.conf   nfs.conf                   profile.d         selinux              tuned
  asound.conf        DIR_COLORS               grub2.cfg    libnl           nfsmount.conf              protocols         services             udev
  at.deny            DIR_COLORS.256color      grub.d       libreport       nscd.conf                  python            sestatus.conf        update-motd.d
  audisp             DIR_COLORS.lightbgcolor  gshadow      libuser.conf    nsswitch.conf              rc0.d             shadow               vconsole.conf
  audit              docker                   gshadow-     locale.conf     nsswitch.conf.bak          rc1.d             shadow-              vimrc
  bash_completion.d  dracut.conf              gss          localtime       nsswitch.conf.rpmnew       rc2.d             shells               virc
  bashrc             dracut.conf.d            gssproxy     login.defs      ntp.conf                   rc3.d             skel                 wgetrc
  binfmt.d           e2fsck.conf              host.conf    logrotate.conf  oci-register-machine.conf  rc4.d             ssh                  wireguard
  centos-release     environment              hostname     logrotate.d     oci-umount                 rc5.d             ssl                  wpa_supplicant
  chkconfig.d        ethertypes               hosts        lsb-release.d   oci-umount.conf            rc6.d             statetab             X11
  chrony.conf        exports                  hosts.allow  lvm             openldap                   rc.d              statetab.d           xdg
  chrony.keys        exports.d                hosts.deny   machine-id      openvpn                    rc.local          subgid               xinetd.d
  cloud              favicon.png              idmapd.conf  magic           opt                        redhat-release    subuid               yum
  containers         filesystems              image-id     mailcap         os-release                 request-key.conf  sudo.conf            yum.conf
  cron.d             firewalld                influxdb     mail.rc         pam.d                      request-key.d     sudoers              yum.repos.d
  cron.daily         fonts                    init.d       man_db.conf     passwd                     resolv.conf       sudoers.d
  cron.deny          fstab                    inittab      mime.types      passwd-                    resolv.conf.save  sudo-ldap.conf
  cron.hourly        fuse.conf                inputrc      mke2fs.conf     pkcs11                     rpc               sysconfig
  cron.monthly       gcrypt                   iproute2     modprobe.d      pki                        rpm               sysctl.conf
  crontab            gdbinit                  issue        modules-load.d  pm                         rsyncd.conf       sysctl.conf.rpmsave
  [root@t2 etc]# cd op
  openldap/ openvpn/  opt/      
  [root@t2 etc]# cd op
  openldap/ openvpn/  opt/      
  [root@t2 etc]# cd openvpn/
  [root@t2 openvpn]# ls
  ca.crt  ccd  checkpsw.sh  client  dh.pem  easy-rsa  ipp.txt  openvpn-password.log  psw-file  server  server.conf  server_crt  server.crt  server.key
  [root@t2 openvpn]# ls
  ca.crt  ccd  checkpsw.sh  client  dh.pem  easy-rsa  ipp.txt  openvpn-password.log  psw-file  server  server.conf  server_crt  server.crt  server.key
  [root@t2 openvpn]# openssl x509 -noout -text in server.crt 
  unknown option in
  usage: x509 args
   -inform arg     - input format - default PEM (one of DER, NET or PEM)
   -outform arg    - output format - default PEM (one of DER, NET or PEM)
   -keyform arg    - private key format - default PEM
   -CAform arg     - CA format - default PEM
   -CAkeyform arg  - CA key format - default PEM
   -in arg         - input file - default stdin
   -out arg        - output file - default stdout
   -passin arg     - private key password source
   -serial         - print serial number value
   -subject_hash   - print subject hash value
   -subject_hash_old   - print old-style (MD5) subject hash value
   -issuer_hash    - print issuer hash value
   -issuer_hash_old    - print old-style (MD5) issuer hash value
   -hash           - synonym for -subject_hash
   -subject        - print subject DN
   -issuer         - print issuer DN
   -email          - print email address(es)
   -startdate      - notBefore field
   -enddate        - notAfter field
   -purpose        - print out certificate purposes
   -dates          - both Before and After dates
   -modulus        - print the RSA key modulus
   -pubkey         - output the public key
   -fingerprint    - print the certificate fingerprint
   -alias          - output certificate alias
   -noout          - no certificate output
   -ocspid         - print OCSP hash values for the subject name and public key
   -ocsp_uri       - print OCSP Responder URL(s)
   -trustout       - output a "trusted" certificate
   -clrtrust       - clear all trusted purposes
   -clrreject      - clear all rejected purposes
   -addtrust arg   - trust certificate for a given purpose
   -addreject arg  - reject certificate for a given purpose
   -setalias arg   - set certificate alias
   -days arg       - How long till expiry of a signed certificate - def 30 days
   -checkend arg   - check whether the cert expires in the next arg seconds
                     exit 1 if so, 0 if not
   -signkey arg    - self sign cert with arg
   -x509toreq      - output a certification request object
   -req            - input is a certificate request, sign and output.
   -CA arg         - set the CA certificate, must be PEM format.
   -CAkey arg      - set the CA key, must be PEM format
                     missing, it is assumed to be in the CA file.
   -CAcreateserial - create serial number file if it does not exist
   -CAserial arg   - serial file
   -set_serial     - serial number to use
   -text           - print the certificate in text form
   -C              - print out C code forms
   -&lt;dgst&gt;         - digest to use, see openssl dgst -h output for list
   -extfile        - configuration file with X509V3 extensions to add
   -extensions     - section from config file with X509V3 extensions to add
   -clrext         - delete extensions before signing and input certificate
   -nameopt arg    - various certificate name options
   -engine e       - use engine e, possibly a hardware device.
   -certopt arg    - various certificate text options
   -checkhost host - check certificate matches "host"
   -checkemail email - check certificate matches "email"
   -checkip ipaddr - check certificate matches "ipaddr"
  [root@t2 openvpn]# openssl x509 -noout -text -in server.crt 
  Certificate:
      Data:
          Version: 3 (0x2)
          Serial Number:
              95:a6:bd:d4:02:d2:63:dc:bd:08:39:14:6e:a3:3c:94
      Signature Algorithm: sha256WithRSAEncryption
          Issuer: CN=micvs
          Validity
              Not Before: Dec 12 04:50:26 2020 GMT
              Not After : Mar 17 04:50:26 2023 GMT
          Subject: CN=micvs_server
          Subject Public Key Info:
              Public Key Algorithm: rsaEncryption
                  Public-Key: (2048 bit)
                  Modulus:
                      00:d8:2e:eb:0a:e8:e1:9f:87:b8:79:61:ab:96:75:
                      be:87:8e:33:74:80:d0:0b:aa:8e:7d:56:39:04:e4:
                      89:d4:38:a9:77:fc:c0:60:27:f7:1c:0f:cc:cb:30:
                      2f:c9:92:ed:06:06:c7:b3:5b:9e:fc:c3:f5:ca:8a:
                      d0:8d:ff:28:e4:29:03:41:5b:fd:8b:97:f9:d1:17:
                      2f:ce:37:8e:36:dd:d9:e0:94:7e:a6:16:9f:2e:98:
                      13:13:20:05:fa:3a:1a:17:55:87:0b:68:51:4c:7f:
                      64:32:13:94:5e:31:3a:12:5e:02:33:1e:67:99:a7:
                      0f:ee:2e:a9:5c:0d:d4:31:e4:8b:8f:ea:70:3d:1b:
                      d9:81:5d:2a:5f:6f:1c:d2:89:f9:e7:85:01:ee:6d:
                      ec:64:4e:5d:1b:91:e7:f7:98:eb:24:85:fe:bc:7c:
                      28:3a:2f:8a:ca:bf:50:53:db:66:59:4a:fa:2e:eb:
                      1f:81:f5:64:86:f0:1e:a3:87:52:95:59:6d:39:5d:
                      0b:71:bc:48:70:d2:cc:7f:bd:0c:2e:2d:0e:7a:0b:
                      01:3e:1b:63:3d:d8:af:8c:42:50:be:aa:6e:11:a9:
                      0f:10:71:38:22:95:42:83:d6:02:c3:2d:b2:83:01:
                      11:18:c9:b9:eb:28:72:b6:72:fe:e0:2c:98:0c:01:
                      8a:9d
                  Exponent: 65537 (0x10001)
          X509v3 extensions:
              X509v3 Basic Constraints: 
                  CA:FALSE
              X509v3 Subject Key Identifier: 
                  5C:E3:7F:82:65:F7:D7:A6:09:05:4F:88:09:E9:C2:75:5F:5F:B4:CE
              X509v3 Authority Key Identifier: 
                  keyid:51:93:EB:47:9A:69:76:9B:5D:46:5F:EC:C6:8D:50:2D:18:6D:8D:11
                  DirName:/CN=micvs
                  serial:8F:B4:36:E7:E5:0C:1A:B6

              X509v3 Extended Key Usage: 
                  TLS Web Server Authentication
              X509v3 Key Usage: 
                  Digital Signature, Key Encipherment
              X509v3 Subject Alternative Name: 
                  DNS:micvs_server
      Signature Algorithm: sha256WithRSAEncryption
           64:e1:1c:b9:c4:58:1b:69:93:d6:be:38:39:b1:ca:c1:41:89:
           e4:12:16:b8:b7:66:6a:15:1e:75:41:d9:93:af:d2:ff:d0:78:
           47:0a:cd:3d:de:cf:fc:26:f7:d1:76:6c:fc:b4:9c:78:53:b9:
           02:0a:35:fd:83:78:4e:ef:b4:ba:60:93:b9:64:b6:fb:25:9c:
           2a:53:ec:ec:b4:6d:a5:5e:15:46:c1:c1:6f:2c:99:cf:36:91:
           10:07:05:27:91:ff:20:3e:29:8f:20:b3:58:34:04:b6:d0:b1:
           e9:a3:27:d8:81:77:cf:f0:19:8e:5c:a1:5e:39:d1:a3:ba:e4:
           bf:9e:0d:57:a3:6c:b4:b0:10:81:29:08:d0:55:35:b8:72:b9:
           b4:2a:04:18:99:e2:08:70:6b:1f:fc:ee:2f:94:4c:59:0c:e9:
           66:1a:fa:a5:e6:6f:a2:f3:66:18:22:da:17:a2:a8:76:01:06:
           98:f7:a2:8a:b5:1e:ca:b0:ae:6f:eb:f4:1a:6e:e2:89:cd:87:
           39:67:28:79:f6:f4:86:7d:de:5e:b1:10:3c:a7:72:69:fd:58:
           9d:1e:9f:54:e2:06:3c:a7:39:4c:a5:d0:4b:12:15:8a:c7:95:
           06:41:33:f4:74:0d:68:7c:5e:f5:67:f2:d1:cc:fc:e3:aa:14:
           49:09:5a:7b
  [root@t2 openvpn]# ls
  ca.crt  ccd  checkpsw.sh  client  dh.pem  easy-rsa  ipp.txt  openvpn-password.log  psw-file  server  server.conf  server_crt  server.crt  server.key
  [root@t2 openvpn]# mkdir cert_2023-208
  [root@t2 openvpn]# ls
  ca.crt  cert_2023-208  client  easy-rsa  openvpn-password.log  server       server_crt  server.key
  ccd     checkpsw.sh    dh.pem  ipp.txt   psw-file              server.conf  server.crt
  [root@t2 openvpn]# cd cert_2023-208/
  [root@t2 cert_2023-208]# ls
  [root@t2 cert_2023-208]# cd ..
  [root@t2 openvpn]# ls
  ca.crt  cert_2023-208  client  easy-rsa  openvpn-password.log  server       server_crt  server.key
  ccd     checkpsw.sh    dh.pem  ipp.txt   psw-file              server.conf  server.crt
  [root@t2 openvpn]# cd easy-rsa/
  [root@t2 easy-rsa]# ls
  build      COPYING.md  doc       KNOWN_ISSUES  op_test.orig  README.md             release-keys  wop_test.sh
  ChangeLog  distro      easyrsa3  Licensing     op_test.sh    README.quickstart.md  wop_test.bat
  [root@t2 easy-rsa]# cd ..
  [root@t2 openvpn]# ls
  ca.crt  cert_2023-208  client  easy-rsa  openvpn-password.log  server       server_crt  server.key
  ccd     checkpsw.sh    dh.pem  ipp.txt   psw-file              server.conf  server.crt
  [root@t2 openvpn]# cd cert_2023-208/
  [root@t2 cert_2023-208]# ls
  [root@t2 cert_2023-208]# cp -r /etc/open
  openldap/ openvpn/  
  [root@t2 cert_2023-208]# cp -r /etc/open
  openldap/ openvpn/  
  [root@t2 cert_2023-208]# cp -r /etc/openvpn/easy-rsa ./
  [root@t2 cert_2023-208]# ls
  easy-rsa
  [root@t2 cert_2023-208]# cd easy-rsa/
  [root@t2 easy-rsa]# ls
  build      COPYING.md  doc       KNOWN_ISSUES  op_test.orig  README.md             release-keys  wop_test.sh
  ChangeLog  distro      easyrsa3  Licensing     op_test.sh    README.quickstart.md  wop_test.bat
  [root@t2 easy-rsa]# ./easyrsa3/
  easyrsa     pki/        x509-types/ 
  [root@t2 easy-rsa]# cd easyrsa3/
  [root@t2 easyrsa3]# ls
  easyrsa  openssl-easyrsa.cnf  pki  vars  vars.example  x509-types
  [root@t2 easyrsa3]# rm -rf pki/
  [root@t2 easyrsa3]# ls
  easyrsa  openssl-easyrsa.cnf  vars  vars.example  x509-types
  [root@t2 easyrsa3]# ./easyrsa init-pki

  Note: using Easy-RSA configuration from: /etc/openvpn/cert_2023-208/easy-rsa/easyrsa3/vars

  init-pki complete; you may now create a CA or requests.
  Your newly created PKI dir is: /etc/openvpn/cert_2023-208/easy-rsa/easyrsa3/pki


  [root@t2 easyrsa3]# ls
  easyrsa  openssl-easyrsa.cnf  pki  vars  vars.example  x509-types
  [root@t2 easyrsa3]# ./easyrsa --batch build-ca nopass
  Using SSL: openssl OpenSSL 1.0.2k-fips  26 Jan 2017
  Generating RSA private key, 2048 bit long modulus
  ...................+++
  .......................................................+++
  e is 65537 (0x10001)

  [root@t2 easyrsa3]# EASYRSA_CERT_EXPIRE=3650 ./easyrsa build-server-full server nopassEASYRSA_CERT_EXPIRE=3650 ./easyrsa build-server-full server nopass^C
  [root@t2 easyrsa3]# EASYRSA_CERT_EXPIRE=3650 ./easyrsa build-server-full server nopass

  Note: using Easy-RSA configuration from: /etc/openvpn/cert_2023-208/easy-rsa/easyrsa3/vars
  Using SSL: openssl OpenSSL 1.0.2k-fips  26 Jan 2017
  Generating a 2048 bit RSA private key
  ............+++
  ................................................................+++
  writing new private key to '/etc/openvpn/cert_2023-208/easy-rsa/easyrsa3/pki/easy-rsa-14928.jQN2WT/tmp.EkeT0G'
  -----
  Using configuration from /etc/openvpn/cert_2023-208/easy-rsa/easyrsa3/pki/easy-rsa-14928.jQN2WT/tmp.DmWly1
  Check that the request matches the signature
  Signature ok
  The Subject's Distinguished Name is as follows
  commonName            :ASN.1 12:'server'
  Certificate is to be certified until Mar 14 07:15:04 2033 GMT (3650 days)

  Write out database with 1 new entries
  Data Base Updated

  [root@t2 easyrsa3]# EASYRSA_CERT_EXPIRE=3650 ./easyrsa build-client-full client nopass

  Note: using Easy-RSA configuration from: /etc/openvpn/cert_2023-208/easy-rsa/easyrsa3/vars
  Using SSL: openssl OpenSSL 1.0.2k-fips  26 Jan 2017
  Generating a 2048 bit RSA private key
  ......+++
  .................................+++
  writing new private key to '/etc/openvpn/cert_2023-208/easy-rsa/easyrsa3/pki/easy-rsa-15028.kmnt4D/tmp.rZUlnF'
  -----
  Using configuration from /etc/openvpn/cert_2023-208/easy-rsa/easyrsa3/pki/easy-rsa-15028.kmnt4D/tmp.cqwOFp
  Check that the request matches the signature
  Signature ok
  The Subject's Distinguished Name is as follows
  commonName            :ASN.1 12:'client'
  Certificate is to be certified until Mar 14 07:15:19 2033 GMT (3650 days)

  Write out database with 1 new entries
  Data Base Updated

  [root@t2 easyrsa3]# EASYRSA_CRL_DAYS=3650 ./easyrsa gen-crl

  Note: using Easy-RSA configuration from: /etc/openvpn/cert_2023-208/easy-rsa/easyrsa3/vars
  Using SSL: openssl OpenSSL 1.0.2k-fips  26 Jan 2017
  Using configuration from /etc/openvpn/cert_2023-208/easy-rsa/easyrsa3/pki/easy-rsa-15103.TnYFbh/tmp.RbHEgL

  An updated CRL has been created.
  CRL file: /etc/openvpn/cert_2023-208/easy-rsa/easyrsa3/pki/crl.pem


  [root@t2 easyrsa3]# cp pki/ca.crt pki/private/ca.key pki/issued/server.crt pki/private/server.key pki/crl.pem ../../
  [root@t2 easyrsa3]# ls
  easyrsa  openssl-easyrsa.cnf  pki  vars  vars.example  x509-types
  [root@t2 easyrsa3]# cd ..
  [root@t2 easy-rsa]# ls
  build      COPYING.md  doc       KNOWN_ISSUES  op_test.orig  README.md             release-keys  wop_test.sh
  ChangeLog  distro      easyrsa3  Licensing     op_test.sh    README.quickstart.md  wop_test.bat
  [root@t2 easy-rsa]# cd ..
  [root@t2 cert_2023-208]# ls
  ca.crt  ca.key  crl.pem  easy-rsa  server.crt  server.key
  [root@t2 cert_2023-208]# chown nobody:nobody crl.pem 
  [root@t2 cert_2023-208]#  openssl verify -CAfile ca.crt -purpose sslserver server.crt
  server.crt: OK
  [root@t2 cert_2023-208]# cd ..
  [root@t2 openvpn]# ls
  ca.crt  cert_2023-208  client  easy-rsa  openvpn-password.log  server       server_crt  server.key
  ccd     checkpsw.sh    dh.pem  ipp.txt   psw-file              server.conf  server.crt
  [root@t2 openvpn]# cd  client/
  [root@t2 client]# ls
  [root@t2 client]# cd ..
  [root@t2 openvpn]# ls
  ca.crt  cert_2023-208  client  easy-rsa  openvpn-password.log  server       server_crt  server.key
  ccd     checkpsw.sh    dh.pem  ipp.txt   psw-file              server.conf  server.crt
  [root@t2 openvpn]# cd server
  [root@t2 server]# l
  -bash: l: command not found
  [root@t2 server]# cd ..
  [root@t2 openvpn]# ls
  ca.crt  cert_2023-208  client  easy-rsa  openvpn-password.log  server       server_crt  server.key
  ccd     checkpsw.sh    dh.pem  ipp.txt   psw-file              server.conf  server.crt
  [root@t2 openvpn]# cd server
  [root@t2 server]# ls
  [root@t2 server]# cd ..
  [root@t2 openvpn]# ls
  ca.crt  cert_2023-208  client  easy-rsa  openvpn-password.log  server       server_crt  server.key
  ccd     checkpsw.sh    dh.pem  ipp.txt   psw-file              server.conf  server.crt
  [root@t2 openvpn]# cd server
  [root@t2 server]# cd ..
  [root@t2 openvpn]# cd server_crt/
  [root@t2 server_crt]# ls
  ca.crt  dh.pem  server.crt  server.key
  [root@t2 server_crt]# cd ..
  [root@t2 openvpn]# ls
  ca.crt  cert_2023-208  client  easy-rsa  openvpn-password.log  server       server_crt  server.key
  ccd     checkpsw.sh    dh.pem  ipp.txt   psw-file              server.conf  server.crt
  [root@t2 openvpn]# cd cert_2023-208/
  [root@t2 cert_2023-208]# ls
  ca.crt  ca.key  crl.pem  easy-rsa  server.crt  server.key
  [root@t2 cert_2023-208]# cd ..
  [root@t2 openvpn]# ls
  ca.crt  cert_2023-208  client  easy-rsa  openvpn-password.log  server       server_crt  server.key
  ccd     checkpsw.sh    dh.pem  ipp.txt   psw-file              server.conf  server.crt
  [root@t2 openvpn]# cd server_crt/
  [root@t2 server_crt]# ls
  ca.crt  dh.pem  server.crt  server.key
  [root@t2 server_crt]# ls
  ca.crt  dh.pem  server.crt  server.key
  [root@t2 server_crt]# zip ./* Expires_2023年3月17日.zip
      zip warning: missing end signature--probably not a zip file (did you
      zip warning: remember to use binary mode when you transferred it?)
      zip warning: (if you are trying to read a damaged archive try -F)

  zip error: Zip file structure invalid (./ca.crt)
  [root@t2 server_crt]# ls
  ca.crt  dh.pem  server.crt  server.key
  [root@t2 server_crt]# zip  Expires_2023年3月17日.zip ./*
    adding: ca.crt (deflated 27%)
    adding: dh.pem (deflated 18%)
    adding: server.crt (deflated 45%)
    adding: server.key (deflated 23%)
  [root@t2 server_crt]# LS
  -bash: LS: command not found
  [root@t2 server_crt]# LS
  -bash: LS: command not found
  [root@t2 server_crt]# ls
  ca.crt  dh.pem  Expires_2023年3月17日.zip  server.crt  server.key
  [root@t2 server_crt]# ls
  ca.crt  dh.pem  Expires_2023年3月17日.zip  server.crt  server.key
  [root@t2 server_crt]# cd ..
  [root@t2 openvpn]# ls
  ca.crt  cert_2023-208  client  easy-rsa  openvpn-password.log  server       server_crt  server.key
  ccd     checkpsw.sh    dh.pem  ipp.txt   psw-file              server.conf  server.crt
  [root@t2 openvpn]# cd cert_2023-208/
  [root@t2 cert_2023-208]# ;s
  -bash: syntax error near unexpected token `;'
  [root@t2 cert_2023-208]# ls
  ca.crt  ca.key  crl.pem  easy-rsa  server.crt  server.key
  [root@t2 cert_2023-208]# cp ca.crt ca.key crl.pem server.crt server.key /etc/openvpn/server
  server/      server.conf  server_crt/  server.crt   server.key   
  [root@t2 cert_2023-208]# cp ca.crt ca.key crl.pem server.crt server.key /etc/openvpn/server_crt/
  cp: overwrite ‘/etc/openvpn/server_crt/ca.crt’? y
  cp: overwrite ‘/etc/openvpn/server_crt/server.crt’? y
  cp: overwrite ‘/etc/openvpn/server_crt/server.key’? y
  [root@t2 cert_2023-208]# ls
  ca.crt  ca.key  crl.pem  easy-rsa  server.crt  server.key
  [root@t2 cert_2023-208]# cd ..
  [root@t2 openvpn]# ls
  ca.crt  cert_2023-208  client  easy-rsa  openvpn-password.log  server       server_crt  server.key
  ccd     checkpsw.sh    dh.pem  ipp.txt   psw-file              server.conf  server.crt
  [root@t2 openvpn]# cd server_crt/
  [root@t2 server_crt]# ls
  ca.crt  ca.key  crl.pem  dh.pem  Expires_2023年3月17日.zip  server.crt  server.key
  [root@t2 server_crt]# cd ..
  [root@t2 openvpn]# ls
  ca.crt  cert_2023-208  client  easy-rsa  openvpn-password.log  server       server_crt  server.key
  ccd     checkpsw.sh    dh.pem  ipp.txt   psw-file              server.conf  server.crt
  [root@t2 openvpn]# vim server.conf 
  [root@t2 openvpn]# ls
  ca.crt  cert_2023-208  client  easy-rsa  openvpn-password.log  server       server_crt  server.key
  ccd     checkpsw.sh    dh.pem  ipp.txt   psw-file              server.conf  server.crt
  [root@t2 openvpn]# cd ..
  [root@t2 etc]# ls
  adjtime            cron.weekly              gdbinit.d    issue.net       motd                       polkit-1          rsyslog.conf         sysctl.d
  adjtime.rpmsave    crypttab                 GeoIP.conf   kernel          mtab                       popt.d            rsyslog.d            systemd
  aliases            csh.cshrc                gnupg        krb5.conf       my.cnf                     postfix           rwtab                system-lsb
  aliases.db         csh.login                grafana      krb5.conf.d     my.cnf.d                   ppp               rwtab.d              system-release
  alinux-release     dbus-1                   GREP_COLORS  ld.so.cache     netconfig                  prelink.conf.d    sasl2                telegraf
  alternatives       default                  groff        ld.so.conf      NetworkManager             printcap          securetty            terminfo
  anacrontab         depmod.d                 group        ld.so.conf.d    networks                   profile           security             tmpfiles.d
  anolis-release     dhcp                     group-       libaudit.conf   nfs.conf                   profile.d         selinux              tuned
  asound.conf        DIR_COLORS               grub2.cfg    libnl           nfsmount.conf              protocols         services             udev
  at.deny            DIR_COLORS.256color      grub.d       libreport       nscd.conf                  python            sestatus.conf        update-motd.d
  audisp             DIR_COLORS.lightbgcolor  gshadow      libuser.conf    nsswitch.conf              rc0.d             shadow               vconsole.conf
  audit              docker                   gshadow-     locale.conf     nsswitch.conf.bak          rc1.d             shadow-              vimrc
  bash_completion.d  dracut.conf              gss          localtime       nsswitch.conf.rpmnew       rc2.d             shells               virc
  bashrc             dracut.conf.d            gssproxy     login.defs      ntp.conf                   rc3.d             skel                 wgetrc
  binfmt.d           e2fsck.conf              host.conf    logrotate.conf  oci-register-machine.conf  rc4.d             ssh                  wireguard
  centos-release     environment              hostname     logrotate.d     oci-umount                 rc5.d             ssl                  wpa_supplicant
  chkconfig.d        ethertypes               hosts        lsb-release.d   oci-umount.conf            rc6.d             statetab             X11
  chrony.conf        exports                  hosts.allow  lvm             openldap                   rc.d              statetab.d           xdg
  chrony.keys        exports.d                hosts.deny   machine-id      openvpn                    rc.local          subgid               xinetd.d
  cloud              favicon.png              idmapd.conf  magic           opt                        redhat-release    subuid               yum
  containers         filesystems              image-id     mailcap         os-release                 request-key.conf  sudo.conf            yum.conf
  cron.d             firewalld                influxdb     mail.rc         pam.d                      request-key.d     sudoers              yum.repos.d
  cron.daily         fonts                    init.d       man_db.conf     passwd                     resolv.conf       sudoers.d
  cron.deny          fstab                    inittab      mime.types      passwd-                    resolv.conf.save  sudo-ldap.conf
  cron.hourly        fuse.conf                inputrc      mke2fs.conf     pkcs11                     rpc               sysconfig
  cron.monthly       gcrypt                   iproute2     modprobe.d      pki                        rpm               sysctl.conf
  crontab            gdbinit                  issue        modules-load.d  pm                         rsyncd.conf       sysctl.conf.rpmsave
  [root@t2 etc]# cd openvpn/
  [root@t2 openvpn]# ls
  ca.crt  cert_2023-208  client  easy-rsa  openvpn-password.log  server       server_crt  server.key
  ccd     checkpsw.sh    dh.pem  ipp.txt   psw-file              server.conf  server.crt
  [root@t2 openvpn]# cd cert_2023-208/
  [root@t2 cert_2023-208]# ls
  ca.crt  ca.key  crl.pem  easy-rsa  server.crt  server.key
  [root@t2 cert_2023-208]# cp ca.crt ca.key crl.pem server.crt server.key /etc/openvpn/
  cp: overwrite ‘/etc/openvpn/ca.crt’? y
  cp: overwrite ‘/etc/openvpn/server.crt’? y
  cp: overwrite ‘/etc/openvpn/server.key’? y
  [root@t2 cert_2023-208]# cd ..
  [root@t2 openvpn]# ls
  ca.crt  ccd            checkpsw.sh  crl.pem  easy-rsa  openvpn-password.log  server       server_crt  server.key
  ca.key  cert_2023-208  client       dh.pem   ipp.txt   psw-file              server.conf  server.crt
  [root@t2 openvpn]# vim server.conf 
  [root@t2 openvpn]# systemctl restart openvpn
  Failed to restart openvpn.service: Unit not found.
  [root@t2 openvpn]# systemctl status openvpn
  Unit openvpn.service could not be found.
  [root@t2 openvpn]# systemctl status openvpn@server.service 
  ● openvpn@server.service - OpenVPN Robust And Highly Flexible Tunneling Application On server
     Loaded: loaded (/usr/lib/systemd/system/openvpn@.service; enabled; vendor preset: disabled)
     Active: active (running) since Fri 2022-11-25 14:59:33 CST; 3 months 20 days ago
   Main PID: 24698 (openvpn)
     Status: "Initialization Sequence Completed"
     CGroup: /system.slice/system-openvpn.slice/openvpn@server.service
             └─24698 /usr/sbin/openvpn --cd /etc/openvpn/ --config server.conf
  ^C
  [root@t2 openvpn]# systemctl restart openvpn@server.service
  [root@t2 openvpn]# systemctl status openvpn@server.service 
  ● openvpn@server.service - OpenVPN Robust And Highly Flexible Tunneling Application On server
     Loaded: loaded (/usr/lib/systemd/system/openvpn@.service; enabled; vendor preset: disabled)
     Active: active (running) since Fri 2023-03-17 15:25:09 CST; 1s ago
   Main PID: 15869 (openvpn)
     Status: "Initialization Sequence Completed"
     CGroup: /system.slice/system-openvpn.slice/openvpn@server.service
             └─15869 /usr/sbin/openvpn --cd /etc/openvpn/ --config server.conf

  Mar 17 15:25:09 t2 systemd[1]: Starting OpenVPN Robust And Highly Flexible Tunneling Application On server...
  Mar 17 15:25:09 t2 openvpn[15869]: Fri Mar 17 15:25:09 2023 DEPRECATED OPTION: --client-cert-not-required, use --verify-client-cert instead
  Mar 17 15:25:09 t2 systemd[1]: Started OpenVPN Robust And Highly Flexible Tunneling Application On server.
  [root@t2 openvpn]# systemctl stop openvpn@server.service 
  [root@t2 openvpn]# systemctl status openvpn@server.service 
  ● openvpn@server.service - OpenVPN Robust And Highly Flexible Tunneling Application On server
     Loaded: loaded (/usr/lib/systemd/system/openvpn@.service; enabled; vendor preset: disabled)
     Active: inactive (dead) since Fri 2023-03-17 15:25:35 CST; 2s ago
    Process: 15869 ExecStart=/usr/sbin/openvpn --cd /etc/openvpn/ --config %i.conf (code=exited, status=0/SUCCESS)
   Main PID: 15869 (code=exited, status=0/SUCCESS)
     Status: "Initialization Sequence Completed"

  Mar 17 15:25:09 t2 systemd[1]: Starting OpenVPN Robust And Highly Flexible Tunneling Application On server...
  Mar 17 15:25:09 t2 openvpn[15869]: Fri Mar 17 15:25:09 2023 DEPRECATED OPTION: --client-cert-not-required, use --verify-client-cert instead
  Mar 17 15:25:09 t2 systemd[1]: Started OpenVPN Robust And Highly Flexible Tunneling Application On server.
  Mar 17 15:25:35 t2 systemd[1]: Stopping OpenVPN Robust And Highly Flexible Tunneling Application On server...
  Mar 17 15:25:35 t2 systemd[1]: Stopped OpenVPN Robust And Highly Flexible Tunneling Application On server.
  [root@t2 openvpn]# systemctl start openvpn@server.service
  [root@t2 openvpn]# systemctl status openvpn@server.service 
  ● openvpn@server.service - OpenVPN Robust And Highly Flexible Tunneling Application On server
     Loaded: loaded (/usr/lib/systemd/system/openvpn@.service; enabled; vendor preset: disabled)
     Active: active (running) since Fri 2023-03-17 15:25:43 CST; 29s ago
   Main PID: 15925 (openvpn)
     Status: "Initialization Sequence Completed"
     CGroup: /system.slice/system-openvpn.slice/openvpn@server.service
             └─15925 /usr/sbin/openvpn --cd /etc/openvpn/ --config server.conf

  Mar 17 15:25:43 t2 systemd[1]: Starting OpenVPN Robust And Highly Flexible Tunneling Application On server...
  Mar 17 15:25:43 t2 openvpn[15925]: Fri Mar 17 15:25:43 2023 DEPRECATED OPTION: --client-cert-not-required, use --verify-client-cert instead
  Mar 17 15:25:43 t2 systemd[1]: Started OpenVPN Robust And Highly Flexible Tunneling Application On server.
  [root@t2 openvpn]# cd /var/lo
  local/ lock/  log/   
  [root@t2 openvpn]# cd /var/log/
  anaconda/ audit/    chrony/   grafana/  influxdb/ journal/  openvpn/  rhsm/     sa/       telegraf/ tuned/    
  [root@t2 openvpn]# cd /var/log/openvpn/
  [root@t2 openvpn]# ls
  openvpn.log
  [root@t2 openvpn]# tail -f openvpn.log 
  Fri Mar 17 15:26:21 2023 58.33.81.92:49850 SIGUSR1[soft,connection-reset] received, client-instance restarting
  Fri Mar 17 15:26:21 2023 58.33.81.92:63785 Connection reset, restarting [0]
  Fri Mar 17 15:26:21 2023 58.33.81.92:63785 SIGUSR1[soft,connection-reset] received, client-instance restarting
  Fri Mar 17 15:26:22 2023 58.33.81.92:57615 TLS: Initial packet from [AF_INET]58.33.81.92:57615, sid=e879fe27 d222dded
  Fri Mar 17 15:26:22 2023 58.33.81.92:57615 Connection reset, restarting [0]
  Fri Mar 17 15:26:22 2023 58.33.81.92:57615 SIGUSR1[soft,connection-reset] received, client-instance restarting
  Fri Mar 17 15:26:22 2023 TCP connection established with [AF_INET]101.85.37.188:13366
  Fri Mar 17 15:26:22 2023 101.85.37.188:13366 TLS: Initial packet from [AF_INET]101.85.37.188:13366, sid=37c74b71 7dbb2f42
  Fri Mar 17 15:26:22 2023 101.85.37.188:13366 Connection reset, restarting [0]
  Fri Mar 17 15:26:22 2023 101.85.37.188:13366 SIGUSR1[soft,connection-reset] received, client-instance restarting
  Fri Mar 17 15:26:37 2023 TCP connection established with [AF_INET]58.33.81.92:49851
  Fri Mar 17 15:26:37 2023 58.33.81.92:49851 TCP connection established with [AF_INET]58.33.81.92:63787
  Fri Mar 17 15:26:38 2023 58.33.81.92:63787 TCP connection established with [AF_INET]58.33.81.92:57617
  Fri Mar 17 15:26:38 2023 58.33.81.92:49851 TLS: Initial packet from [AF_INET]58.33.81.92:49851, sid=64e00504 c4fb8b9b
  Fri Mar 17 15:26:38 2023 58.33.81.92:49851 Connection reset, restarting [0]
  Fri Mar 17 15:26:38 2023 58.33.81.92:49851 SIGUSR1[soft,connection-reset] received, client-instance restarting
  Fri Mar 17 15:26:38 2023 58.33.81.92:63787 TLS: Initial packet from [AF_INET]58.33.81.92:63787, sid=0fada2a3 e7c94ebb
  Fri Mar 17 15:26:38 2023 58.33.81.92:63787 Connection reset, restarting [0]
  Fri Mar 17 15:26:38 2023 58.33.81.92:63787 SIGUSR1[soft,connection-reset] received, client-instance restarting
  Fri Mar 17 15:26:39 2023 58.33.81.92:57617 TLS: Initial packet from [AF_INET]58.33.81.92:57617, sid=76ba2f59 a275c4f9
  Fri Mar 17 15:26:39 2023 58.33.81.92:57617 Connection reset, restarting [0]
  Fri Mar 17 15:26:39 2023 58.33.81.92:57617 SIGUSR1[soft,connection-reset] received, client-instance restarting
  Fri Mar 17 15:26:41 2023 TCP connection established with [AF_INET]101.85.37.188:13379
  Fri Mar 17 15:26:41 2023 101.85.37.188:13379 TLS: Initial packet from [AF_INET]101.85.37.188:13379, sid=7ec36c11 e1ee83e9
  Fri Mar 17 15:26:41 2023 101.85.37.188:13379 Connection reset, restarting [0]
  Fri Mar 17 15:26:41 2023 101.85.37.188:13379 SIGUSR1[soft,connection-reset] received, client-instance restarting
  ^C
  [root@t2 openvpn]# sudo reboot
  Connection to 172.17.21.25 closed by remote host.
  Connection to 172.17.21.25 closed.
  [yangchaojie@t3 ~]$ ssh -p 22 root@172.17.21.25
  root@172.17.21.25's password: 
  Last login: Fri Mar 17 15:08:33 2023 from 172.17.21.26

  Welcome to Alibaba Cloud Elastic Compute Service !

  [root@t2 ~]# ls
  centos7-vpn.sh  client.tar.gz   epel-release-latest-7.noarch.rpm  influxdb-1.7.6.x86_64.rpm  master.zip  telegraf-1.15.3-1.x86_64.rpm
  client          controlsfx.jar  grafana-7.1.0-1.x86_64.rpm        influxdb-1.8.3.x86_64.rpm  nginx
  [root@t2 ~]# cd /etc/openvpn/
  [root@t2 openvpn]# ls
  ca.crt  ccd            checkpsw.sh  crl.pem  easy-rsa  openvpn-password.log  server       server_crt  server.key
  ca.key  cert_2023-208  client       dh.pem   ipp.txt   psw-file              server.conf  server.crt
  [root@t2 openvpn]# vim server.conf 
  [root@t2 openvpn]# ls
  ca.crt  ccd            checkpsw.sh  crl.pem  easy-rsa  openvpn-password.log  server       server_crt  server.key
  ca.key  cert_2023-208  client       dh.pem   ipp.txt   psw-file              server.conf  server.crt
  [root@t2 openvpn]# rm -rf server.key ca.key ca.crt dh.pem 
  [root@t2 openvpn]# ls
  ccd  cert_2023-208  checkpsw.sh  client  crl.pem  easy-rsa  ipp.txt  openvpn-password.log  psw-file  server  server.conf  server_crt  server.crt
  [root@t2 openvpn]# cd easy-rsa/
  [root@t2 easy-rsa]# ls
  build      COPYING.md  doc       KNOWN_ISSUES  op_test.orig  README.md             release-keys  wop_test.sh
  ChangeLog  distro      easyrsa3  Licensing     op_test.sh    README.quickstart.md  wop_test.bat
  [root@t2 easy-rsa]# cd ..
  [root@t2 openvpn]# ls
  ccd  cert_2023-208  checkpsw.sh  client  crl.pem  easy-rsa  ipp.txt  openvpn-password.log  psw-file  server  server.conf  server_crt  server.crt
  [root@t2 openvpn]# rm -rf easy-rsa/
  [root@t2 openvpn]# ;s
  -bash: syntax error near unexpected token `;'
  [root@t2 openvpn]# ls
  ccd  cert_2023-208  checkpsw.sh  client  crl.pem  ipp.txt  openvpn-password.log  psw-file  server  server.conf  server_crt  server.crt
  [root@t2 openvpn]# cp cert_2023-208/ ./
  cp: omitting directory ‘cert_2023-208/’
  [root@t2 openvpn]# cp cert_2023-208/* ./
  cp: overwrite ‘./crl.pem’? y
  cp: omitting directory ‘cert_2023-208/easy-rsa’
  cp: overwrite ‘./server.crt’? ^C
  [root@t2 openvpn]# cp -y cert_2023-208/* ./
  cp: invalid option -- 'y'
  Try 'cp --help' for more information.
  [root@t2 openvpn]# cp y cert_2023-208/* ./
  cp: cannot stat ‘y’: No such file or directory
  cp: overwrite ‘./ca.crt’? ^C
  [root@t2 openvpn]# ls
  ca.crt  ca.key  ccd  cert_2023-208  checkpsw.sh  client  crl.pem  ipp.txt  openvpn-password.log  psw-file  server  server.conf  server_crt  server.crt
  [root@t2 openvpn]# \cp  cert_2023-208/* ./
  cp: omitting directory ‘cert_2023-208/easy-rsa’
  [root@t2 openvpn]# ls
  ca.crt  ccd            checkpsw.sh  crl.pem  openvpn-password.log  server       server_crt  server.key
  ca.key  cert_2023-208  client       ipp.txt  psw-file              server.conf  server.crt
  [root@t2 openvpn]# ls
  ca.crt  ccd            checkpsw.sh  crl.pem  openvpn-password.log  server       server_crt  server.key
  ca.key  cert_2023-208  client       ipp.txt  psw-file              server.conf  server.crt
  [root@t2 openvpn]# cd cert_2023-208/
  [root@t2 cert_2023-208]# ls
  ca.crt  ca.key  crl.pem  easy-rsa  server.crt  server.key
  [root@t2 cert_2023-208]# cd easy-rsa/
  [root@t2 easy-rsa]# ls
  build      COPYING.md  doc       KNOWN_ISSUES  op_test.orig  README.md             release-keys  wop_test.sh
  ChangeLog  distro      easyrsa3  Licensing     op_test.sh    README.quickstart.md  wop_test.bat
  [root@t2 easy-rsa]# cd easyrsa3/
  [root@t2 easyrsa3]# ls
  easyrsa  openssl-easyrsa.cnf  pki  vars  vars.example  x509-types
  [root@t2 easyrsa3]# cd pki/
  [root@t2 pki]# ls
  ca.crt           crl.pem    index.txt.attr      index.txt.old  openssl-easyrsa.cnf  renewed  revoked              serial
  certs_by_serial  index.txt  index.txt.attr.old  issued         private              reqs     safessl-easyrsa.cnf  serial.old
  [root@t2 pki]# cd ..
  [root@t2 easyrsa3]# ls
  easyrsa  openssl-easyrsa.cnf  pki  vars  vars.example  x509-types
  [root@t2 easyrsa3]# cd ..
  [root@t2 easy-rsa]# ls
  build      COPYING.md  doc       KNOWN_ISSUES  op_test.orig  README.md             release-keys  wop_test.sh
  ChangeLog  distro      easyrsa3  Licensing     op_test.sh    README.quickstart.md  wop_test.bat
  [root@t2 easy-rsa]# cd ..
  [root@t2 cert_2023-208]# ls
  ca.crt  ca.key  crl.pem  easy-rsa  server.crt  server.key
  [root@t2 cert_2023-208]# cd ..
  [root@t2 openvpn]# ls
  ca.crt  ccd            checkpsw.sh  crl.pem  openvpn-password.log  server       server_crt  server.key
  ca.key  cert_2023-208  client       ipp.txt  psw-file              server.conf  server.crt
  [root@t2 openvpn]# vim server.conf 
  [root@t2 openvpn]# vim server.conf 
  [root@t2 openvpn]# vim server.conf 
  [root@t2 openvpn]# systemctl restart openvpn
  openvpn@                openvpn-client@         openvpn-server@         openvpn@server.service  
  [root@t2 openvpn]# systemctl restart openvpn@
  openvpn@                openvpn@server.service  
  [root@t2 openvpn]# systemctl restart openvpn@server.service 
  [root@t2 openvpn]# ls
  ca.crt  ccd            checkpsw.sh  crl.pem  openvpn-password.log  server       server_crt  server.key
  ca.key  cert_2023-208  client       ipp.txt  psw-file              server.conf  server.crt
  [root@t2 openvpn]# sz ca.crt 

  [root@t2 openvpn]# ls
  ca.crt  ccd            checkpsw.sh  crl.pem  openvpn-password.log  server       server_crt  server.key
  ca.key  cert_2023-208  client       ipp.txt  psw-file              server.conf  server.crt
  [root@t2 openvpn]# cd /var/log/openvpn/
  [root@t2 openvpn]# ls
  openvpn.log
  [root@t2 openvpn]# tail -f openvpn.log 
  Fri Mar 17 15:34:10 2023 --pull-filter ignored for --mode server
  Fri Mar 17 15:34:10 2023 WARNING: POTENTIALLY DANGEROUS OPTION --verify-client-cert none|optional (or --client-cert-not-required) may accept clients which do not present a certificate
  Fri Mar 17 15:34:10 2023 OpenVPN 2.4.12 x86_64-redhat-linux-gnu [Fedora EPEL patched] [SSL (OpenSSL)] [LZO] [LZ4] [EPOLL] [PKCS11] [MH/PKTINFO] [AEAD] built on Mar 17 2022
  Fri Mar 17 15:34:10 2023 library versions: OpenSSL 1.0.2k-fips  26 Jan 2017, LZO 2.06
  Fri Mar 17 15:34:10 2023 WARNING: using --duplicate-cn and --client-config-dir together is probably not what you want
  Fri Mar 17 15:34:10 2023 WARNING: --ifconfig-pool-persist will not work with --duplicate-cn
  Fri Mar 17 15:34:10 2023 NOTE: the current --script-security setting may allow this configuration to call user-defined scripts
  Fri Mar 17 15:34:10 2023 OpenSSL: error:0906D06C:PEM routines:PEM_read_bio:no start line
  Fri Mar 17 15:34:10 2023 Cannot load DH parameters from /etc/openvpn/crl.pem
  Fri Mar 17 15:34:10 2023 Exiting due to fatal error
  c^H^C
  [root@t2 openvpn]# 
  [root@t2 openvpn]# ls
  openvpn.log
  [root@t2 openvpn]# cd ..
  [root@t2 log]# ls
  anaconda              cron                          grubby_prune_debug  maillog            messages-20230312   secure-20230312   wtmp
  audit                 cron-20230219                 influxdb            maillog-20230219   openvpn             spooler           yum.log
  boot.log              cron-20230226                 journal             maillog-20230226   openvpn-status.log  spooler-20230219  yum.log-20210101
  boot.log-20200711     cron-20230305                 kern                maillog-20230305   rhsm                spooler-20230226  yum.log-20220101
  btmp                  cron-20230312                 kern-20230219       maillog-20230312   sa                  spooler-20230305  yum.log-20230101
  btmp-20230301         ecs_network_optimization.log  kern-20230226       messages           secure              spooler-20230312
  chrony                firewalld                     kern-20230305       messages-20230219  secure-20230219     tallylog
  cloudinit-deploy.log  grafana                       kern-20230312       messages-20230226  secure-20230226     telegraf
  cloud-init.log        grubby                        lastlog             messages-20230305  secure-20230305     tuned
  [root@t2 log]# cd ..
  [root@t2 var]# ls
  adm  cache  db  empty  games  gopher  kerberos  lib  local  lock  log  mail  nis  opt  preserve  run  spool  tmp  yp
  [root@t2 var]# cd /etc/
  alternatives/      dbus-1/            groff/             logrotate.d/       pki/               rc4.d/             ssl/               wireguard/
  audisp/            default/           grub.d/            lsb-release.d/     pm/                rc5.d/             statetab.d/        wpa_supplicant/
  audit/             depmod.d/          gss/               lvm/               polkit-1/          rc6.d/             sudoers.d/         X11/
  bash_completion.d/ dhcp/              gssproxy/          modprobe.d/        popt.d/            rc.d/              sysconfig/         xdg/
  binfmt.d/          docker/            influxdb/          modules-load.d/    postfix/           request-key.d/     sysctl.d/          xinetd.d/
  chkconfig.d/       dracut.conf.d/     init.d/            my.cnf.d/          ppp/               rpm/               systemd/           yum/
  cloud/             exports.d/         iproute2/          NetworkManager/    prelink.conf.d/    rsyslog.d/         system-lsb/        yum.repos.d/
  containers/        firewalld/         .java/             oci-umount/        profile.d/         rwtab.d/           telegraf/          
  cron.d/            fonts/             kernel/            openldap/          python/            sasl2/             terminfo/          
  cron.daily/        gcrypt/            krb5.conf.d/       openvpn/           rc0.d/             security/          tmpfiles.d/        
  cron.hourly/       gdbinit.d/         ld.so.conf.d/      opt/               rc1.d/             selinux/           tuned/             
  cron.monthly/      gnupg/             libnl/             pam.d/             rc2.d/             skel/              udev/              
  cron.weekly/       grafana/           libreport/         pkcs11/            rc3.d/             ssh/               update-motd.d/     
  [root@t2 var]# cd /etc/
  alternatives/      dbus-1/            groff/             logrotate.d/       pki/               rc4.d/             ssl/               wireguard/
  audisp/            default/           grub.d/            lsb-release.d/     pm/                rc5.d/             statetab.d/        wpa_supplicant/
  audit/             depmod.d/          gss/               lvm/               polkit-1/          rc6.d/             sudoers.d/         X11/
  bash_completion.d/ dhcp/              gssproxy/          modprobe.d/        popt.d/            rc.d/              sysconfig/         xdg/
  binfmt.d/          docker/            influxdb/          modules-load.d/    postfix/           request-key.d/     sysctl.d/          xinetd.d/
  chkconfig.d/       dracut.conf.d/     init.d/            my.cnf.d/          ppp/               rpm/               systemd/           yum/
  cloud/             exports.d/         iproute2/          NetworkManager/    prelink.conf.d/    rsyslog.d/         system-lsb/        yum.repos.d/
  containers/        firewalld/         .java/             oci-umount/        profile.d/         rwtab.d/           telegraf/          
  cron.d/            fonts/             kernel/            openldap/          python/            sasl2/             terminfo/          
  cron.daily/        gcrypt/            krb5.conf.d/       openvpn/           rc0.d/             security/          tmpfiles.d/        
  cron.hourly/       gdbinit.d/         ld.so.conf.d/      opt/               rc1.d/             selinux/           tuned/             
  cron.monthly/      gnupg/             libnl/             pam.d/             rc2.d/             skel/              udev/              
  cron.weekly/       grafana/           libreport/         pkcs11/            rc3.d/             ssh/               update-motd.d/     
  [root@t2 var]# cd /etc/openvpn/
  [root@t2 openvpn]# ls
  ca.crt  ccd            checkpsw.sh  crl.pem  openvpn-password.log  server       server_crt  server.key
  ca.key  cert_2023-208  client       ipp.txt  psw-file              server.conf  server.crt
  [root@t2 openvpn]# vim server.conf 
  [root@t2 openvpn]# ls
  ca.crt  ccd            checkpsw.sh  crl.pem  openvpn-password.log  server       server_crt  server.key
  ca.key  cert_2023-208  client       ipp.txt  psw-file              server.conf  server.crt
  [root@t2 openvpn]# cd cert_2023-208/
  [root@t2 cert_2023-208]# ls
  ca.crt  ca.key  crl.pem  easy-rsa  server.crt  server.key
  [root@t2 cert_2023-208]# cd easy-rsa/
  [root@t2 easy-rsa]# ls
  build      COPYING.md  doc       KNOWN_ISSUES  op_test.orig  README.md             release-keys  wop_test.sh
  ChangeLog  distro      easyrsa3  Licensing     op_test.sh    README.quickstart.md  wop_test.bat
  [root@t2 easy-rsa]# cd easyrsa3/
  [root@t2 easyrsa3]# ls
  easyrsa  openssl-easyrsa.cnf  pki  vars  vars.example  x509-types
  [root@t2 easyrsa3]# cd pki/
  [root@t2 pki]# ls
  ca.crt           crl.pem    index.txt.attr      index.txt.old  openssl-easyrsa.cnf  renewed  revoked              serial
  certs_by_serial  index.txt  index.txt.attr.old  issued         private              reqs     safessl-easyrsa.cnf  serial.old
  [root@t2 pki]# cd private/
  [root@t2 private]# ls
  ca.key  client.key  server.key
  [root@t2 private]# cd ..
  [root@t2 pki]# ls
  ca.crt           crl.pem    index.txt.attr      index.txt.old  openssl-easyrsa.cnf  renewed  revoked              serial
  certs_by_serial  index.txt  index.txt.attr.old  issued         private              reqs     safessl-easyrsa.cnf  serial.old
  [root@t2 pki]# cd ..
  [root@t2 easyrsa3]# ls
  easyrsa  openssl-easyrsa.cnf  pki  vars  vars.example  x509-types
  [root@t2 easyrsa3]# cd x509-types/
  [root@t2 x509-types]# ls
  ca  client  code-signing  COMMON  email  kdc  server  serverClient
  [root@t2 x509-types]# cd ..
  [root@t2 easyrsa3]# ls
  easyrsa  openssl-easyrsa.cnf  pki  vars  vars.example  x509-types
  [root@t2 easyrsa3]# ./easyrsa gen-dh

  Note: using Easy-RSA configuration from: /etc/openvpn/cert_2023-208/easy-rsa/easyrsa3/vars
  Using SSL: openssl OpenSSL 1.0.2k-fips  26 Jan 2017
  Generating DH parameters, 2048 bit long safe prime, generator 2
  This is going to take a long time
  ..........................................................................................................................................................................................................................................................................................................................................................+.......................+...................................+.........................+...............................+.............................................................................................+.....................................................................................................................+........................................................................................................................................+..........................................................+.....................................+.....................................................................................+.................+......................................+...................+.............................................................+...............+..................................................................................................................................+..............................................................................................................................................................................................................................................................................................................................................................................+...............................................+....................................................................+...................................................+.....................................................................................................................................................................................................................................................................................+....+.............................................................+..........................+.............................................................................................................................................................................+...................................+................................................................................+.......................................................................................................................................................+..............................................................................................................................+................................................................................+...+.............................................................+..........................+.................................................................................................................................................................................................................................................................................................................+..........................................................+....+...................................................................................+........................+.......................................................+......................................................................................+..............+...............................................................+..........+..............................................................................................................................................................................................................................................................................................+..........................................................................................................................................................................+..................................................................................................................................................+.................+..............................+..............................................................+......................................................+............................................+.......................................+.....................................................................................................................+.........................................................................+.....................................................+........................................+................................+.............................................................................................................................................................................................................................................................................................................................................................................................................................................................+...........................................................................................................+..............................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................+........................+....................................................................................................................................................................................+....................................+..........+................................................................................................................................+......+.+....+...............................................................+..................................................................................................................................................................+..............+......................................................+..............+........+....+.....................................................+.................................................................................................................................+.......................+....................................................................................................................................................................................................................................................................................+.......+.............................................................................................+..................................................................................+............................................................................+......................................................................+..+..........................................................................................+......................................................................+...........................+.....................................................................................................................................+..............................................................................................................................................+..........................................+.........+..........................+........................................................................................................................................................+...................................................................................................................+...................+.................................................................................................................................................................................................................................................................................+............+..........................+............+...............................................................................................................+...........................................................................................................................................+............................................................................................................................................................................................................................................................................................................................++*++*

  DH parameters of size 2048 created at /etc/openvpn/cert_2023-208/easy-rsa/easyrsa3/pki/dh.pem


  [root@t2 easyrsa3]# ls
  easyrsa  openssl-easyrsa.cnf  pki  vars  vars.example  x509-types
  [root@t2 easyrsa3]# cd pki/
  [root@t2 pki]# ls
  ca.crt           crl.pem  index.txt       index.txt.attr.old  issued               private  reqs     safessl-easyrsa.cnf  serial.old
  certs_by_serial  dh.pem   index.txt.attr  index.txt.old       openssl-easyrsa.cnf  renewed  revoked  serial
  [root@t2 pki]# cp dh.pem /etc/openvpn/
  [root@t2 pki]# cd ../../../
  [root@t2 cert_2023-208]# ls
  ca.crt  ca.key  crl.pem  easy-rsa  server.crt  server.key
  [root@t2 cert_2023-208]# cd ..
  [root@t2 openvpn]# ls
  ca.crt  ccd            checkpsw.sh  crl.pem  ipp.txt               psw-file  server.conf  server.crt
  ca.key  cert_2023-208  client       dh.pem   openvpn-password.log  server    server_crt   server.key
  [root@t2 openvpn]# vim server.conf 
  [root@t2 openvpn]# systemctl restart openvpn@server.service 
  [root@t2 openvpn]# systemctl status openvpn@server.service 
  ● openvpn@server.service - OpenVPN Robust And Highly Flexible Tunneling Application On server
     Loaded: loaded (/usr/lib/systemd/system/openvpn@.service; enabled; vendor preset: disabled)
     Active: active (running) since Fri 2023-03-17 15:44:05 CST; 8s ago
   Main PID: 2483 (openvpn)
     Status: "Initialization Sequence Completed"
     CGroup: /system.slice/system-openvpn.slice/openvpn@server.service
             └─2483 /usr/sbin/openvpn --cd /etc/openvpn/ --config server.conf

  Mar 17 15:44:05 t2 systemd[1]: Starting OpenVPN Robust And Highly Flexible Tunneling Application On server...
  Mar 17 15:44:05 t2 openvpn[2483]: Fri Mar 17 15:44:05 2023 DEPRECATED OPTION: --client-cert-not-required, use --verify-client-cert instead
  Mar 17 15:44:05 t2 systemd[1]: Started OpenVPN Robust And Highly Flexible Tunneling Application On server.
  [root@t2 openvpn]#
  </code></pre>

markdown_text:
  ```bash
  Xshell 7 (Build 0093)
  Copyright (c) 2020 NetSarang Computer, Inc. All rights reserved.

  Type `help' to learn how to use Xshell prompt.
  [C:\~]$ 

  Connecting to 172.17.21.25:22...
  Could not connect to '172.17.21.25' (port 22): Connection failed.

  Type `help' to learn how to use Xshell prompt.
  [C:\~]$ 

  Connecting to 172.17.21.25:22...
  Could not connect to '172.17.21.25' (port 22): Connection failed.

  Type `help' to learn how to use Xshell prompt.
  [C:\~]$ 

  Connecting to 172.17.21.26:22...
  Connection established.
  To escape to local shell, press Ctrl+Alt+].

  WARNING! The remote SSH server rejected X11 forwarding request.

  Welcome to Alibaba Cloud Elastic Compute Service !

  Last login: Wed Mar 15 16:39:34 2023 from 172.17.21.25
  [yangchaojie@t3 ~]$ ssh -p 22 root@172.17.21.25
  The authenticity of host '172.17.21.25 (172.17.21.25)' can't be established.
  ECDSA key fingerprint is SHA256:VY15Lar9HjcUt2OwYClxK4L2AnLJBAh/GheCVG3iep4.
  Are you sure you want to continue connecting (yes/no/[fingerprint])? 
  Host key verification failed.
  [yangchaojie@t3 ~]$ ssh -p 22 root@172.17.21.25
  The authenticity of host '172.17.21.25 (172.17.21.25)' can't be established.
  ECDSA key fingerprint is SHA256:VY15Lar9HjcUt2OwYClxK4L2AnLJBAh/GheCVG3iep4.
  Are you sure you want to continue connecting (yes/no/[fingerprint])? y
  Please type 'yes', 'no' or the fingerprint: y
  Please type 'yes', 'no' or the fingerprint: yes
  Warning: Permanently added '172.17.21.25' (ECDSA) to the list of known hosts.
  root@172.17.21.25's password: 
  Last login: Fri Nov 25 15:00:03 2022 from 192.168.112.30

  Welcome to Alibaba Cloud Elastic Compute Service !

  [root@t2 ~]# ls
  centos7-vpn.sh  client.tar.gz   epel-release-latest-7.noarch.rpm  influxdb-1.7.6.x86_64.rpm  master.zip  telegraf-1.15.3-1.x86_64.rpm
  client          controlsfx.jar  grafana-7.1.0-1.x86_64.rpm        influxdb-1.8.3.x86_64.rpm  nginx
  [root@t2 ~]# cd /opt/
  [root@t2 opt]# ls
  project
  [root@t2 opt]# cd project/
  [root@t2 project]# ls
  db.sqlite3  manage.py  nohup.out  Pipfile  Pipfile.lock  requirements.txt  start.sh  static  templates  vpn  vpn_ms
  [root@t2 project]# cd vpn
  [root@t2 vpn]# ls
  admin.py  apps.py  context_processors.py  __init__.py  migrations  models.py  __pycache__  tests.py  views.py
  [root@t2 vpn]# cd /etc/
  [root@t2 etc]# ls
  adjtime            cron.weekly              gdbinit.d    issue.net       motd                       polkit-1          rsyslog.conf         sysctl.d
  adjtime.rpmsave    crypttab                 GeoIP.conf   kernel          mtab                       popt.d            rsyslog.d            systemd
  aliases            csh.cshrc                gnupg        krb5.conf       my.cnf                     postfix           rwtab                system-lsb
  aliases.db         csh.login                grafana      krb5.conf.d     my.cnf.d                   ppp               rwtab.d              system-release
  alinux-release     dbus-1                   GREP_COLORS  ld.so.cache     netconfig                  prelink.conf.d    sasl2                telegraf
  alternatives       default                  groff        ld.so.conf      NetworkManager             printcap          securetty            terminfo
  anacrontab         depmod.d                 group        ld.so.conf.d    networks                   profile           security             tmpfiles.d
  anolis-release     dhcp                     group-       libaudit.conf   nfs.conf                   profile.d         selinux              tuned
  asound.conf        DIR_COLORS               grub2.cfg    libnl           nfsmount.conf              protocols         services             udev
  at.deny            DIR_COLORS.256color      grub.d       libreport       nscd.conf                  python            sestatus.conf        update-motd.d
  audisp             DIR_COLORS.lightbgcolor  gshadow      libuser.conf    nsswitch.conf              rc0.d             shadow               vconsole.conf
  audit              docker                   gshadow-     locale.conf     nsswitch.conf.bak          rc1.d             shadow-              vimrc
  bash_completion.d  dracut.conf              gss          localtime       nsswitch.conf.rpmnew       rc2.d             shells               virc
  bashrc             dracut.conf.d            gssproxy     login.defs      ntp.conf                   rc3.d             skel                 wgetrc
  binfmt.d           e2fsck.conf              host.conf    logrotate.conf  oci-register-machine.conf  rc4.d             ssh                  wireguard
  centos-release     environment              hostname     logrotate.d     oci-umount                 rc5.d             ssl                  wpa_supplicant
  chkconfig.d        ethertypes               hosts        lsb-release.d   oci-umount.conf            rc6.d             statetab             X11
  chrony.conf        exports                  hosts.allow  lvm             openldap                   rc.d              statetab.d           xdg
  chrony.keys        exports.d                hosts.deny   machine-id      openvpn                    rc.local          subgid               xinetd.d
  cloud              favicon.png              idmapd.conf  magic           opt                        redhat-release    subuid               yum
  containers         filesystems              image-id     mailcap         os-release                 request-key.conf  sudo.conf            yum.conf
  cron.d             firewalld                influxdb     mail.rc         pam.d                      request-key.d     sudoers              yum.repos.d
  cron.daily         fonts                    init.d       man_db.conf     passwd                     resolv.conf       sudoers.d
  cron.deny          fstab                    inittab      mime.types      passwd-                    resolv.conf.save  sudo-ldap.conf
  cron.hourly        fuse.conf                inputrc      mke2fs.conf     pkcs11                     rpc               sysconfig
  cron.monthly       gcrypt                   iproute2     modprobe.d      pki                        rpm               sysctl.conf
  crontab            gdbinit                  issue        modules-load.d  pm                         rsyncd.conf       sysctl.conf.rpmsave
  [root@t2 etc]# cd op
  openldap/ openvpn/  opt/      
  [root@t2 etc]# cd op
  openldap/ openvpn/  opt/      
  [root@t2 etc]# cd openvpn/
  [root@t2 openvpn]# ls
  ca.crt  ccd  checkpsw.sh  client  dh.pem  easy-rsa  ipp.txt  openvpn-password.log  psw-file  server  server.conf  server_crt  server.crt  server.key
  [root@t2 openvpn]# ls
  ca.crt  ccd  checkpsw.sh  client  dh.pem  easy-rsa  ipp.txt  openvpn-password.log  psw-file  server  server.conf  server_crt  server.crt  server.key
  [root@t2 openvpn]# openssl x509 -noout -text in server.crt 
  unknown option in
  usage: x509 args
   -inform arg     - input format - default PEM (one of DER, NET or PEM)
   -outform arg    - output format - default PEM (one of DER, NET or PEM)
   -keyform arg    - private key format - default PEM
   -CAform arg     - CA format - default PEM
   -CAkeyform arg  - CA key format - default PEM
   -in arg         - input file - default stdin
   -out arg        - output file - default stdout
   -passin arg     - private key password source
   -serial         - print serial number value
   -subject_hash   - print subject hash value
   -subject_hash_old   - print old-style (MD5) subject hash value
   -issuer_hash    - print issuer hash value
   -issuer_hash_old    - print old-style (MD5) issuer hash value
   -hash           - synonym for -subject_hash
   -subject        - print subject DN
   -issuer         - print issuer DN
   -email          - print email address(es)
   -startdate      - notBefore field
   -enddate        - notAfter field
   -purpose        - print out certificate purposes
   -dates          - both Before and After dates
   -modulus        - print the RSA key modulus
   -pubkey         - output the public key
   -fingerprint    - print the certificate fingerprint
   -alias          - output certificate alias
   -noout          - no certificate output
   -ocspid         - print OCSP hash values for the subject name and public key
   -ocsp_uri       - print OCSP Responder URL(s)
   -trustout       - output a "trusted" certificate
   -clrtrust       - clear all trusted purposes
   -clrreject      - clear all rejected purposes
   -addtrust arg   - trust certificate for a given purpose
   -addreject arg  - reject certificate for a given purpose
   -setalias arg   - set certificate alias
   -days arg       - How long till expiry of a signed certificate - def 30 days
   -checkend arg   - check whether the cert expires in the next arg seconds
                     exit 1 if so, 0 if not
   -signkey arg    - self sign cert with arg
   -x509toreq      - output a certification request object
   -req            - input is a certificate request, sign and output.
   -CA arg         - set the CA certificate, must be PEM format.
   -CAkey arg      - set the CA key, must be PEM format
                     missing, it is assumed to be in the CA file.
   -CAcreateserial - create serial number file if it does not exist
   -CAserial arg   - serial file
   -set_serial     - serial number to use
   -text           - print the certificate in text form
   -C              - print out C code forms
   -<dgst>         - digest to use, see openssl dgst -h output for list
   -extfile        - configuration file with X509V3 extensions to add
   -extensions     - section from config file with X509V3 extensions to add
   -clrext         - delete extensions before signing and input certificate
   -nameopt arg    - various certificate name options
   -engine e       - use engine e, possibly a hardware device.
   -certopt arg    - various certificate text options
   -checkhost host - check certificate matches "host"
   -checkemail email - check certificate matches "email"
   -checkip ipaddr - check certificate matches "ipaddr"
  [root@t2 openvpn]# openssl x509 -noout -text -in server.crt 
  Certificate:
      Data:
          Version: 3 (0x2)
          Serial Number:
              95:a6:bd:d4:02:d2:63:dc:bd:08:39:14:6e:a3:3c:94
      Signature Algorithm: sha256WithRSAEncryption
          Issuer: CN=micvs
          Validity
              Not Before: Dec 12 04:50:26 2020 GMT
              Not After : Mar 17 04:50:26 2023 GMT
          Subject: CN=micvs_server
          Subject Public Key Info:
              Public Key Algorithm: rsaEncryption
                  Public-Key: (2048 bit)
                  Modulus:
                      00:d8:2e:eb:0a:e8:e1:9f:87:b8:79:61:ab:96:75:
                      be:87:8e:33:74:80:d0:0b:aa:8e:7d:56:39:04:e4:
                      89:d4:38:a9:77:fc:c0:60:27:f7:1c:0f:cc:cb:30:
                      2f:c9:92:ed:06:06:c7:b3:5b:9e:fc:c3:f5:ca:8a:
                      d0:8d:ff:28:e4:29:03:41:5b:fd:8b:97:f9:d1:17:
                      2f:ce:37:8e:36:dd:d9:e0:94:7e:a6:16:9f:2e:98:
                      13:13:20:05:fa:3a:1a:17:55:87:0b:68:51:4c:7f:
                      64:32:13:94:5e:31:3a:12:5e:02:33:1e:67:99:a7:
                      0f:ee:2e:a9:5c:0d:d4:31:e4:8b:8f:ea:70:3d:1b:
                      d9:81:5d:2a:5f:6f:1c:d2:89:f9:e7:85:01:ee:6d:
                      ec:64:4e:5d:1b:91:e7:f7:98:eb:24:85:fe:bc:7c:
                      28:3a:2f:8a:ca:bf:50:53:db:66:59:4a:fa:2e:eb:
                      1f:81:f5:64:86:f0:1e:a3:87:52:95:59:6d:39:5d:
                      0b:71:bc:48:70:d2:cc:7f:bd:0c:2e:2d:0e:7a:0b:
                      01:3e:1b:63:3d:d8:af:8c:42:50:be:aa:6e:11:a9:
                      0f:10:71:38:22:95:42:83:d6:02:c3:2d:b2:83:01:
                      11:18:c9:b9:eb:28:72:b6:72:fe:e0:2c:98:0c:01:
                      8a:9d
                  Exponent: 65537 (0x10001)
          X509v3 extensions:
              X509v3 Basic Constraints: 
                  CA:FALSE
              X509v3 Subject Key Identifier: 
                  5C:E3:7F:82:65:F7:D7:A6:09:05:4F:88:09:E9:C2:75:5F:5F:B4:CE
              X509v3 Authority Key Identifier: 
                  keyid:51:93:EB:47:9A:69:76:9B:5D:46:5F:EC:C6:8D:50:2D:18:6D:8D:11
                  DirName:/CN=micvs
                  serial:8F:B4:36:E7:E5:0C:1A:B6

              X509v3 Extended Key Usage: 
                  TLS Web Server Authentication
              X509v3 Key Usage: 
                  Digital Signature, Key Encipherment
              X509v3 Subject Alternative Name: 
                  DNS:micvs_server
      Signature Algorithm: sha256WithRSAEncryption
           64:e1:1c:b9:c4:58:1b:69:93:d6:be:38:39:b1:ca:c1:41:89:
           e4:12:16:b8:b7:66:6a:15:1e:75:41:d9:93:af:d2:ff:d0:78:
           47:0a:cd:3d:de:cf:fc:26:f7:d1:76:6c:fc:b4:9c:78:53:b9:
           02:0a:35:fd:83:78:4e:ef:b4:ba:60:93:b9:64:b6:fb:25:9c:
           2a:53:ec:ec:b4:6d:a5:5e:15:46:c1:c1:6f:2c:99:cf:36:91:
           10:07:05:27:91:ff:20:3e:29:8f:20:b3:58:34:04:b6:d0:b1:
           e9:a3:27:d8:81:77:cf:f0:19:8e:5c:a1:5e:39:d1:a3:ba:e4:
           bf:9e:0d:57:a3:6c:b4:b0:10:81:29:08:d0:55:35:b8:72:b9:
           b4:2a:04:18:99:e2:08:70:6b:1f:fc:ee:2f:94:4c:59:0c:e9:
           66:1a:fa:a5:e6:6f:a2:f3:66:18:22:da:17:a2:a8:76:01:06:
           98:f7:a2:8a:b5:1e:ca:b0:ae:6f:eb:f4:1a:6e:e2:89:cd:87:
           39:67:28:79:f6:f4:86:7d:de:5e:b1:10:3c:a7:72:69:fd:58:
           9d:1e:9f:54:e2:06:3c:a7:39:4c:a5:d0:4b:12:15:8a:c7:95:
           06:41:33:f4:74:0d:68:7c:5e:f5:67:f2:d1:cc:fc:e3:aa:14:
           49:09:5a:7b
  [root@t2 openvpn]# ls
  ca.crt  ccd  checkpsw.sh  client  dh.pem  easy-rsa  ipp.txt  openvpn-password.log  psw-file  server  server.conf  server_crt  server.crt  server.key
  [root@t2 openvpn]# mkdir cert_2023-208
  [root@t2 openvpn]# ls
  ca.crt  cert_2023-208  client  easy-rsa  openvpn-password.log  server       server_crt  server.key
  ccd     checkpsw.sh    dh.pem  ipp.txt   psw-file              server.conf  server.crt
  [root@t2 openvpn]# cd cert_2023-208/
  [root@t2 cert_2023-208]# ls
  [root@t2 cert_2023-208]# cd ..
  [root@t2 openvpn]# ls
  ca.crt  cert_2023-208  client  easy-rsa  openvpn-password.log  server       server_crt  server.key
  ccd     checkpsw.sh    dh.pem  ipp.txt   psw-file              server.conf  server.crt
  [root@t2 openvpn]# cd easy-rsa/
  [root@t2 easy-rsa]# ls
  build      COPYING.md  doc       KNOWN_ISSUES  op_test.orig  README.md             release-keys  wop_test.sh
  ChangeLog  distro      easyrsa3  Licensing     op_test.sh    README.quickstart.md  wop_test.bat
  [root@t2 easy-rsa]# cd ..
  [root@t2 openvpn]# ls
  ca.crt  cert_2023-208  client  easy-rsa  openvpn-password.log  server       server_crt  server.key
  ccd     checkpsw.sh    dh.pem  ipp.txt   psw-file              server.conf  server.crt
  [root@t2 openvpn]# cd cert_2023-208/
  [root@t2 cert_2023-208]# ls
  [root@t2 cert_2023-208]# cp -r /etc/open
  openldap/ openvpn/  
  [root@t2 cert_2023-208]# cp -r /etc/open
  openldap/ openvpn/  
  [root@t2 cert_2023-208]# cp -r /etc/openvpn/easy-rsa ./
  [root@t2 cert_2023-208]# ls
  easy-rsa
  [root@t2 cert_2023-208]# cd easy-rsa/
  [root@t2 easy-rsa]# ls
  build      COPYING.md  doc       KNOWN_ISSUES  op_test.orig  README.md             release-keys  wop_test.sh
  ChangeLog  distro      easyrsa3  Licensing     op_test.sh    README.quickstart.md  wop_test.bat
  [root@t2 easy-rsa]# ./easyrsa3/
  easyrsa     pki/        x509-types/ 
  [root@t2 easy-rsa]# cd easyrsa3/
  [root@t2 easyrsa3]# ls
  easyrsa  openssl-easyrsa.cnf  pki  vars  vars.example  x509-types
  [root@t2 easyrsa3]# rm -rf pki/
  [root@t2 easyrsa3]# ls
  easyrsa  openssl-easyrsa.cnf  vars  vars.example  x509-types
  [root@t2 easyrsa3]# ./easyrsa init-pki

  Note: using Easy-RSA configuration from: /etc/openvpn/cert_2023-208/easy-rsa/easyrsa3/vars

  init-pki complete; you may now create a CA or requests.
  Your newly created PKI dir is: /etc/openvpn/cert_2023-208/easy-rsa/easyrsa3/pki


  [root@t2 easyrsa3]# ls
  easyrsa  openssl-easyrsa.cnf  pki  vars  vars.example  x509-types
  [root@t2 easyrsa3]# ./easyrsa --batch build-ca nopass
  Using SSL: openssl OpenSSL 1.0.2k-fips  26 Jan 2017
  Generating RSA private key, 2048 bit long modulus
  ...................+++
  .......................................................+++
  e is 65537 (0x10001)

  [root@t2 easyrsa3]# EASYRSA_CERT_EXPIRE=3650 ./easyrsa build-server-full server nopassEASYRSA_CERT_EXPIRE=3650 ./easyrsa build-server-full server nopass^C
  [root@t2 easyrsa3]# EASYRSA_CERT_EXPIRE=3650 ./easyrsa build-server-full server nopass

  Note: using Easy-RSA configuration from: /etc/openvpn/cert_2023-208/easy-rsa/easyrsa3/vars
  Using SSL: openssl OpenSSL 1.0.2k-fips  26 Jan 2017
  Generating a 2048 bit RSA private key
  ............+++
  ................................................................+++
  writing new private key to '/etc/openvpn/cert_2023-208/easy-rsa/easyrsa3/pki/easy-rsa-14928.jQN2WT/tmp.EkeT0G'
  -----
  Using configuration from /etc/openvpn/cert_2023-208/easy-rsa/easyrsa3/pki/easy-rsa-14928.jQN2WT/tmp.DmWly1
  Check that the request matches the signature
  Signature ok
  The Subject's Distinguished Name is as follows
  commonName            :ASN.1 12:'server'
  Certificate is to be certified until Mar 14 07:15:04 2033 GMT (3650 days)

  Write out database with 1 new entries
  Data Base Updated

  [root@t2 easyrsa3]# EASYRSA_CERT_EXPIRE=3650 ./easyrsa build-client-full client nopass

  Note: using Easy-RSA configuration from: /etc/openvpn/cert_2023-208/easy-rsa/easyrsa3/vars
  Using SSL: openssl OpenSSL 1.0.2k-fips  26 Jan 2017
  Generating a 2048 bit RSA private key
  ......+++
  .................................+++
  writing new private key to '/etc/openvpn/cert_2023-208/easy-rsa/easyrsa3/pki/easy-rsa-15028.kmnt4D/tmp.rZUlnF'
  -----
  Using configuration from /etc/openvpn/cert_2023-208/easy-rsa/easyrsa3/pki/easy-rsa-15028.kmnt4D/tmp.cqwOFp
  Check that the request matches the signature
  Signature ok
  The Subject's Distinguished Name is as follows
  commonName            :ASN.1 12:'client'
  Certificate is to be certified until Mar 14 07:15:19 2033 GMT (3650 days)

  Write out database with 1 new entries
  Data Base Updated

  [root@t2 easyrsa3]# EASYRSA_CRL_DAYS=3650 ./easyrsa gen-crl

  Note: using Easy-RSA configuration from: /etc/openvpn/cert_2023-208/easy-rsa/easyrsa3/vars
  Using SSL: openssl OpenSSL 1.0.2k-fips  26 Jan 2017
  Using configuration from /etc/openvpn/cert_2023-208/easy-rsa/easyrsa3/pki/easy-rsa-15103.TnYFbh/tmp.RbHEgL

  An updated CRL has been created.
  CRL file: /etc/openvpn/cert_2023-208/easy-rsa/easyrsa3/pki/crl.pem


  [root@t2 easyrsa3]# cp pki/ca.crt pki/private/ca.key pki/issued/server.crt pki/private/server.key pki/crl.pem ../../
  [root@t2 easyrsa3]# ls
  easyrsa  openssl-easyrsa.cnf  pki  vars  vars.example  x509-types
  [root@t2 easyrsa3]# cd ..
  [root@t2 easy-rsa]# ls
  build      COPYING.md  doc       KNOWN_ISSUES  op_test.orig  README.md             release-keys  wop_test.sh
  ChangeLog  distro      easyrsa3  Licensing     op_test.sh    README.quickstart.md  wop_test.bat
  [root@t2 easy-rsa]# cd ..
  [root@t2 cert_2023-208]# ls
  ca.crt  ca.key  crl.pem  easy-rsa  server.crt  server.key
  [root@t2 cert_2023-208]# chown nobody:nobody crl.pem 
  [root@t2 cert_2023-208]#  openssl verify -CAfile ca.crt -purpose sslserver server.crt
  server.crt: OK
  [root@t2 cert_2023-208]# cd ..
  [root@t2 openvpn]# ls
  ca.crt  cert_2023-208  client  easy-rsa  openvpn-password.log  server       server_crt  server.key
  ccd     checkpsw.sh    dh.pem  ipp.txt   psw-file              server.conf  server.crt
  [root@t2 openvpn]# cd  client/
  [root@t2 client]# ls
  [root@t2 client]# cd ..
  [root@t2 openvpn]# ls
  ca.crt  cert_2023-208  client  easy-rsa  openvpn-password.log  server       server_crt  server.key
  ccd     checkpsw.sh    dh.pem  ipp.txt   psw-file              server.conf  server.crt
  [root@t2 openvpn]# cd server
  [root@t2 server]# l
  -bash: l: command not found
  [root@t2 server]# cd ..
  [root@t2 openvpn]# ls
  ca.crt  cert_2023-208  client  easy-rsa  openvpn-password.log  server       server_crt  server.key
  ccd     checkpsw.sh    dh.pem  ipp.txt   psw-file              server.conf  server.crt
  [root@t2 openvpn]# cd server
  [root@t2 server]# ls
  [root@t2 server]# cd ..
  [root@t2 openvpn]# ls
  ca.crt  cert_2023-208  client  easy-rsa  openvpn-password.log  server       server_crt  server.key
  ccd     checkpsw.sh    dh.pem  ipp.txt   psw-file              server.conf  server.crt
  [root@t2 openvpn]# cd server
  [root@t2 server]# cd ..
  [root@t2 openvpn]# cd server_crt/
  [root@t2 server_crt]# ls
  ca.crt  dh.pem  server.crt  server.key
  [root@t2 server_crt]# cd ..
  [root@t2 openvpn]# ls
  ca.crt  cert_2023-208  client  easy-rsa  openvpn-password.log  server       server_crt  server.key
  ccd     checkpsw.sh    dh.pem  ipp.txt   psw-file              server.conf  server.crt
  [root@t2 openvpn]# cd cert_2023-208/
  [root@t2 cert_2023-208]# ls
  ca.crt  ca.key  crl.pem  easy-rsa  server.crt  server.key
  [root@t2 cert_2023-208]# cd ..
  [root@t2 openvpn]# ls
  ca.crt  cert_2023-208  client  easy-rsa  openvpn-password.log  server       server_crt  server.key
  ccd     checkpsw.sh    dh.pem  ipp.txt   psw-file              server.conf  server.crt
  [root@t2 openvpn]# cd server_crt/
  [root@t2 server_crt]# ls
  ca.crt  dh.pem  server.crt  server.key
  [root@t2 server_crt]# ls
  ca.crt  dh.pem  server.crt  server.key
  [root@t2 server_crt]# zip ./* Expires_2023年3月17日.zip
  	zip warning: missing end signature--probably not a zip file (did you
  	zip warning: remember to use binary mode when you transferred it?)
  	zip warning: (if you are trying to read a damaged archive try -F)

  zip error: Zip file structure invalid (./ca.crt)
  [root@t2 server_crt]# ls
  ca.crt  dh.pem  server.crt  server.key
  [root@t2 server_crt]# zip  Expires_2023年3月17日.zip ./*
    adding: ca.crt (deflated 27%)
    adding: dh.pem (deflated 18%)
    adding: server.crt (deflated 45%)
    adding: server.key (deflated 23%)
  [root@t2 server_crt]# LS
  -bash: LS: command not found
  [root@t2 server_crt]# LS
  -bash: LS: command not found
  [root@t2 server_crt]# ls
  ca.crt  dh.pem  Expires_2023年3月17日.zip  server.crt  server.key
  [root@t2 server_crt]# ls
  ca.crt  dh.pem  Expires_2023年3月17日.zip  server.crt  server.key
  [root@t2 server_crt]# cd ..
  [root@t2 openvpn]# ls
  ca.crt  cert_2023-208  client  easy-rsa  openvpn-password.log  server       server_crt  server.key
  ccd     checkpsw.sh    dh.pem  ipp.txt   psw-file              server.conf  server.crt
  [root@t2 openvpn]# cd cert_2023-208/
  [root@t2 cert_2023-208]# ;s
  -bash: syntax error near unexpected token `;'
  [root@t2 cert_2023-208]# ls
  ca.crt  ca.key  crl.pem  easy-rsa  server.crt  server.key
  [root@t2 cert_2023-208]# cp ca.crt ca.key crl.pem server.crt server.key /etc/openvpn/server
  server/      server.conf  server_crt/  server.crt   server.key   
  [root@t2 cert_2023-208]# cp ca.crt ca.key crl.pem server.crt server.key /etc/openvpn/server_crt/
  cp: overwrite ‘/etc/openvpn/server_crt/ca.crt’? y
  cp: overwrite ‘/etc/openvpn/server_crt/server.crt’? y
  cp: overwrite ‘/etc/openvpn/server_crt/server.key’? y
  [root@t2 cert_2023-208]# ls
  ca.crt  ca.key  crl.pem  easy-rsa  server.crt  server.key
  [root@t2 cert_2023-208]# cd ..
  [root@t2 openvpn]# ls
  ca.crt  cert_2023-208  client  easy-rsa  openvpn-password.log  server       server_crt  server.key
  ccd     checkpsw.sh    dh.pem  ipp.txt   psw-file              server.conf  server.crt
  [root@t2 openvpn]# cd server_crt/
  [root@t2 server_crt]# ls
  ca.crt  ca.key  crl.pem  dh.pem  Expires_2023年3月17日.zip  server.crt  server.key
  [root@t2 server_crt]# cd ..
  [root@t2 openvpn]# ls
  ca.crt  cert_2023-208  client  easy-rsa  openvpn-password.log  server       server_crt  server.key
  ccd     checkpsw.sh    dh.pem  ipp.txt   psw-file              server.conf  server.crt
  [root@t2 openvpn]# vim server.conf 
  [root@t2 openvpn]# ls
  ca.crt  cert_2023-208  client  easy-rsa  openvpn-password.log  server       server_crt  server.key
  ccd     checkpsw.sh    dh.pem  ipp.txt   psw-file              server.conf  server.crt
  [root@t2 openvpn]# cd ..
  [root@t2 etc]# ls
  adjtime            cron.weekly              gdbinit.d    issue.net       motd                       polkit-1          rsyslog.conf         sysctl.d
  adjtime.rpmsave    crypttab                 GeoIP.conf   kernel          mtab                       popt.d            rsyslog.d            systemd
  aliases            csh.cshrc                gnupg        krb5.conf       my.cnf                     postfix           rwtab                system-lsb
  aliases.db         csh.login                grafana      krb5.conf.d     my.cnf.d                   ppp               rwtab.d              system-release
  alinux-release     dbus-1                   GREP_COLORS  ld.so.cache     netconfig                  prelink.conf.d    sasl2                telegraf
  alternatives       default                  groff        ld.so.conf      NetworkManager             printcap          securetty            terminfo
  anacrontab         depmod.d                 group        ld.so.conf.d    networks                   profile           security             tmpfiles.d
  anolis-release     dhcp                     group-       libaudit.conf   nfs.conf                   profile.d         selinux              tuned
  asound.conf        DIR_COLORS               grub2.cfg    libnl           nfsmount.conf              protocols         services             udev
  at.deny            DIR_COLORS.256color      grub.d       libreport       nscd.conf                  python            sestatus.conf        update-motd.d
  audisp             DIR_COLORS.lightbgcolor  gshadow      libuser.conf    nsswitch.conf              rc0.d             shadow               vconsole.conf
  audit              docker                   gshadow-     locale.conf     nsswitch.conf.bak          rc1.d             shadow-              vimrc
  bash_completion.d  dracut.conf              gss          localtime       nsswitch.conf.rpmnew       rc2.d             shells               virc
  bashrc             dracut.conf.d            gssproxy     login.defs      ntp.conf                   rc3.d             skel                 wgetrc
  binfmt.d           e2fsck.conf              host.conf    logrotate.conf  oci-register-machine.conf  rc4.d             ssh                  wireguard
  centos-release     environment              hostname     logrotate.d     oci-umount                 rc5.d             ssl                  wpa_supplicant
  chkconfig.d        ethertypes               hosts        lsb-release.d   oci-umount.conf            rc6.d             statetab             X11
  chrony.conf        exports                  hosts.allow  lvm             openldap                   rc.d              statetab.d           xdg
  chrony.keys        exports.d                hosts.deny   machine-id      openvpn                    rc.local          subgid               xinetd.d
  cloud              favicon.png              idmapd.conf  magic           opt                        redhat-release    subuid               yum
  containers         filesystems              image-id     mailcap         os-release                 request-key.conf  sudo.conf            yum.conf
  cron.d             firewalld                influxdb     mail.rc         pam.d                      request-key.d     sudoers              yum.repos.d
  cron.daily         fonts                    init.d       man_db.conf     passwd                     resolv.conf       sudoers.d
  cron.deny          fstab                    inittab      mime.types      passwd-                    resolv.conf.save  sudo-ldap.conf
  cron.hourly        fuse.conf                inputrc      mke2fs.conf     pkcs11                     rpc               sysconfig
  cron.monthly       gcrypt                   iproute2     modprobe.d      pki                        rpm               sysctl.conf
  crontab            gdbinit                  issue        modules-load.d  pm                         rsyncd.conf       sysctl.conf.rpmsave
  [root@t2 etc]# cd openvpn/
  [root@t2 openvpn]# ls
  ca.crt  cert_2023-208  client  easy-rsa  openvpn-password.log  server       server_crt  server.key
  ccd     checkpsw.sh    dh.pem  ipp.txt   psw-file              server.conf  server.crt
  [root@t2 openvpn]# cd cert_2023-208/
  [root@t2 cert_2023-208]# ls
  ca.crt  ca.key  crl.pem  easy-rsa  server.crt  server.key
  [root@t2 cert_2023-208]# cp ca.crt ca.key crl.pem server.crt server.key /etc/openvpn/
  cp: overwrite ‘/etc/openvpn/ca.crt’? y
  cp: overwrite ‘/etc/openvpn/server.crt’? y
  cp: overwrite ‘/etc/openvpn/server.key’? y
  [root@t2 cert_2023-208]# cd ..
  [root@t2 openvpn]# ls
  ca.crt  ccd            checkpsw.sh  crl.pem  easy-rsa  openvpn-password.log  server       server_crt  server.key
  ca.key  cert_2023-208  client       dh.pem   ipp.txt   psw-file              server.conf  server.crt
  [root@t2 openvpn]# vim server.conf 
  [root@t2 openvpn]# systemctl restart openvpn
  Failed to restart openvpn.service: Unit not found.
  [root@t2 openvpn]# systemctl status openvpn
  Unit openvpn.service could not be found.
  [root@t2 openvpn]# systemctl status openvpn@server.service 
  ● openvpn@server.service - OpenVPN Robust And Highly Flexible Tunneling Application On server
     Loaded: loaded (/usr/lib/systemd/system/openvpn@.service; enabled; vendor preset: disabled)
     Active: active (running) since Fri 2022-11-25 14:59:33 CST; 3 months 20 days ago
   Main PID: 24698 (openvpn)
     Status: "Initialization Sequence Completed"
     CGroup: /system.slice/system-openvpn.slice/openvpn@server.service
             └─24698 /usr/sbin/openvpn --cd /etc/openvpn/ --config server.conf
  ^C
  [root@t2 openvpn]# systemctl restart openvpn@server.service
  [root@t2 openvpn]# systemctl status openvpn@server.service 
  ● openvpn@server.service - OpenVPN Robust And Highly Flexible Tunneling Application On server
     Loaded: loaded (/usr/lib/systemd/system/openvpn@.service; enabled; vendor preset: disabled)
     Active: active (running) since Fri 2023-03-17 15:25:09 CST; 1s ago
   Main PID: 15869 (openvpn)
     Status: "Initialization Sequence Completed"
     CGroup: /system.slice/system-openvpn.slice/openvpn@server.service
             └─15869 /usr/sbin/openvpn --cd /etc/openvpn/ --config server.conf

  Mar 17 15:25:09 t2 systemd[1]: Starting OpenVPN Robust And Highly Flexible Tunneling Application On server...
  Mar 17 15:25:09 t2 openvpn[15869]: Fri Mar 17 15:25:09 2023 DEPRECATED OPTION: --client-cert-not-required, use --verify-client-cert instead
  Mar 17 15:25:09 t2 systemd[1]: Started OpenVPN Robust And Highly Flexible Tunneling Application On server.
  [root@t2 openvpn]# systemctl stop openvpn@server.service 
  [root@t2 openvpn]# systemctl status openvpn@server.service 
  ● openvpn@server.service - OpenVPN Robust And Highly Flexible Tunneling Application On server
     Loaded: loaded (/usr/lib/systemd/system/openvpn@.service; enabled; vendor preset: disabled)
     Active: inactive (dead) since Fri 2023-03-17 15:25:35 CST; 2s ago
    Process: 15869 ExecStart=/usr/sbin/openvpn --cd /etc/openvpn/ --config %i.conf (code=exited, status=0/SUCCESS)
   Main PID: 15869 (code=exited, status=0/SUCCESS)
     Status: "Initialization Sequence Completed"

  Mar 17 15:25:09 t2 systemd[1]: Starting OpenVPN Robust And Highly Flexible Tunneling Application On server...
  Mar 17 15:25:09 t2 openvpn[15869]: Fri Mar 17 15:25:09 2023 DEPRECATED OPTION: --client-cert-not-required, use --verify-client-cert instead
  Mar 17 15:25:09 t2 systemd[1]: Started OpenVPN Robust And Highly Flexible Tunneling Application On server.
  Mar 17 15:25:35 t2 systemd[1]: Stopping OpenVPN Robust And Highly Flexible Tunneling Application On server...
  Mar 17 15:25:35 t2 systemd[1]: Stopped OpenVPN Robust And Highly Flexible Tunneling Application On server.
  [root@t2 openvpn]# systemctl start openvpn@server.service
  [root@t2 openvpn]# systemctl status openvpn@server.service 
  ● openvpn@server.service - OpenVPN Robust And Highly Flexible Tunneling Application On server
     Loaded: loaded (/usr/lib/systemd/system/openvpn@.service; enabled; vendor preset: disabled)
     Active: active (running) since Fri 2023-03-17 15:25:43 CST; 29s ago
   Main PID: 15925 (openvpn)
     Status: "Initialization Sequence Completed"
     CGroup: /system.slice/system-openvpn.slice/openvpn@server.service
             └─15925 /usr/sbin/openvpn --cd /etc/openvpn/ --config server.conf

  Mar 17 15:25:43 t2 systemd[1]: Starting OpenVPN Robust And Highly Flexible Tunneling Application On server...
  Mar 17 15:25:43 t2 openvpn[15925]: Fri Mar 17 15:25:43 2023 DEPRECATED OPTION: --client-cert-not-required, use --verify-client-cert instead
  Mar 17 15:25:43 t2 systemd[1]: Started OpenVPN Robust And Highly Flexible Tunneling Application On server.
  [root@t2 openvpn]# cd /var/lo
  local/ lock/  log/   
  [root@t2 openvpn]# cd /var/log/
  anaconda/ audit/    chrony/   grafana/  influxdb/ journal/  openvpn/  rhsm/     sa/       telegraf/ tuned/    
  [root@t2 openvpn]# cd /var/log/openvpn/
  [root@t2 openvpn]# ls
  openvpn.log
  [root@t2 openvpn]# tail -f openvpn.log 
  Fri Mar 17 15:26:21 2023 58.33.81.92:49850 SIGUSR1[soft,connection-reset] received, client-instance restarting
  Fri Mar 17 15:26:21 2023 58.33.81.92:63785 Connection reset, restarting [0]
  Fri Mar 17 15:26:21 2023 58.33.81.92:63785 SIGUSR1[soft,connection-reset] received, client-instance restarting
  Fri Mar 17 15:26:22 2023 58.33.81.92:57615 TLS: Initial packet from [AF_INET]58.33.81.92:57615, sid=e879fe27 d222dded
  Fri Mar 17 15:26:22 2023 58.33.81.92:57615 Connection reset, restarting [0]
  Fri Mar 17 15:26:22 2023 58.33.81.92:57615 SIGUSR1[soft,connection-reset] received, client-instance restarting
  Fri Mar 17 15:26:22 2023 TCP connection established with [AF_INET]101.85.37.188:13366
  Fri Mar 17 15:26:22 2023 101.85.37.188:13366 TLS: Initial packet from [AF_INET]101.85.37.188:13366, sid=37c74b71 7dbb2f42
  Fri Mar 17 15:26:22 2023 101.85.37.188:13366 Connection reset, restarting [0]
  Fri Mar 17 15:26:22 2023 101.85.37.188:13366 SIGUSR1[soft,connection-reset] received, client-instance restarting
  Fri Mar 17 15:26:37 2023 TCP connection established with [AF_INET]58.33.81.92:49851
  Fri Mar 17 15:26:37 2023 58.33.81.92:49851 TCP connection established with [AF_INET]58.33.81.92:63787
  Fri Mar 17 15:26:38 2023 58.33.81.92:63787 TCP connection established with [AF_INET]58.33.81.92:57617
  Fri Mar 17 15:26:38 2023 58.33.81.92:49851 TLS: Initial packet from [AF_INET]58.33.81.92:49851, sid=64e00504 c4fb8b9b
  Fri Mar 17 15:26:38 2023 58.33.81.92:49851 Connection reset, restarting [0]
  Fri Mar 17 15:26:38 2023 58.33.81.92:49851 SIGUSR1[soft,connection-reset] received, client-instance restarting
  Fri Mar 17 15:26:38 2023 58.33.81.92:63787 TLS: Initial packet from [AF_INET]58.33.81.92:63787, sid=0fada2a3 e7c94ebb
  Fri Mar 17 15:26:38 2023 58.33.81.92:63787 Connection reset, restarting [0]
  Fri Mar 17 15:26:38 2023 58.33.81.92:63787 SIGUSR1[soft,connection-reset] received, client-instance restarting
  Fri Mar 17 15:26:39 2023 58.33.81.92:57617 TLS: Initial packet from [AF_INET]58.33.81.92:57617, sid=76ba2f59 a275c4f9
  Fri Mar 17 15:26:39 2023 58.33.81.92:57617 Connection reset, restarting [0]
  Fri Mar 17 15:26:39 2023 58.33.81.92:57617 SIGUSR1[soft,connection-reset] received, client-instance restarting
  Fri Mar 17 15:26:41 2023 TCP connection established with [AF_INET]101.85.37.188:13379
  Fri Mar 17 15:26:41 2023 101.85.37.188:13379 TLS: Initial packet from [AF_INET]101.85.37.188:13379, sid=7ec36c11 e1ee83e9
  Fri Mar 17 15:26:41 2023 101.85.37.188:13379 Connection reset, restarting [0]
  Fri Mar 17 15:26:41 2023 101.85.37.188:13379 SIGUSR1[soft,connection-reset] received, client-instance restarting
  ^C
  [root@t2 openvpn]# sudo reboot
  Connection to 172.17.21.25 closed by remote host.
  Connection to 172.17.21.25 closed.
  [yangchaojie@t3 ~]$ ssh -p 22 root@172.17.21.25
  root@172.17.21.25's password: 
  Last login: Fri Mar 17 15:08:33 2023 from 172.17.21.26

  Welcome to Alibaba Cloud Elastic Compute Service !

  [root@t2 ~]# ls
  centos7-vpn.sh  client.tar.gz   epel-release-latest-7.noarch.rpm  influxdb-1.7.6.x86_64.rpm  master.zip  telegraf-1.15.3-1.x86_64.rpm
  client          controlsfx.jar  grafana-7.1.0-1.x86_64.rpm        influxdb-1.8.3.x86_64.rpm  nginx
  [root@t2 ~]# cd /etc/openvpn/
  [root@t2 openvpn]# ls
  ca.crt  ccd            checkpsw.sh  crl.pem  easy-rsa  openvpn-password.log  server       server_crt  server.key
  ca.key  cert_2023-208  client       dh.pem   ipp.txt   psw-file              server.conf  server.crt
  [root@t2 openvpn]# vim server.conf 
  [root@t2 openvpn]# ls
  ca.crt  ccd            checkpsw.sh  crl.pem  easy-rsa  openvpn-password.log  server       server_crt  server.key
  ca.key  cert_2023-208  client       dh.pem   ipp.txt   psw-file              server.conf  server.crt
  [root@t2 openvpn]# rm -rf server.key ca.key ca.crt dh.pem 
  [root@t2 openvpn]# ls
  ccd  cert_2023-208  checkpsw.sh  client  crl.pem  easy-rsa  ipp.txt  openvpn-password.log  psw-file  server  server.conf  server_crt  server.crt
  [root@t2 openvpn]# cd easy-rsa/
  [root@t2 easy-rsa]# ls
  build      COPYING.md  doc       KNOWN_ISSUES  op_test.orig  README.md             release-keys  wop_test.sh
  ChangeLog  distro      easyrsa3  Licensing     op_test.sh    README.quickstart.md  wop_test.bat
  [root@t2 easy-rsa]# cd ..
  [root@t2 openvpn]# ls
  ccd  cert_2023-208  checkpsw.sh  client  crl.pem  easy-rsa  ipp.txt  openvpn-password.log  psw-file  server  server.conf  server_crt  server.crt
  [root@t2 openvpn]# rm -rf easy-rsa/
  [root@t2 openvpn]# ;s
  -bash: syntax error near unexpected token `;'
  [root@t2 openvpn]# ls
  ccd  cert_2023-208  checkpsw.sh  client  crl.pem  ipp.txt  openvpn-password.log  psw-file  server  server.conf  server_crt  server.crt
  [root@t2 openvpn]# cp cert_2023-208/ ./
  cp: omitting directory ‘cert_2023-208/’
  [root@t2 openvpn]# cp cert_2023-208/* ./
  cp: overwrite ‘./crl.pem’? y
  cp: omitting directory ‘cert_2023-208/easy-rsa’
  cp: overwrite ‘./server.crt’? ^C
  [root@t2 openvpn]# cp -y cert_2023-208/* ./
  cp: invalid option -- 'y'
  Try 'cp --help' for more information.
  [root@t2 openvpn]# cp y cert_2023-208/* ./
  cp: cannot stat ‘y’: No such file or directory
  cp: overwrite ‘./ca.crt’? ^C
  [root@t2 openvpn]# ls
  ca.crt  ca.key  ccd  cert_2023-208  checkpsw.sh  client  crl.pem  ipp.txt  openvpn-password.log  psw-file  server  server.conf  server_crt  server.crt
  [root@t2 openvpn]# \cp  cert_2023-208/* ./
  cp: omitting directory ‘cert_2023-208/easy-rsa’
  [root@t2 openvpn]# ls
  ca.crt  ccd            checkpsw.sh  crl.pem  openvpn-password.log  server       server_crt  server.key
  ca.key  cert_2023-208  client       ipp.txt  psw-file              server.conf  server.crt
  [root@t2 openvpn]# ls
  ca.crt  ccd            checkpsw.sh  crl.pem  openvpn-password.log  server       server_crt  server.key
  ca.key  cert_2023-208  client       ipp.txt  psw-file              server.conf  server.crt
  [root@t2 openvpn]# cd cert_2023-208/
  [root@t2 cert_2023-208]# ls
  ca.crt  ca.key  crl.pem  easy-rsa  server.crt  server.key
  [root@t2 cert_2023-208]# cd easy-rsa/
  [root@t2 easy-rsa]# ls
  build      COPYING.md  doc       KNOWN_ISSUES  op_test.orig  README.md             release-keys  wop_test.sh
  ChangeLog  distro      easyrsa3  Licensing     op_test.sh    README.quickstart.md  wop_test.bat
  [root@t2 easy-rsa]# cd easyrsa3/
  [root@t2 easyrsa3]# ls
  easyrsa  openssl-easyrsa.cnf  pki  vars  vars.example  x509-types
  [root@t2 easyrsa3]# cd pki/
  [root@t2 pki]# ls
  ca.crt           crl.pem    index.txt.attr      index.txt.old  openssl-easyrsa.cnf  renewed  revoked              serial
  certs_by_serial  index.txt  index.txt.attr.old  issued         private              reqs     safessl-easyrsa.cnf  serial.old
  [root@t2 pki]# cd ..
  [root@t2 easyrsa3]# ls
  easyrsa  openssl-easyrsa.cnf  pki  vars  vars.example  x509-types
  [root@t2 easyrsa3]# cd ..
  [root@t2 easy-rsa]# ls
  build      COPYING.md  doc       KNOWN_ISSUES  op_test.orig  README.md             release-keys  wop_test.sh
  ChangeLog  distro      easyrsa3  Licensing     op_test.sh    README.quickstart.md  wop_test.bat
  [root@t2 easy-rsa]# cd ..
  [root@t2 cert_2023-208]# ls
  ca.crt  ca.key  crl.pem  easy-rsa  server.crt  server.key
  [root@t2 cert_2023-208]# cd ..
  [root@t2 openvpn]# ls
  ca.crt  ccd            checkpsw.sh  crl.pem  openvpn-password.log  server       server_crt  server.key
  ca.key  cert_2023-208  client       ipp.txt  psw-file              server.conf  server.crt
  [root@t2 openvpn]# vim server.conf 
  [root@t2 openvpn]# vim server.conf 
  [root@t2 openvpn]# vim server.conf 
  [root@t2 openvpn]# systemctl restart openvpn
  openvpn@                openvpn-client@         openvpn-server@         openvpn@server.service  
  [root@t2 openvpn]# systemctl restart openvpn@
  openvpn@                openvpn@server.service  
  [root@t2 openvpn]# systemctl restart openvpn@server.service 
  [root@t2 openvpn]# ls
  ca.crt  ccd            checkpsw.sh  crl.pem  openvpn-password.log  server       server_crt  server.key
  ca.key  cert_2023-208  client       ipp.txt  psw-file              server.conf  server.crt
  [root@t2 openvpn]# sz ca.crt 

  [root@t2 openvpn]# ls
  ca.crt  ccd            checkpsw.sh  crl.pem  openvpn-password.log  server       server_crt  server.key
  ca.key  cert_2023-208  client       ipp.txt  psw-file              server.conf  server.crt
  [root@t2 openvpn]# cd /var/log/openvpn/
  [root@t2 openvpn]# ls
  openvpn.log
  [root@t2 openvpn]# tail -f openvpn.log 
  Fri Mar 17 15:34:10 2023 --pull-filter ignored for --mode server
  Fri Mar 17 15:34:10 2023 WARNING: POTENTIALLY DANGEROUS OPTION --verify-client-cert none|optional (or --client-cert-not-required) may accept clients which do not present a certificate
  Fri Mar 17 15:34:10 2023 OpenVPN 2.4.12 x86_64-redhat-linux-gnu [Fedora EPEL patched] [SSL (OpenSSL)] [LZO] [LZ4] [EPOLL] [PKCS11] [MH/PKTINFO] [AEAD] built on Mar 17 2022
  Fri Mar 17 15:34:10 2023 library versions: OpenSSL 1.0.2k-fips  26 Jan 2017, LZO 2.06
  Fri Mar 17 15:34:10 2023 WARNING: using --duplicate-cn and --client-config-dir together is probably not what you want
  Fri Mar 17 15:34:10 2023 WARNING: --ifconfig-pool-persist will not work with --duplicate-cn
  Fri Mar 17 15:34:10 2023 NOTE: the current --script-security setting may allow this configuration to call user-defined scripts
  Fri Mar 17 15:34:10 2023 OpenSSL: error:0906D06C:PEM routines:PEM_read_bio:no start line
  Fri Mar 17 15:34:10 2023 Cannot load DH parameters from /etc/openvpn/crl.pem
  Fri Mar 17 15:34:10 2023 Exiting due to fatal error
  c^H^C
  [root@t2 openvpn]# 
  [root@t2 openvpn]# ls
  openvpn.log
  [root@t2 openvpn]# cd ..
  [root@t2 log]# ls
  anaconda              cron                          grubby_prune_debug  maillog            messages-20230312   secure-20230312   wtmp
  audit                 cron-20230219                 influxdb            maillog-20230219   openvpn             spooler           yum.log
  boot.log              cron-20230226                 journal             maillog-20230226   openvpn-status.log  spooler-20230219  yum.log-20210101
  boot.log-20200711     cron-20230305                 kern                maillog-20230305   rhsm                spooler-20230226  yum.log-20220101
  btmp                  cron-20230312                 kern-20230219       maillog-20230312   sa                  spooler-20230305  yum.log-20230101
  btmp-20230301         ecs_network_optimization.log  kern-20230226       messages           secure              spooler-20230312
  chrony                firewalld                     kern-20230305       messages-20230219  secure-20230219     tallylog
  cloudinit-deploy.log  grafana                       kern-20230312       messages-20230226  secure-20230226     telegraf
  cloud-init.log        grubby                        lastlog             messages-20230305  secure-20230305     tuned
  [root@t2 log]# cd ..
  [root@t2 var]# ls
  adm  cache  db  empty  games  gopher  kerberos  lib  local  lock  log  mail  nis  opt  preserve  run  spool  tmp  yp
  [root@t2 var]# cd /etc/
  alternatives/      dbus-1/            groff/             logrotate.d/       pki/               rc4.d/             ssl/               wireguard/
  audisp/            default/           grub.d/            lsb-release.d/     pm/                rc5.d/             statetab.d/        wpa_supplicant/
  audit/             depmod.d/          gss/               lvm/               polkit-1/          rc6.d/             sudoers.d/         X11/
  bash_completion.d/ dhcp/              gssproxy/          modprobe.d/        popt.d/            rc.d/              sysconfig/         xdg/
  binfmt.d/          docker/            influxdb/          modules-load.d/    postfix/           request-key.d/     sysctl.d/          xinetd.d/
  chkconfig.d/       dracut.conf.d/     init.d/            my.cnf.d/          ppp/               rpm/               systemd/           yum/
  cloud/             exports.d/         iproute2/          NetworkManager/    prelink.conf.d/    rsyslog.d/         system-lsb/        yum.repos.d/
  containers/        firewalld/         .java/             oci-umount/        profile.d/         rwtab.d/           telegraf/          
  cron.d/            fonts/             kernel/            openldap/          python/            sasl2/             terminfo/          
  cron.daily/        gcrypt/            krb5.conf.d/       openvpn/           rc0.d/             security/          tmpfiles.d/        
  cron.hourly/       gdbinit.d/         ld.so.conf.d/      opt/               rc1.d/             selinux/           tuned/             
  cron.monthly/      gnupg/             libnl/             pam.d/             rc2.d/             skel/              udev/              
  cron.weekly/       grafana/           libreport/         pkcs11/            rc3.d/             ssh/               update-motd.d/     
  [root@t2 var]# cd /etc/
  alternatives/      dbus-1/            groff/             logrotate.d/       pki/               rc4.d/             ssl/               wireguard/
  audisp/            default/           grub.d/            lsb-release.d/     pm/                rc5.d/             statetab.d/        wpa_supplicant/
  audit/             depmod.d/          gss/               lvm/               polkit-1/          rc6.d/             sudoers.d/         X11/
  bash_completion.d/ dhcp/              gssproxy/          modprobe.d/        popt.d/            rc.d/              sysconfig/         xdg/
  binfmt.d/          docker/            influxdb/          modules-load.d/    postfix/           request-key.d/     sysctl.d/          xinetd.d/
  chkconfig.d/       dracut.conf.d/     init.d/            my.cnf.d/          ppp/               rpm/               systemd/           yum/
  cloud/             exports.d/         iproute2/          NetworkManager/    prelink.conf.d/    rsyslog.d/         system-lsb/        yum.repos.d/
  containers/        firewalld/         .java/             oci-umount/        profile.d/         rwtab.d/           telegraf/          
  cron.d/            fonts/             kernel/            openldap/          python/            sasl2/             terminfo/          
  cron.daily/        gcrypt/            krb5.conf.d/       openvpn/           rc0.d/             security/          tmpfiles.d/        
  cron.hourly/       gdbinit.d/         ld.so.conf.d/      opt/               rc1.d/             selinux/           tuned/             
  cron.monthly/      gnupg/             libnl/             pam.d/             rc2.d/             skel/              udev/              
  cron.weekly/       grafana/           libreport/         pkcs11/            rc3.d/             ssh/               update-motd.d/     
  [root@t2 var]# cd /etc/openvpn/
  [root@t2 openvpn]# ls
  ca.crt  ccd            checkpsw.sh  crl.pem  openvpn-password.log  server       server_crt  server.key
  ca.key  cert_2023-208  client       ipp.txt  psw-file              server.conf  server.crt
  [root@t2 openvpn]# vim server.conf 
  [root@t2 openvpn]# ls
  ca.crt  ccd            checkpsw.sh  crl.pem  openvpn-password.log  server       server_crt  server.key
  ca.key  cert_2023-208  client       ipp.txt  psw-file              server.conf  server.crt
  [root@t2 openvpn]# cd cert_2023-208/
  [root@t2 cert_2023-208]# ls
  ca.crt  ca.key  crl.pem  easy-rsa  server.crt  server.key
  [root@t2 cert_2023-208]# cd easy-rsa/
  [root@t2 easy-rsa]# ls
  build      COPYING.md  doc       KNOWN_ISSUES  op_test.orig  README.md             release-keys  wop_test.sh
  ChangeLog  distro      easyrsa3  Licensing     op_test.sh    README.quickstart.md  wop_test.bat
  [root@t2 easy-rsa]# cd easyrsa3/
  [root@t2 easyrsa3]# ls
  easyrsa  openssl-easyrsa.cnf  pki  vars  vars.example  x509-types
  [root@t2 easyrsa3]# cd pki/
  [root@t2 pki]# ls
  ca.crt           crl.pem    index.txt.attr      index.txt.old  openssl-easyrsa.cnf  renewed  revoked              serial
  certs_by_serial  index.txt  index.txt.attr.old  issued         private              reqs     safessl-easyrsa.cnf  serial.old
  [root@t2 pki]# cd private/
  [root@t2 private]# ls
  ca.key  client.key  server.key
  [root@t2 private]# cd ..
  [root@t2 pki]# ls
  ca.crt           crl.pem    index.txt.attr      index.txt.old  openssl-easyrsa.cnf  renewed  revoked              serial
  certs_by_serial  index.txt  index.txt.attr.old  issued         private              reqs     safessl-easyrsa.cnf  serial.old
  [root@t2 pki]# cd ..
  [root@t2 easyrsa3]# ls
  easyrsa  openssl-easyrsa.cnf  pki  vars  vars.example  x509-types
  [root@t2 easyrsa3]# cd x509-types/
  [root@t2 x509-types]# ls
  ca  client  code-signing  COMMON  email  kdc  server  serverClient
  [root@t2 x509-types]# cd ..
  [root@t2 easyrsa3]# ls
  easyrsa  openssl-easyrsa.cnf  pki  vars  vars.example  x509-types
  [root@t2 easyrsa3]# ./easyrsa gen-dh

  Note: using Easy-RSA configuration from: /etc/openvpn/cert_2023-208/easy-rsa/easyrsa3/vars
  Using SSL: openssl OpenSSL 1.0.2k-fips  26 Jan 2017
  Generating DH parameters, 2048 bit long safe prime, generator 2
  This is going to take a long time
  ..........................................................................................................................................................................................................................................................................................................................................................+.......................+...................................+.........................+...............................+.............................................................................................+.....................................................................................................................+........................................................................................................................................+..........................................................+.....................................+.....................................................................................+.................+......................................+...................+.............................................................+...............+..................................................................................................................................+..............................................................................................................................................................................................................................................................................................................................................................................+...............................................+....................................................................+...................................................+.....................................................................................................................................................................................................................................................................................+....+.............................................................+..........................+.............................................................................................................................................................................+...................................+................................................................................+.......................................................................................................................................................+..............................................................................................................................+................................................................................+...+.............................................................+..........................+.................................................................................................................................................................................................................................................................................................................+..........................................................+....+...................................................................................+........................+.......................................................+......................................................................................+..............+...............................................................+..........+..............................................................................................................................................................................................................................................................................................+..........................................................................................................................................................................+..................................................................................................................................................+.................+..............................+..............................................................+......................................................+............................................+.......................................+.....................................................................................................................+.........................................................................+.....................................................+........................................+................................+.............................................................................................................................................................................................................................................................................................................................................................................................................................................................+...........................................................................................................+..............................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................+........................+....................................................................................................................................................................................+....................................+..........+................................................................................................................................+......+.+....+...............................................................+..................................................................................................................................................................+..............+......................................................+..............+........+....+.....................................................+.................................................................................................................................+.......................+....................................................................................................................................................................................................................................................................................+.......+.............................................................................................+..................................................................................+............................................................................+......................................................................+..+..........................................................................................+......................................................................+...........................+.....................................................................................................................................+..............................................................................................................................................+..........................................+.........+..........................+........................................................................................................................................................+...................................................................................................................+...................+.................................................................................................................................................................................................................................................................................+............+..........................+............+...............................................................................................................+...........................................................................................................................................+............................................................................................................................................................................................................................................................................................................................++*++*

  DH parameters of size 2048 created at /etc/openvpn/cert_2023-208/easy-rsa/easyrsa3/pki/dh.pem


  [root@t2 easyrsa3]# ls
  easyrsa  openssl-easyrsa.cnf  pki  vars  vars.example  x509-types
  [root@t2 easyrsa3]# cd pki/
  [root@t2 pki]# ls
  ca.crt           crl.pem  index.txt       index.txt.attr.old  issued               private  reqs     safessl-easyrsa.cnf  serial.old
  certs_by_serial  dh.pem   index.txt.attr  index.txt.old       openssl-easyrsa.cnf  renewed  revoked  serial
  [root@t2 pki]# cp dh.pem /etc/openvpn/
  [root@t2 pki]# cd ../../../
  [root@t2 cert_2023-208]# ls
  ca.crt  ca.key  crl.pem  easy-rsa  server.crt  server.key
  [root@t2 cert_2023-208]# cd ..
  [root@t2 openvpn]# ls
  ca.crt  ccd            checkpsw.sh  crl.pem  ipp.txt               psw-file  server.conf  server.crt
  ca.key  cert_2023-208  client       dh.pem   openvpn-password.log  server    server_crt   server.key
  [root@t2 openvpn]# vim server.conf 
  [root@t2 openvpn]# systemctl restart openvpn@server.service 
  [root@t2 openvpn]# systemctl status openvpn@server.service 
  ● openvpn@server.service - OpenVPN Robust And Highly Flexible Tunneling Application On server
     Loaded: loaded (/usr/lib/systemd/system/openvpn@.service; enabled; vendor preset: disabled)
     Active: active (running) since Fri 2023-03-17 15:44:05 CST; 8s ago
   Main PID: 2483 (openvpn)
     Status: "Initialization Sequence Completed"
     CGroup: /system.slice/system-openvpn.slice/openvpn@server.service
             └─2483 /usr/sbin/openvpn --cd /etc/openvpn/ --config server.conf

  Mar 17 15:44:05 t2 systemd[1]: Starting OpenVPN Robust And Highly Flexible Tunneling Application On server...
  Mar 17 15:44:05 t2 openvpn[2483]: Fri Mar 17 15:44:05 2023 DEPRECATED OPTION: --client-cert-not-required, use --verify-client-cert instead
  Mar 17 15:44:05 t2 systemd[1]: Started OpenVPN Robust And Highly Flexible Tunneling Application On server.
  [root@t2 openvpn]# 
  ```


title: ChatGPT: 调用PC摄像头并可以实时标注识别到的多个二维码或条形码
created_time: 2023-03-24 03:41:45.808509
intro:
  好的，以下是一个基于 Python 的程序，可以调用 PC 摄像头并实时标注识别到的多个二维码或条形码。在实现这个程序之前，你需要安装 OpenCV 和 pyzbar 库。OpenCV 是一个开源的计算机视觉库，可以用于图像处理和计算机视觉任务，而 pyzbar 则是一个 Python 的二维码和条形码识别库，基于 ZBar 库实现。

html_text:
  <h4 id="h4-chatgpt-pc-"><a name="ChatGPT 生成 调用PC摄像头并可以实时标注识别到的多个二维码或条形码" class="reference-link"></a><span class="header-link octicon octicon-link"></span>ChatGPT 生成 调用PC摄像头并可以实时标注识别到的多个二维码或条形码</h4><pre><code class="lang-python">import cv2
  from pyzbar import pyzbar

  # 打开摄像头
  cap = cv2.VideoCapture(0)

  while True:
      # 读取摄像头的图像
      ret, image = cap.read()

      # 对图像进行灰度处理
      gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

      # 识别图像中的二维码和条形码
      barcodes = pyzbar.decode(gray)

      # 遍历每一个二维码或条形码，进行标注
      for barcode in barcodes:
          # 获取二维码或条形码的位置和内容
          (x, y, w, h) = barcode.rect
          barcodeData = barcode.data.decode("utf-8")

          # 在图像上绘制标注框和文字
          cv2.rectangle(image, (x, y), (x + w, y + h), (0, 255, 0), 2)
          cv2.putText(image, barcodeData, (x, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)

      # 显示处理后的图像
      cv2.imshow("Barcode Scanner", image)

      # 等待按下 ESC 键退出程序
      if cv2.waitKey(1) == 27:
          break

  # 关闭摄像头和窗口
  cap.release()
  cv2.destroyAllWindows()
  </code></pre>

markdown_text:
  #### ChatGPT 生成 调用PC摄像头并可以实时标注识别到的多个二维码或条形码
  ````python
  import cv2
  from pyzbar import pyzbar

  # 打开摄像头
  cap = cv2.VideoCapture(0)

  while True:
      # 读取摄像头的图像
      ret, image = cap.read()

      # 对图像进行灰度处理
      gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

      # 识别图像中的二维码和条形码
      barcodes = pyzbar.decode(gray)

      # 遍历每一个二维码或条形码，进行标注
      for barcode in barcodes:
          # 获取二维码或条形码的位置和内容
          (x, y, w, h) = barcode.rect
          barcodeData = barcode.data.decode("utf-8")

          # 在图像上绘制标注框和文字
          cv2.rectangle(image, (x, y), (x + w, y + h), (0, 255, 0), 2)
          cv2.putText(image, barcodeData, (x, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)

      # 显示处理后的图像
      cv2.imshow("Barcode Scanner", image)

      # 等待按下 ESC 键退出程序
      if cv2.waitKey(1) == 27:
          break

  # 关闭摄像头和窗口
  cap.release()
  cv2.destroyAllWindows()
  ````


title: 代码记录: 三个excel合并 Copy HLink 整合输出
intro: 代码记录: 三个excel合并 Copy HLink 整合输出
created_time: 2023-05-15 00:32:25.877607
html_text:
  <pre><code class="lang-python">import copy
  import random

  import openpyxl
  from openpyxl import Workbook
  from openpyxl.styles import Font

  file1 = r'C:\Users\chaojie\Desktop\接口\API\工作表 在 SAIC-HY_智能制造数字化项目_MOM系统与底盘&amp;总装中控详细对接会议纪要-20230328.xlsx'
  file2 = r'C:\Users\chaojie\Desktop\接口\API\工作表 在 SAIC-HY_智能制造数字化项目_MOM系统与焊装中控详细对接会议纪要-20230323(1).xlsx'
  file3 = r'C:\Users\chaojie\Desktop\接口\API\工作表 在 SAIC-HY_智能制造数字化项目_MOM系统与涂装中控详细对接会议纪要-20230327(1).xlsx'
  file_list = [file1, file2, file3]
  wb = Workbook()
  to_name = 'API_DOC_{0}.xlsx'.format(random.randint(100, 10000))

  api_sheet = wb.create_sheet(title="接口目录")

  header = ['接口代码', '接口名称', '接口描述', '源系统', '目标系统', '消息协议']

  row_data = []

  sheet_code = []


  def get_sheet_name(code, idx):
      global sheet_code
      for item in sheet_code:
          if item['code'] == code and item['wb'] == idx:
              return item['sheet']
      return None


  def get_sheet(idx):
      items = []
      global sheet_code
      for item in sheet_code:
          if item['wb'] == idx:
              items.append(item)
      return items


  def get_row(code):
      global row_data
      for row in row_data:
          if row[0] == code:
              return row
      return None


  def get_row_already(data_list, code):
      for row in data_list:
          if row[1] == code:
              return True
      return False


  def get_all_work():
      data_list = []
      serial = 0
      for f_name in file_list:
          workbook = openpyxl.load_workbook(f_name)
          sheet1 = workbook['接口目录']
          rows = sheet1.max_row
          # 获取工作表总列数
          cols = sheet1.max_column
          # sheet_code.append(
          #     {'code': cell_value, 'wb': file_list.index(f_name),
          #      'sheet': str(sheet1.cell(row=row_index, column=i).hyperlink.location).split('!')[0]})

          for row_index in range(4, rows):
              sheet_obj = {'code': None, 'wb': file_list.index(f_name),
                           'sheet': None, 'sheet_name': None}
              is_hl_obj = False
              row_data = []
              for i in range(1, cols):
                  cell_value = sheet1.cell(row=row_index, column=i).value
                  if i == 6:
                      if (cell_value is None or cell_value == '') or get_row_already(data_list, cell_value):
                          break
                      serial += 1
                      if (serial + 1) &gt; 10:
                          row_data.append('IF_IOT_0' + str(serial))
                      else:
                          row_data.append('IF_IOT_00' + str(serial))

                      row_data.append(cell_value)
                      hl_obj = sheet1.cell(row=row_index, column=i).hyperlink
                      if hl_obj:
                          sheet_obj['code'] = row_data[0]
                          sheet_obj['sheet'] = str(sheet1.cell(row=row_index, column=i).hyperlink.location).split('!')[0]
                          sheet_obj['sheet_name'] = f"{row_data[0]}_{cell_value}".replace(
                              '/', ' ')
                          is_hl_obj = True
                  if i == 7:
                      row_data.append(cell_value)
                  if i == 11:
                      row_data.append(cell_value)
                  if i == 13:
                      row_data.append(cell_value)
                  if i == 19:
                      row_data.append(cell_value)

              if len(row_data) &gt; 0:
                  data_list.append(row_data)
              if is_hl_obj:
                  sheet_code.append(sheet_obj)
      # for idx in range(0, len(data_list)):
      #     if idx &gt; 8:
      #         data_list[idx].insert(0, 'IF_IOT_0' + str(idx + 1))
      #     else:
      #         data_list[idx].insert(0, 'IF_IOT_00' + str(idx + 1))
      return data_list


  def save_to_sheet1():
      global row_data
      api_sheet.append(header)
      for row in row_data:
          api_sheet.append(row)
      for row in range(0, len(row_data)):
          cell = api_sheet.cell(row=row + 2, column=1)
          sheet_name = f"{api_sheet.cell(row=row + 2, column=1).value}_{api_sheet.cell(row=row + 2, column=2).value}".replace(
              '/', ' ')
          link = f"{to_name}#{sheet_name}!A1"
          if cell is not None:
              cell.hyperlink = (link)
              cell.style = 'Hyperlink'
      # set link


  def generate():
      for f_name in file_list:
          wb1 = openpyxl.load_workbook(f_name)
          file_idx = file_list.index(f_name)
          sheets = get_sheet(file_idx)
          for sheet_obj in sheets:
              rows = get_row(sheet_obj['code'])
              sheet = wb.create_sheet(sheet_obj['sheet_name'])
              sheet.column_dimensions['K'].width = 120
              sheet.column_dimensions['L'].width = 50
              sheet.column_dimensions['M'].width = 50
              sheet.column_dimensions['E'].width = 80

              sheet['a1'] = '接口代码'
              sheet['a2'] = '接口名称'
              sheet['a1'].font = Font(bold=True)
              sheet['a2'].font = Font(bold=True)
              sheet['b1'] = rows[0]
              sheet['b2'] = rows[1]
              sheet['b1'].font = Font(bold=True)
              sheet['b2'].font = Font(bold=True)

              sheet['a3'] = '调用方   '
              sheet['a4'] = '被调用方  '
              sheet['a5'] = '通讯方式  '
              sheet['a6'] = '请求地址  '
              sheet['a7'] = '说明     '

              sheet['b3'] = rows[3]
              sheet['b4'] = rows[4]
              sheet['b5'] = rows[5]
              sheet['b6'] = ''
              sheet['b7'] = rows[2]
              sheet['d1'] = '返回'
              link = f"{to_name}#接口目录!A1"
              sheet['d1'].hyperlink = (link)
              sheet['d1'].style = 'Hyperlink'
              api_sheet = None
              try:
                  api_sheet = wb1.get_sheet_by_name(sheet_obj['sheet'])
              except KeyError as ke:
                  pass
              if api_sheet is None:
                  pass
              else:
                  mr = api_sheet.max_row
                  mc = api_sheet.max_column
                  for i in range(5, mr + 1):
                      for j in range(1, mc + 1):

                          c = api_sheet.cell(row=i, column=j)
                          sheet.cell(row=i + 5, column=j).value = c.value
                          sheet.cell(row=i + 5, column=j).fill = copy.copy(c.fill)

                          if c.has_style:
                              sheet.cell(row=i + 5, column=j)._style = copy.copy(c._style)
                              sheet.cell(row=i + 5, column=j).font = copy.copy(c.font)
                              sheet.cell(row=i + 5, column=j).border = copy.copy(c.border)
                              sheet.cell(row=i + 5, column=j).fill = copy.copy(c.fill)
                              sheet.cell(row=i + 5, column=j).number_format = copy.copy(c.number_format)
                              sheet.cell(row=i + 5, column=j).protection = copy.copy(c.protection)
                              sheet.cell(row=i + 5, column=j).alignment = copy.copy(c.alignment)
                  # area1 = CellRange("c11:c28")  # area being copied
                  # area2 = CellRange("d12:d28")  # area being copied
                  # area3 = CellRange("c24:c28")  # area being copied
                  # area4 = CellRange("d24:d28")  # area being copied

                  sheet.merge_cells(range_string='c11:c28')
                  sheet.merge_cells(range_string='d11:d14')
                  sheet.merge_cells(range_string='d15:d28')
                  sheet.merge_cells(range_string='c29:c33')
                  sheet.merge_cells(range_string='d29:d33')


  if __name__ == '__main__':
      row_data = get_all_work()
      print(row_data)
      save_to_sheet1()
      print(sheet_code)
      # print(len(sheet_code))
      generate()
      wb.save(to_name)
  </code></pre>

markdown_text:
  ```python
  import copy
  import random

  import openpyxl
  from openpyxl import Workbook
  from openpyxl.styles import Font

  file1 = r'C:\Users\chaojie\Desktop\接口\API\工作表 在 SAIC-HY_智能制造数字化项目_MOM系统与底盘&总装中控详细对接会议纪要-20230328.xlsx'
  file2 = r'C:\Users\chaojie\Desktop\接口\API\工作表 在 SAIC-HY_智能制造数字化项目_MOM系统与焊装中控详细对接会议纪要-20230323(1).xlsx'
  file3 = r'C:\Users\chaojie\Desktop\接口\API\工作表 在 SAIC-HY_智能制造数字化项目_MOM系统与涂装中控详细对接会议纪要-20230327(1).xlsx'
  file_list = [file1, file2, file3]
  wb = Workbook()
  to_name = 'API_DOC_{0}.xlsx'.format(random.randint(100, 10000))

  api_sheet = wb.create_sheet(title="接口目录")

  header = ['接口代码', '接口名称', '接口描述', '源系统', '目标系统', '消息协议']

  row_data = []

  sheet_code = []


  def get_sheet_name(code, idx):
      global sheet_code
      for item in sheet_code:
          if item['code'] == code and item['wb'] == idx:
              return item['sheet']
      return None


  def get_sheet(idx):
      items = []
      global sheet_code
      for item in sheet_code:
          if item['wb'] == idx:
              items.append(item)
      return items


  def get_row(code):
      global row_data
      for row in row_data:
          if row[0] == code:
              return row
      return None


  def get_row_already(data_list, code):
      for row in data_list:
          if row[1] == code:
              return True
      return False


  def get_all_work():
      data_list = []
      serial = 0
      for f_name in file_list:
          workbook = openpyxl.load_workbook(f_name)
          sheet1 = workbook['接口目录']
          rows = sheet1.max_row
          # 获取工作表总列数
          cols = sheet1.max_column
          # sheet_code.append(
          #     {'code': cell_value, 'wb': file_list.index(f_name),
          #      'sheet': str(sheet1.cell(row=row_index, column=i).hyperlink.location).split('!')[0]})

          for row_index in range(4, rows):
              sheet_obj = {'code': None, 'wb': file_list.index(f_name),
                           'sheet': None, 'sheet_name': None}
              is_hl_obj = False
              row_data = []
              for i in range(1, cols):
                  cell_value = sheet1.cell(row=row_index, column=i).value
                  if i == 6:
                      if (cell_value is None or cell_value == '') or get_row_already(data_list, cell_value):
                          break
                      serial += 1
                      if (serial + 1) > 10:
                          row_data.append('IF_IOT_0' + str(serial))
                      else:
                          row_data.append('IF_IOT_00' + str(serial))

                      row_data.append(cell_value)
                      hl_obj = sheet1.cell(row=row_index, column=i).hyperlink
                      if hl_obj:
                          sheet_obj['code'] = row_data[0]
                          sheet_obj['sheet'] = str(sheet1.cell(row=row_index, column=i).hyperlink.location).split('!')[0]
                          sheet_obj['sheet_name'] = f"{row_data[0]}_{cell_value}".replace(
                              '/', ' ')
                          is_hl_obj = True
                  if i == 7:
                      row_data.append(cell_value)
                  if i == 11:
                      row_data.append(cell_value)
                  if i == 13:
                      row_data.append(cell_value)
                  if i == 19:
                      row_data.append(cell_value)

              if len(row_data) > 0:
                  data_list.append(row_data)
              if is_hl_obj:
                  sheet_code.append(sheet_obj)
      # for idx in range(0, len(data_list)):
      #     if idx > 8:
      #         data_list[idx].insert(0, 'IF_IOT_0' + str(idx + 1))
      #     else:
      #         data_list[idx].insert(0, 'IF_IOT_00' + str(idx + 1))
      return data_list


  def save_to_sheet1():
      global row_data
      api_sheet.append(header)
      for row in row_data:
          api_sheet.append(row)
      for row in range(0, len(row_data)):
          cell = api_sheet.cell(row=row + 2, column=1)
          sheet_name = f"{api_sheet.cell(row=row + 2, column=1).value}_{api_sheet.cell(row=row + 2, column=2).value}".replace(
              '/', ' ')
          link = f"{to_name}#{sheet_name}!A1"
          if cell is not None:
              cell.hyperlink = (link)
              cell.style = 'Hyperlink'
      # set link


  def generate():
      for f_name in file_list:
          wb1 = openpyxl.load_workbook(f_name)
          file_idx = file_list.index(f_name)
          sheets = get_sheet(file_idx)
          for sheet_obj in sheets:
              rows = get_row(sheet_obj['code'])
              sheet = wb.create_sheet(sheet_obj['sheet_name'])
              sheet.column_dimensions['K'].width = 120
              sheet.column_dimensions['L'].width = 50
              sheet.column_dimensions['M'].width = 50
              sheet.column_dimensions['E'].width = 80

              sheet['a1'] = '接口代码'
              sheet['a2'] = '接口名称'
              sheet['a1'].font = Font(bold=True)
              sheet['a2'].font = Font(bold=True)
              sheet['b1'] = rows[0]
              sheet['b2'] = rows[1]
              sheet['b1'].font = Font(bold=True)
              sheet['b2'].font = Font(bold=True)

              sheet['a3'] = '调用方   '
              sheet['a4'] = '被调用方  '
              sheet['a5'] = '通讯方式  '
              sheet['a6'] = '请求地址  '
              sheet['a7'] = '说明     '

              sheet['b3'] = rows[3]
              sheet['b4'] = rows[4]
              sheet['b5'] = rows[5]
              sheet['b6'] = ''
              sheet['b7'] = rows[2]
              sheet['d1'] = '返回'
              link = f"{to_name}#接口目录!A1"
              sheet['d1'].hyperlink = (link)
              sheet['d1'].style = 'Hyperlink'
              api_sheet = None
              try:
                  api_sheet = wb1.get_sheet_by_name(sheet_obj['sheet'])
              except KeyError as ke:
                  pass
              if api_sheet is None:
                  pass
              else:
                  mr = api_sheet.max_row
                  mc = api_sheet.max_column
                  for i in range(5, mr + 1):
                      for j in range(1, mc + 1):

                          c = api_sheet.cell(row=i, column=j)
                          sheet.cell(row=i + 5, column=j).value = c.value
                          sheet.cell(row=i + 5, column=j).fill = copy.copy(c.fill)

                          if c.has_style:
                              sheet.cell(row=i + 5, column=j)._style = copy.copy(c._style)
                              sheet.cell(row=i + 5, column=j).font = copy.copy(c.font)
                              sheet.cell(row=i + 5, column=j).border = copy.copy(c.border)
                              sheet.cell(row=i + 5, column=j).fill = copy.copy(c.fill)
                              sheet.cell(row=i + 5, column=j).number_format = copy.copy(c.number_format)
                              sheet.cell(row=i + 5, column=j).protection = copy.copy(c.protection)
                              sheet.cell(row=i + 5, column=j).alignment = copy.copy(c.alignment)
                  # area1 = CellRange("c11:c28")  # area being copied
                  # area2 = CellRange("d12:d28")  # area being copied
                  # area3 = CellRange("c24:c28")  # area being copied
                  # area4 = CellRange("d24:d28")  # area being copied

                  sheet.merge_cells(range_string='c11:c28')
                  sheet.merge_cells(range_string='d11:d14')
                  sheet.merge_cells(range_string='d15:d28')
                  sheet.merge_cells(range_string='c29:c33')
                  sheet.merge_cells(range_string='d29:d33')


  if __name__ == '__main__':
      row_data = get_all_work()
      print(row_data)
      save_to_sheet1()
      print(sheet_code)
      # print(len(sheet_code))
      generate()
      wb.save(to_name)

  ```


title: 新的博客网站设计
created_time: 2023-07-20 12:20:04.789469
intro:
  最近申请到了觉得很棒的域名: CyberSicko.net 可惜.com的已经被注册了，在回头看看以前的老博客，觉得顿时丑的不能看，配不上这么酷的域名。
  于是这个网站诞生了，牺牲了我一周末的时间开发完主要功能。

html_text:
  <p style="text-align: justify;"><span style="font-size: 19px;">最近申请到了觉得很棒的域名: CyberSicko.net 可惜.com的已经被注册了，在回头看看以前的老博客，觉得顿时丑的不能看，配不上这么酷的域名。</span></p><p style="text-align: justify;"><span style="font-size: 19px;">于是这个网站诞生了，牺牲了我一周末的时间开发完主要功能。</span></p><h3 style="text-align: start;">开发框架</h3><p style="text-align: justify;"><span style="font-size: 19px;">后端: Python Django Django-avatar Sqlite </span></p><p style="text-align: justify;"><span style="font-size: 19px;">网页设计UI框架:</span><a href=" https://semantic-ui.com/ " target="_blank"><span style="font-size: 19px;"> </span></a><a href="https://semantic-ui.com/ " target="_blank"><span style="font-size: 19px;">semanticui</span></a></p><p style="text-align: justify;"><span style="font-size: 19px;">网页编辑器: </span><a href=" https://www.wangeditor.com/demo/index.html " target="_blank"><span style="font-size: 19px;">wangeditor</span></a></p><h3 style="text-align: start;">介绍</h3><p style="text-align: justify;"><span style="font-size: 19px;">主页如下</span></p><p style="text-align: justify;"><img alt="1920,986" src="/upload/files/png/f18331fbf18f548e34ce1aa0d0f60ad3.png"></p><p style="text-align: justify;"><span style="font-size: 19px;">在开发博客页面的时候，我在想该以什么方式来呈现博客的内容，才能让博客看上去简约而又不古板，于是就有了上面的页面。将博客内容以强调时间和用户存在感的方式呈现出来，给了一种有社交感，不再像以往博客的条目那么古板，多了一些灵活而又不会变得复杂无序。(当然是没有社交功能的)</span></p><p style="text-align: justify;"><span style="font-size: 19px;">和传统博客不同的事，传统博客的内容太重了，你基本只能在里面写长篇大论，如果一个博客就短短几句话都会显得有些违和，现在的人总会有类似朋友圈的需求，发一发心情或图片的分享，所以我做了动态分享：</span></p><p style="text-align: justify;"><img alt="1365,790" src="/upload/files/png/8b04ae20dacba832044258cbbf256d5c.png"></p><p style="text-align: justify;"><span style="font-size: 19px;">在没有社交的网站分享类似朋友圈的信息谁来看呢？</span></p><p style="text-align: justify;"><span style="font-size: 19px;">比如我就有这个需求，只有一个微信号，也不想去设置维护什么不可见标签，干脆眼不见心不烦不发了，但在这里我可以毫无顾忌，简单点就是：</span></p><p style="text-align: justify;"><span style="font-size: 19px;"><strong>这是我的地盘。</strong></span></p><p style="text-align: justify;"><span style="font-size: 19px;">而且没人会评论你。</span></p><p style="text-align: justify;"><span style="font-size: 19px;">这里既可以分享长篇大论，也可以分享简短的文字，简直是心灵后花园的不二之选。</span></p><h3 style="text-align: start;">后台管理</h3><p style="text-align: justify;"><span style="font-size: 19px;">后台管理拥有基本的博客内容管理，Web编辑器采用wangeditor编辑器，这款编辑器的高仿腾讯文档demo放我眼前一亮</span></p><p style="text-align: justify;"><img alt="1920,986" src="/upload/files/png/2ca3fa5a0c2952c9c56915934c8d13d4.png"></p><p style="text-align: justify;"><span style="font-size: 19px;">对于一个爱好写文字的人来说，一个干净舒适的码字环境绝对能提高写文章时的幸福感。</span></p><p style="text-align: justify;"><span style="font-size: 19px;">后台管理上基本就这些内容。</span></p><p style="text-align: justify;"><img alt="1920,986" src="/upload/files/png/9499b2632db39230a3e74b25b8f416ff.png"></p><h3 style="text-align: start;">开源地址</h3><p style="text-align: justify;"><a href="http://118.89.119.129:3000/yangchaojie/Blog " target="_blank"><span style="font-size: 19px;">http://118.89.119.129:3000/yangchaojie/Blog </span></a></p><p style="text-align: justify;"><span style="font-size: 19px;">clone 代码到本地，使用pipenv install安装运行环境，pipenv 版本为: pipenv, version 2021.11.23</span></p><p style="text-align: justify;"><br></p><h2 style="text-align: justify;"><span style="font-size: 19px;">Issue</span></h2><p><span style="font-size: 19px;">写完发布的时候，在选标签的输入框按了回车，结果幸幸苦苦写的全没了，卧槽，搞得心情特别差，马上把form回车自动提交的功能给加上了，还有我发现缩放页面可能会导致上面俩按钮消失。</span></p><p><span style="font-size: 19px;">我觉得有必要做一个草稿功能，还有将内容实时更新在localstroage里面，以防止意外导致发生糟糕的情绪。</span></p><p><br></p><p><span style="font-size: 19px;">end.</span></p><p style="text-align: justify;"><br></p><p style="text-align: justify;"><span style="font-size: 19px;">截止 2023 7月26日 更新功能：</span></p><ol><li style="text-align: justify;"><span style="font-size: 19px;">写文章加入恢复功能，当文章字数超过50将在localStroage实时记录。</span></li></ol><p style="text-align: justify;"><img alt="376,61" src="/upload/files/png/1277a386322cd8c1655c4ad19666d131.png"></p><p style="text-align: justify;"><span style="font-size: 19px;">2. &nbsp;草稿箱功能，草稿状态的文字不会在页面显示。</span></p><p style="text-align: justify;"><span style="font-size: 19px;">3. &nbsp;主页加入查询功能。</span></p><p style="text-align: justify;"><span style="font-size: 19px;">4. &nbsp;修复上传的图片在更新文章界面不显示问题。</span></p><p style="text-align: justify;"><span style="font-size: 19px;">5. &nbsp;加入 blockquote 样式</span></p><p style="text-align: justify;"><span style="font-size: 19px;">6. &nbsp;修复归档错乱</span></p><p style="text-align: justify;"><br></p><p style="text-align: justify;"><br></p><p style="text-align: justify;"><br></p>

markdown_text:
  最近申请到了觉得很棒的域名: CyberSicko.net 可惜.com的已经被注册了，在回头看看以前的老博客，觉得顿时丑的不能看，配不上这么酷的域名。
  于是这个网站诞生了，牺牲了我一周末的时间开发完主要功能。
  开发框架
  后端: Python Django Django-avatar Sqlite 
  网页设计UI框架: semanticui
  网页编辑器: wangeditor
  介绍
  主页如下

  在开发博客页面的时候，我在想该以什么方式来呈现博客的内容，才能让博客看上去简约而又不古板，于是就有了上面的页面。将博客内容以强调时间和用户存在感的方式呈现出来，给了一种有社交感，不再像以往博客的条目那么古板，多了一些灵活而又不会变得复杂无序。(当然是没有社交功能的)
  和传统博客不同的事，传统博客的内容太重了，你基本只能在里面写长篇大论，如果一个博客就短短几句话都会显得有些违和，现在的人总会有类似朋友圈的需求，发一发心情或图片的分享，所以我做了动态分享：

  在没有社交的网站分享类似朋友圈的信息谁来看呢？
  比如我就有这个需求，只有一个微信号，也不想去设置维护什么不可见标签，干脆眼不见心不烦不发了，但在这里我可以毫无顾忌，简单点就是：
  这是我的地盘。
  而且没人会评论你。
  这里既可以分享长篇大论，也可以分享简短的文字，简直是心灵后花园的不二之选。
  后台管理
  后台管理拥有基本的博客内容管理，Web编辑器采用wangeditor编辑器，这款编辑器的高仿腾讯文档demo放我眼前一亮

  对于一个爱好写文字的人来说，一个干净舒适的码字环境绝对能提高写文章时的幸福感。
  后台管理上基本就这些内容。

  开源地址
  http://118.89.119.129:3000/yangchaojie/Blog 
  clone 代码到本地，使用pipenv install安装运行环境，pipenv 版本为: pipenv, version 2021.11.23

  Issue
  写完发布的时候，在选标签的输入框按了回车，结果幸幸苦苦写的全没了，卧槽，搞得心情特别差，马上把form回车自动提交的功能给加上了，还有我发现缩放页面可能会导致上面俩按钮消失。
  我觉得有必要做一个草稿功能，还有将内容实时更新在localstroage里面，以防止意外导致发生糟糕的情绪。

  end.

  截止 2023 7月26日 更新功能：
  写文章加入恢复功能，当文章字数超过50将在localStroage实时记录。

  2.  草稿箱功能，草稿状态的文字不会在页面显示。
  3.  主页加入查询功能。
  4.  修复上传的图片在更新文章界面不显示问题。
  5.  加入 blockquote 样式
  6.  修复归档错乱


title: 楔子
markdown_text: 运输区盘根错节的悬浮轨道像是织在天空的蜘蛛网，载人列车和重型货运列车在这张网上飞速的穿梭，噪音不是很大，但在很远的地方也能不间断的听到快速穿行留下的电机声。
created_time: 2023-07-26 02:02:48.738809
intro:
  运输区盘根错节的悬浮轨道像是织在天空的蜘蛛网，载人列车和重型货运列车在这张网上飞速的穿梭，噪音不是很大，但在很远的地方也能不间断的听到快速穿行留下的电机声。肖静坐在前往环球科技公司的专属员工列车，作为管理层，她拥有一个独属于她的专用车厢

html_text:
  <p><span style="font-size: 19px; font-family: 仿宋;"> &nbsp; &nbsp;运输区盘根错节的悬浮轨道像是织在天空的蜘蛛网，载人列车和重型货运列车在这张网上飞速的穿梭，噪音不是很大，但在很远的地方也能不间断的听到快速穿行留下的电机声。</span></p><p><span style="font-size: 19px; font-family: 仿宋;"> &nbsp; &nbsp;</span></p>


title: 生死边缘
intro: 黑暗双子大纲
created_time: 2023-07-26 07:17:56.038977
html_text:
  <p style="text-align: justify;"><span style="font-size: 19px; font-family: 楷体;"> &nbsp; &nbsp;2090年，蓝天生命基因研发出可以让人体内细胞停止老化的药剂，注射药剂的人需要定期购买‘T’药剂吸收进行维持，否则将会在大约5年内器官衰竭而亡。</span></p><p style="text-align: justify;"><span style="font-size: 19px; font-family: 楷体;"> &nbsp; &nbsp;全世界发生了翻天覆地的变化，有些事情是可想而知的，比如绝症患者被注射药剂进行封存，富人疯狂的购买永生药剂和T药剂，某国的总统将任期排到了100年以后，随之而来的是贫富差距进一步加剧，各国宪法开始频繁变更，衍变出更多的社会问题，解决这些问题最简单的方式就是镇压，武力镇压，暴力镇压，社会上死气沉沉，历史最高压政治的时代来临。</span></p><p style="text-align: justify;"><span style="font-size: 19px; font-family: 楷体;"> &nbsp; &nbsp;霓虹港。</span></p><p style="text-align: justify;"><span style="font-size: 19px; font-family: 楷体;"> &nbsp; &nbsp;运输区盘根错节的悬浮轨道像是织在天空的蜘蛛网，载人列车和重型货运列车在这张网上飞速的穿梭，噪音不是很大，但在很远的地方也能不间断的听到快速穿行留下的电机声。</span></p><p style="text-align: justify;"><span style="font-size: 19px; font-family: 楷体;"> &nbsp; &nbsp;夜晚十点的西街十六号线普通仓车厢内乘客廖廖，徐云坐在靠窗的座位，周围安静清冷的环境让他放松下来，看着窗外怔怔出神。借着到站的停顿，他看到了对面列车飞快驶过，通过车门上透明的玻璃依稀看到那趟车挤满了人。</span></p><p style="text-align: justify;"><span style="font-size: 19px; font-family: 楷体;"> &nbsp; &nbsp;那些是从霓虹市下班的人，他们住在棚户区，也就是西街。</span></p><p style="text-align: justify;"><span style="font-size: 19px; font-family: 楷体;"> &nbsp; &nbsp;快到站了，徐云眼前豁然开朗，冲天的高楼大厦鳞次栉比，炫目的灯光和3D虚拟影像全方位的投射在霓虹港半空的每个角落，再向远望去是一望无际的黑。徐云感觉有些恐慌，他不知道缘由。</span></p><p style="text-align: justify;"><span style="font-size: 19px; font-family: 楷体;"> &nbsp; &nbsp;徐云独自走出车站，空气中弥漫的与西街不同的气味，西街那的人称作钱味，是由消毒水和类似清新剂等多种气味糅杂混合后的气味，对于不习惯的人来说很容易引起反胃。虚拟的绝色女郎踩着妖娆的猫步走了过来，对着徐云做出性暗示的挑逗动作，做满美甲的手指离开红唇，带起一串闪光的粒子，形成了广告词：超级梦之夜，欢迎您的莅临。</span></p><p style="text-align: justify;"><span style="font-size: 19px; font-family: 楷体;"> &nbsp; &nbsp;此时的车站前人不是很多，许多虚拟广告人都盯上了徐云，他赶紧加快了脚步，越往这座城市的深处走他心里的忧虑就加重一分，他的女友约他今晚过来见面，对于见面理由她没有说，只留下一句：先见面吧，见面再说。</span></p><p style="text-align: justify;"><span style="font-size: 19px; font-family: 楷体;"> &nbsp; &nbsp;约定地点在珍珠海湾，这里在霓虹港算是为数不多的安静场所，是一片修在大海上面的观光点，沿着镶着珍珠圆灯护栏旁的走道，徐云看到了两个星期未见的女友。</span></p><p style="text-align: justify;"><span style="font-size: 19px; font-family: 楷体;"> &nbsp; &nbsp;徐云第一眼就看到了她小臂上的吸收器，这个略有厚度的浅蓝四边形装置是蓝天电子生产的T药剂吸收装置，内置新款的器官芯片，能实时监测人体内T药剂需求量和身体各器官数据，对于徐云来说，这个小东西像是一记重锤，把徐云锤的头晕目眩。</span></p><p style="text-align: justify;"><span style="font-size: 19px; font-family: 楷体;"> &nbsp; &nbsp;“徐云，你也看到了，抱歉之前瞒着你，那天我和客人喝醉了，迷迷糊糊就被打了药剂，等我醒来，事情已经无法挽回了。”</span></p><p style="text-align: justify;"><span style="font-size: 19px; font-family: 楷体;"> &nbsp; &nbsp;徐云盯着她，感受着加快的心率和慢慢涌起的悲伤情绪：</span></p><p style="text-align: justify;"><span style="font-size: 19px; font-family: 楷体;"> &nbsp; &nbsp;“告诉我实话。”</span></p><p style="text-align: justify;"><span style="font-size: 19px; font-family: 楷体;"> &nbsp; &nbsp;“这就是实话。”</span></p><p style="text-align: justify;"><span style="font-size: 19px; font-family: 楷体;"> &nbsp; &nbsp;沉默了一会，徐云问她：</span></p><p style="text-align: justify;"><span style="font-size: 19px; font-family: 楷体;"> &nbsp; &nbsp;“那你现在要跟着他吗？”</span></p><p style="text-align: justify;"><span style="font-size: 19px; font-family: 楷体;"> &nbsp; &nbsp;她提升了音量，有些激动：</span></p><p style="text-align: justify;"><span style="font-size: 19px; font-family: 楷体;"> &nbsp; &nbsp;“那我又有什么办法！我会死的！而且这是永生，我会永远活下去，我们已经不是一个世界的人了，西街那个地方我也不会再去了。”</span></p><p style="text-align: justify;"><span style="font-size: 19px; font-family: 楷体;"> &nbsp; &nbsp;徐云脑海里蹦出了很多承诺，比如：“我去杀了他帮你报仇”，“跟我走吧，我不会让你死的”之类的，但他没有说，他狠狠的在心里嘲笑了下自己的妄想。</span></p><p style="text-align: justify;"><span style="font-size: 19px; font-family: 楷体;"> &nbsp; &nbsp;她从包里拿出了两盒T药剂，递给徐云：</span></p><p style="text-align: justify;"><span style="font-size: 19px; font-family: 楷体;"> &nbsp; &nbsp;“这是一年的量，给你姐姐用吧，我还有。”</span></p><p style="text-align: justify;"><span style="font-size: 19px; font-family: 楷体;"> &nbsp; &nbsp;徐云接过T药剂放到了口袋，忍不住笑了起来：</span></p><p style="text-align: justify;"><span style="font-size: 19px; font-family: 楷体;"> &nbsp; &nbsp;“那再见了！”</span></p><p style="text-align: justify;"><span style="font-size: 19px; font-family: 楷体;"> &nbsp; &nbsp;说完他头也不回的走了，但更像是落荒而逃。</span></p>

markdown_text:
      2090年，蓝天生命基因研发出可以让人体内细胞停止老化的药剂，注射药剂的人需要定期购买‘T’药剂吸收进行维持，否则将会在大约5年内器官衰竭而亡。
      全世界发生了翻天覆地的变化，有些事情是可想而知的，比如绝症患者被注射药剂进行封存，富人疯狂的购买永生药剂和T药剂，某国的总统将任期排到了100年以后，随之而来的是贫富差距进一步加剧，各国宪法开始频繁变更，衍变出更多的社会问题，解决这些问题最简单的方式就是镇压，武力镇压，暴力镇压，社会上死气沉沉，历史最高压政治的时代来临。
      霓虹港。
      运输区盘根错节的悬浮轨道像是织在天空的蜘蛛网，载人列车和重型货运列车在这张网上飞速的穿梭，噪音不是很大，但在很远的地方也能不间断的听到快速穿行留下的电机声。
      夜晚十点的西街十六号线普通仓车厢内乘客廖廖，徐云坐在靠窗的座位，周围安静清冷的环境让他放松下来，看着窗外怔怔出神。借着到站的停顿，他看到了对面列车飞快驶过，通过车门上透明的玻璃依稀看到那趟车挤满了人。
      那些是从霓虹市下班的人，他们住在棚户区，也就是西街。
      快到站了，徐云眼前豁然开朗，冲天的高楼大厦鳞次栉比，炫目的灯光和3D虚拟影像全方位的投射在霓虹港半空的每个角落，再向远望去是一望无际的黑。徐云感觉有些恐慌，他不知道缘由。
      徐云独自走出车站，空气中弥漫的与西街不同的气味，西街那的人称作钱味，是由消毒水和类似清新剂等多种气味糅杂混合后的气味，对于不习惯的人来说很容易引起反胃。虚拟的绝色女郎踩着妖娆的猫步走了过来，对着徐云做出性暗示的挑逗动作，做满美甲的手指离开红唇，带起一串闪光的粒子，形成了广告词：超级梦之夜，欢迎您的莅临。
      此时的车站前人不是很多，许多虚拟广告人都盯上了徐云，他赶紧加快了脚步，越往这座城市的深处走他心里的忧虑就加重一分，他的女友约他今晚过来见面，对于见面理由她没有说，只留下一句：先见面吧，见面再说。
      约定地点在珍珠海湾，这里在霓虹港算是为数不多的安静场所，是一片修在大海上面的观光点，沿着镶着珍珠圆灯护栏旁的走道，徐云看到了两个星期未见的女友。
      徐云第一眼就看到了她小臂上的吸收器，这个略有厚度的浅蓝四边形装置是蓝天电子生产的T药剂吸收装置，内置新款的器官芯片，能实时监测人体内T药剂需求量和身体各器官数据，对于徐云来说，这个小东西像是一记重锤，把徐云锤的头晕目眩。
      “徐云，你也看到了，抱歉之前瞒着你，那天我和客人喝醉了，迷迷糊糊就被打了药剂，等我醒来，事情已经无法挽回了。”
      徐云盯着她，感受着加快的心率和慢慢涌起的悲伤情绪：
      “告诉我实话。”
      “这就是实话。”
      沉默了一会，徐云问她：
      “那你现在要跟着他吗？”
      她提升了音量，有些激动：
      “那我又有什么办法！我会死的！而且这是永生，我会永远活下去，我们已经不是一个世界的人了，西街那个地方我也不会再去了。”
      徐云脑海里蹦出了很多承诺，比如：“我去杀了他帮你报仇”，“跟我走吧，我不会让你死的”之类的，但他没有说，他狠狠的在心里嘲笑了下自己的妄想。
      她从包里拿出了两盒T药剂，递给徐云：
      “这是一年的量，给你姐姐用吧，我还有。”
      徐云接过T药剂放到了口袋，忍不住笑了起来：
      “那再见了！”
      说完他头也不回的走了，但更像是落荒而逃。


title: 一个人住的风水相学
intro: 教你看风水
created_time: 2023-08-03 03:24:35.885946
html_text:
  <p><span style="font-size: 19px;">在风水相术，道家学说，阴阳当中，几乎都是围绕着一个字来进行展开的，这个字也在国粹文化中频频出现，那就是：</span></p><p style="text-align: center;"><img alt="350,350" src="https://c.cidianwang.com/file/shufa/xingshu/80b6983fbabac0e9.gif"></p><p style="text-align: left;"><span style="font-size: 19px;">关于‘气’：</span></p><p style="text-align: left;"><span style="font-size: 19px;">国棋围棋中有个基本概念：</span></p><p style="text-align: center;"><img src="https://5b0988e595225.cdn.sohucs.com/q_70,c_zoom,w_640/images/20180616/34ea5913cbcb4c3598d61c1f65ff896d.jpeg" alt=""></p><p style="text-align: left;"><span style="font-size: 19px;">一个棋子如果被敌对棋子把气堵了，那么这个棋子就‘没气’了，也就死了，可以从棋盘那掉，人也同样如此，比如一个人死了，那么有人可能会这么说：‘这人没气了’。</span></p><p style="text-align: left;"><span style="font-size: 19px;">所以气对人来说至关重要，人的呼气吸气吐纳方式是对天地的连接，这个连接一断，人就没了，这是最外层关于气的解释。</span></p><p style="text-align: left;"><span style="font-size: 19px;">接着就是人身上的‘内气’。</span></p><p style="text-align: left;"><span style="font-size: 19px;">气与对生物进行的连接息息相关，通俗的解释就是气场，也可以理解为存在感。</span></p><p style="text-align: left;"><span style="font-size: 19px;">一个频频出现在公众视野的企业家，政府官员，他们的气就比普通人要强，存在感也强，因为他与其他人进行了连接，许多人都需要他，知道他，这就形成了一种气加身。</span></p><p style="text-align: left;"><span style="font-size: 19px;">普通人身体健康，家庭圆满，也有气，不强但充盈。</span></p><p style="text-align: left;"><span style="font-size: 19px;">如果一个人在这个世界上没有亲人朋友，没有人记得他，需要他，那么这个人的‘人气’就会变弱，但‘生物’也不仅仅包括人，如果能对动物，比如猫狗，或是植物，自然等进行气的连接，这个人身上也会有气加身。</span></p><p style="text-align: left;"><span style="font-size: 19px;">不同的人，气的强弱也有所不同。</span></p><p style="text-align: left;"><span style="font-size: 19px;">一个身体健康，精神坚定，高大魁梧，身强力壮的人阳气充足，最简单的说就是这种人睡坟地没啥事。那么一个身材瘦弱，体弱多病的人就不太行，恐惧会侵蚀他身上可怜的气，甚至能把自己吓死。</span></p><p style="text-align: left;"><br></p><h2 style="text-align: left;">关于住宅</h2><p><span style="font-size: 19px;">一个人住棺材，不通气，不通气不行。</span></p><p><span style="font-size: 19px;">一个人住大豪宅，不聚气，也不行。</span></p><p><span style="font-size: 19px;">最适合一个人住的，是类似酒店房间的一居室，不大不小，通气也聚气。</span></p><p><span style="font-size: 19px;">那么如果遇到一个人住复杂的布局的房子又该怎么办呢？</span></p><p><span style="font-size: 19px;">这是我现在房间的布局：</span></p><p style="text-align: center;"><img alt="790,704" src="/upload/files/png/d1f6834bba7460bc79a1df8348f4a41f.png"></p><p><span style="font-size: 19px;">这房间虽然占地面积不大，但是布局还挺复杂，一个人住就得注意一下。</span></p><p><span style="font-size: 19px;">如图所示，我睡沙发，为什么不睡床呢？因为不通气，卧室的气走不通这么复杂的结构。解决办法是睡客厅，将客厅作为一个城池来守，把卧室封住，那么这个房间的气就都流通起来了。难怪我之前睡卧室有时会有异响，或者莫名其妙的东西掉地上，睡得心神不宁，睡沙发就好一些。</span></p><p><span style="font-size: 19px;">再一个就是上一任租客是一位女性，残留的阴气凝聚在卧室，我再睡卧室那真是把我阳气都吸干了，实属大忌。</span></p><h2>安全考虑</h2><p><span style="font-size: 19px;">安全也是重中之重，如果有歹人来犯，如何最大保障自身安全？</span></p><p><span style="font-size: 19px;">请看下图：</span></p><p style="text-align: center;"><img alt="796,732" src="/upload/files/png/eb3e9d1df8849c44ce2661334b2b520e.png"></p><p><span style="font-size: 19px;">这个房间有两处入口，一个是门，一个是阳台，这两处是最实际的入室犯罪入口点。这时候睡沙发比睡在卧室好在哪呢？</span></p><p><span style="font-size: 19px;">如果睡在卧室，那意味着很难察觉有人入侵，因为路径太过复杂，从阳台或门到卧室都需要拐两个弯，路径的复杂意味着察觉危险的代价高昂，这也是很吓人的一件事情，有人偷偷摸摸到你床边你都不知道，在睡梦中就被抹了脖子，惨！</span></p><p><span style="font-size: 19px;">但睡在客厅就可以一直保持警觉，无论是门还是阳台，都能第一时间察觉！</span></p><p><br></p><p><span style="font-size: 19px;">就算打不过歹人，也能醒着求饶，奉上钱财，说不定能留下一条命！</span></p><p><br></p><p><span style="font-size: 19px;">完。</span></p><p><br></p>

markdown_text:
  在风水相术，道家学说，阴阳当中，几乎都是围绕着一个字来进行展开的，这个字也在国粹文化中频频出现，那就是：

  关于‘气’：
  国棋围棋中有个基本概念：

  一个棋子如果被敌对棋子把气堵了，那么这个棋子就‘没气’了，也就死了，可以从棋盘那掉，人也同样如此，比如一个人死了，那么有人可能会这么说：‘这人没气了’。
  所以气对人来说至关重要，人的呼气吸气吐纳方式是对天地的连接，这个连接一断，人就没了，这是最外层关于气的解释。
  接着就是人身上的‘内气’。
  气与对生物进行的连接息息相关，通俗的解释就是气场，也可以理解为存在感。
  一个频频出现在公众视野的企业家，政府官员，他们的气就比普通人要强，存在感也强，因为他与其他人进行了连接，许多人都需要他，知道他，这就形成了一种气加身。
  普通人身体健康，家庭圆满，也有气，不强但充盈。
  如果一个人在这个世界上没有亲人朋友，没有人记得他，需要他，那么这个人的‘人气’就会变弱，但‘生物’也不仅仅包括人，如果能对动物，比如猫狗，或是植物，自然等进行气的连接，这个人身上也会有气加身。
  不同的人，气的强弱也有所不同。
  一个身体健康，精神坚定，高大魁梧，身强力壮的人阳气充足，最简单的说就是这种人睡坟地没啥事。那么一个身材瘦弱，体弱多病的人就不太行，恐惧会侵蚀他身上可怜的气，甚至能把自己吓死。

  关于住宅
  一个人住棺材，不通气，不通气不行。
  一个人住大豪宅，不聚气，也不行。
  最适合一个人住的，是类似酒店房间的一居室，不大不小，通气也聚气。
  那么如果遇到一个人住复杂的布局的房子又该怎么办呢？
  这是我现在房间的布局：

  这房间虽然占地面积不大，但是布局还挺复杂，一个人住就得注意一下。
  如图所示，我睡沙发，为什么不睡床呢？因为不通气，卧室的气走不通这么复杂的结构。解决办法是睡客厅，将客厅作为一个城池来守，把卧室封住，那么这个房间的气就都流通起来了。难怪我之前睡卧室有时会有异响，或者莫名其妙的东西掉地上，睡得心神不宁，睡沙发就好一些。
  再一个就是上一任租客是一位女性，残留的阴气凝聚在卧室，我再睡卧室那真是把我阳气都吸干了，实属大忌。
  安全考虑
  安全也是重中之重，如果有歹人来犯，如何最大保障自身安全？
  请看下图：

  这个房间有两处入口，一个是门，一个是阳台，这两处是最实际的入室犯罪入口点。这时候睡沙发比睡在卧室好在哪呢？
  如果睡在卧室，那意味着很难察觉有人入侵，因为路径太过复杂，从阳台或门到卧室都需要拐两个弯，路径的复杂意味着察觉危险的代价高昂，这也是很吓人的一件事情，有人偷偷摸摸到你床边你都不知道，在睡梦中就被抹了脖子，惨！
  但睡在客厅就可以一直保持警觉，无论是门还是阳台，都能第一时间察觉！

  就算打不过歹人，也能醒着求饶，奉上钱财，说不定能留下一条命！

  完。


created_time: 2023-08-04 02:32:42.374503
html_text:
  <p><a data-pswp-width="480" data-pswp-height="480" href="/upload/files/jpg/8b687dd8a3ce16c0a06c45261e8b879a.jpg"><img data-magnify="gallery" data-src="/upload/files/jpg/8b687dd8a3ce16c0a06c45261e8b879a.jpg"  src="/upload/files/jpg/8b687dd8a3ce16c0a06c45261e8b879a.jpg"  data-href="/upload/files/jpg/8b687dd8a3ce16c0a06c45261e8b879a.jpg" "/></a><a data-pswp-width="1846" data-pswp-height="1846" href="/upload/files/jpg/c44961693d6a2fb98686074ce6cad2e3.jpg"><img data-magnify="gallery" data-src="/upload/files/jpg/c44961693d6a2fb98686074ce6cad2e3.jpg"  src="/upload/files/jpg/c44961693d6a2fb98686074ce6cad2e3.jpg"  data-href="/upload/files/jpg/c44961693d6a2fb98686074ce6cad2e3.jpg" "/></a></p>


title: 文章排版
intro: 狡啮慎也无所事事地在酒吧喝着酒，目光四下游移，随手将几片药扔进酒杯，左手上的机械食指敲击着杯口边缘，里面的冰块随着指尖的力度左右颤动，不一会儿，杯身下就洇开一团水渍。
created_time: 2023-08-04 06:16:36.186002
html_text:
  <p><span style="font-size: 16px;">狡啮慎也无所事事地在酒吧喝着酒，目光四下游移，随手将几片药扔进酒杯，左手上的机械食指敲击着杯口边缘，里面的冰块随着指尖的力度左右颤动，不一会儿，杯身下就洇开一团水渍。</span><span style="font-size: 19px;"><br>一个东欧人拉开凳子挤进狡啮身侧，狡啮闻见飘来的汗臭，默不作声地往远处侧了侧身子，避开了男人花臂上的一层油脂。酒保拉孜面无表情地招呼着客人，右手的假肢痉挛性地颤抖，粉色塑料外壳看上去已经包裹不住里面改装多次的线路，油漆剥落了一大半，有几处软化成了扭曲的模样，像条半死的虫子。<br>浑浊的雨水冲刷着肮脏的清仁街，略去了闪电与雷声，黑白的世界阴郁地像是失去感情的抑郁症患者。<br>这是狡啮一个月之中第三次光顾“茶壶”酒吧。他在等一个人，来治好他神经网络里残缺的破口。他来千叶城的唯一原因，是因为在千叶城那些深不见底的地下洞窟里有最好的神经拼接术。<br>“茶壶”酒吧的酒保拉孜就是他与地下大夫的牵线人，现在的狡啮看起来与常人无异，却是一个四肢健全的残疾。无法使用电脑操作台，就相当于判了狡啮这种“网络牛仔”的无期徒刑。<br><br>酒吧外的天幕穹顶似乎是坏了，隔几分钟就眨一次眼。不断地确认·重启——无限循环着同一动作。前两个月来的时候，酒吧外面的天幕还会飘下点不合时宜的虚拟樱花，这个年代，天然的事物早已被视作珍禽异兽。<br>拉孜的目光越过狡啮凌乱的黑发，往门口瞟了一眼，低声说了句：“人来了。”手上的活儿可仍旧没停下，倒出的啤酒一滴也没漏到杯外。<br>狡啮点点头，左右两侧都挤满了喧闹的客人，于是将自己的脖子转动一百八十度，保持身体不动的姿势，扫视了背后的来客。<br><br>那人身材高挑而纤瘦，走的近了，看起来全身都整过容。银白的长发条条垂直，在脑后扎了个马尾，像是束着一捆钢筋。前额的头发全部服帖地趴在头顶上，露出光洁锃亮得一截脑门儿。<br>他朝着拉孜眨了下金色的眼睛，丑陋的酒保咧嘴笑了起来，露出一嘴没补齐的烂牙。<br>“槙岛圣护，日本人。”<br>“嗯。”槙岛的目光移到狡啮身上，鼻子里轻轻哼了一声。然后撇下狡啮，和颜悦色地向着他右手边的中国人说起了话。<br>狡啮把脖子转回原样，觉得槙岛圣护的白色太空操作服实在刺眼，似乎照亮了昏暗酒吧的一大半。<br>“你里面有种挺特殊的病毒。”<br>槙岛不知何时坐上了狡啮右手的位置，刚才的中国客人变戏法似的消失了。狡啮看着槙岛瞳孔里的光斑逐渐变大，发亮，似乎正在对他进行着看不见的扫描。<br>“在东南亚，我失败过一次。”他沉闷地回答，右手握拳，指甲刺入皮肉纤维。半年以来的痛苦，伴随着失败的耻辱感，成为狡啮在千叶城夜夜重现的梦魇。<br>槙岛打开手掌，洁白的芯片密密麻麻地排列在手部神经下，如一团团粘连的虫卵，光滑而透明。<br>“我想你需要的是它们。”槙岛的目光划过狡啮的脸，然后合上掌心。<br>“报酬。”<br>狡啮递过去一包颐和园香烟，槙岛撕开皱巴巴的外封锡纸，打开壳子瞥了一眼，露出满意的笑容。<br>“成交。”<br><br>“你是警察？”槙岛将烟盒揣进口袋，最后冷静地问他。<br>狡啮不置可否地扯了扯嘴角，往桌子上丢了点钱，起身离开。一群闹哄哄的妓女正从槙岛身边走过，槙岛盯着狡啮的背影消失在“茶壶”酒吧的入口，转身和拉孜打了个招呼，一饮而尽了狡啮杯子里的酒。<br><br>一出门，臭哄哄的雨水湿气混着风扑了上来，街上飘着一层青烟。槙岛瞧见狡啮在清仁街的一角边躲雨边抽烟。<br>“去你哪儿还是去我哪儿？”他走过去“善解人意”地问。<br>“随意。”<br>狡啮竖起领子，在雨里跑了几步，示意槙岛跟上来。<br>一辆老式改装车停在路边，狡啮拉开车门，将人塞进副驾驶。然后自己钻进去点火起步。<br><br>大雨滂沱，狡啮的车孤零零地在雨幕中寻找去路，像极了迷失在海上的一叶小舟，没人试图打破尴尬的空气，两人似乎都在沉默中寻得了一丝平静。坐在副驾驶的槙岛打量狡啮半晌，干脆从内侧口袋里掏出本书津津有味地读起来。<br>“前警察。”一直专注开车的狡啮开了口。<br>槙岛的目光从那本红色封面的书上收回来，眉毛轻微上挑，露出一种“哦”的了然表情。<br>“最早干过警察，后面出了事，蹲了局子。二十四岁出了狱，干了三年黑活…然后就是现在这幅德行。”<br>狡啮猛吸了一口叼在嘴里的烟，将车窗拉开一条缝，酸臭的雨立刻潲进窗口。<br><br>车子七扭八拐地开到一处小巷，槙岛沉默地跟着狡啮走进一间旅店的房间。<br>“简陋了点，不过也行吧。”槙岛打量了一下狡啮临时的栖身之所，抬脚在满地的酒瓶和烟蒂中开辟出一条路来。然后一屁股坐到了床上，“我最好把你捆起来。预防发狂的病人攻击医生。”槙岛取下脖子上的项链，从里面抽出一条指甲盖粗细的金属线。<br>“不必了。”狡啮在床上坐下，从抽屉里掏出一把手铐，递给他。“把我拷在床头。”<br><br>槙岛圣护掰了一下自己的右手腕关节，咔的一响，接缝处伸出一把银质手术刀。<br>“我可从来没给条子开过刀。”槙岛笑着逼近狡啮。<br>“你要是说假话，我一样有办法把你弄进去。”狡啮感觉刀锋的寒气掠过自己耳后的皮肤。<br>槙岛笑了起来，“那我要感谢你暂时放过我这个犯罪分子咯。”右手无声无息地划了一道，剖开狡啮的大脑。<br>“如何？”<br>“情况不太好。”<br>狡啮听着槙岛重复了一遍和其他医生差不多的结论，最后他又补上一句，“不过我能治好。”<br>狡啮的苦笑在喉咙里滚了一圈。<br>“修补并不难，只是那种病毒，一直在干预你的神经权限。”<br>“我知道。”<br>“军方的病毒？”<br>“是。”<br>槙岛半跪在狡啮面前，脱掉他的外套，双手抚上狡啮的腹肌。<br>“我要链接上你的神经，给我权限…”说着冰冷的双唇贴了上来，唇舌接触的瞬间，狡啮感觉舌尖一麻，只听见槙岛的声音在脑内放大了无数倍。<br>“给我看，你的全部。”<br><br><br>end.</span></p>

markdown_text:
  狡啮慎也无所事事地在酒吧喝着酒，目光四下游移，随手将几片药扔进酒杯，左手上的机械食指敲击着杯口边缘，里面的冰块随着指尖的力度左右颤动，不一会儿，杯身下就洇开一团水渍。
  一个东欧人拉开凳子挤进狡啮身侧，狡啮闻见飘来的汗臭，默不作声地往远处侧了侧身子，避开了男人花臂上的一层油脂。酒保拉孜面无表情地招呼着客人，右手的假肢痉挛性地颤抖，粉色塑料外壳看上去已经包裹不住里面改装多次的线路，油漆剥落了一大半，有几处软化成了扭曲的模样，像条半死的虫子。
  浑浊的雨水冲刷着肮脏的清仁街，略去了闪电与雷声，黑白的世界阴郁地像是失去感情的抑郁症患者。
  这是狡啮一个月之中第三次光顾“茶壶”酒吧。他在等一个人，来治好他神经网络里残缺的破口。他来千叶城的唯一原因，是因为在千叶城那些深不见底的地下洞窟里有最好的神经拼接术。
  “茶壶”酒吧的酒保拉孜就是他与地下大夫的牵线人，现在的狡啮看起来与常人无异，却是一个四肢健全的残疾。无法使用电脑操作台，就相当于判了狡啮这种“网络牛仔”的无期徒刑。

  酒吧外的天幕穹顶似乎是坏了，隔几分钟就眨一次眼。不断地确认·重启——无限循环着同一动作。前两个月来的时候，酒吧外面的天幕还会飘下点不合时宜的虚拟樱花，这个年代，天然的事物早已被视作珍禽异兽。
  拉孜的目光越过狡啮凌乱的黑发，往门口瞟了一眼，低声说了句：“人来了。”手上的活儿可仍旧没停下，倒出的啤酒一滴也没漏到杯外。
  狡啮点点头，左右两侧都挤满了喧闹的客人，于是将自己的脖子转动一百八十度，保持身体不动的姿势，扫视了背后的来客。

  那人身材高挑而纤瘦，走的近了，看起来全身都整过容。银白的长发条条垂直，在脑后扎了个马尾，像是束着一捆钢筋。前额的头发全部服帖地趴在头顶上，露出光洁锃亮得一截脑门儿。
  他朝着拉孜眨了下金色的眼睛，丑陋的酒保咧嘴笑了起来，露出一嘴没补齐的烂牙。
  “槙岛圣护，日本人。”
  “嗯。”槙岛的目光移到狡啮身上，鼻子里轻轻哼了一声。然后撇下狡啮，和颜悦色地向着他右手边的中国人说起了话。
  狡啮把脖子转回原样，觉得槙岛圣护的白色太空操作服实在刺眼，似乎照亮了昏暗酒吧的一大半。
  “你里面有种挺特殊的病毒。”
  槙岛不知何时坐上了狡啮右手的位置，刚才的中国客人变戏法似的消失了。狡啮看着槙岛瞳孔里的光斑逐渐变大，发亮，似乎正在对他进行着看不见的扫描。
  “在东南亚，我失败过一次。”他沉闷地回答，右手握拳，指甲刺入皮肉纤维。半年以来的痛苦，伴随着失败的耻辱感，成为狡啮在千叶城夜夜重现的梦魇。
  槙岛打开手掌，洁白的芯片密密麻麻地排列在手部神经下，如一团团粘连的虫卵，光滑而透明。
  “我想你需要的是它们。”槙岛的目光划过狡啮的脸，然后合上掌心。
  “报酬。”
  狡啮递过去一包颐和园香烟，槙岛撕开皱巴巴的外封锡纸，打开壳子瞥了一眼，露出满意的笑容。
  “成交。”

  “你是警察？”槙岛将烟盒揣进口袋，最后冷静地问他。
  狡啮不置可否地扯了扯嘴角，往桌子上丢了点钱，起身离开。一群闹哄哄的妓女正从槙岛身边走过，槙岛盯着狡啮的背影消失在“茶壶”酒吧的入口，转身和拉孜打了个招呼，一饮而尽了狡啮杯子里的酒。

  一出门，臭哄哄的雨水湿气混着风扑了上来，街上飘着一层青烟。槙岛瞧见狡啮在清仁街的一角边躲雨边抽烟。
  “去你哪儿还是去我哪儿？”他走过去“善解人意”地问。
  “随意。”
  狡啮竖起领子，在雨里跑了几步，示意槙岛跟上来。
  一辆老式改装车停在路边，狡啮拉开车门，将人塞进副驾驶。然后自己钻进去点火起步。

  大雨滂沱，狡啮的车孤零零地在雨幕中寻找去路，像极了迷失在海上的一叶小舟，没人试图打破尴尬的空气，两人似乎都在沉默中寻得了一丝平静。坐在副驾驶的槙岛打量狡啮半晌，干脆从内侧口袋里掏出本书津津有味地读起来。
  “前警察。”一直专注开车的狡啮开了口。
  槙岛的目光从那本红色封面的书上收回来，眉毛轻微上挑，露出一种“哦”的了然表情。
  “最早干过警察，后面出了事，蹲了局子。二十四岁出了狱，干了三年黑活…然后就是现在这幅德行。”
  狡啮猛吸了一口叼在嘴里的烟，将车窗拉开一条缝，酸臭的雨立刻潲进窗口。

  车子七扭八拐地开到一处小巷，槙岛沉默地跟着狡啮走进一间旅店的房间。
  “简陋了点，不过也行吧。”槙岛打量了一下狡啮临时的栖身之所，抬脚在满地的酒瓶和烟蒂中开辟出一条路来。然后一屁股坐到了床上，“我最好把你捆起来。预防发狂的病人攻击医生。”槙岛取下脖子上的项链，从里面抽出一条指甲盖粗细的金属线。
  “不必了。”狡啮在床上坐下，从抽屉里掏出一把手铐，递给他。“把我拷在床头。”

  槙岛圣护掰了一下自己的右手腕关节，咔的一响，接缝处伸出一把银质手术刀。
  “我可从来没给条子开过刀。”槙岛笑着逼近狡啮。
  “你要是说假话，我一样有办法把你弄进去。”狡啮感觉刀锋的寒气掠过自己耳后的皮肤。
  槙岛笑了起来，“那我要感谢你暂时放过我这个犯罪分子咯。”右手无声无息地划了一道，剖开狡啮的大脑。
  “如何？”
  “情况不太好。”
  狡啮听着槙岛重复了一遍和其他医生差不多的结论，最后他又补上一句，“不过我能治好。”
  狡啮的苦笑在喉咙里滚了一圈。
  “修补并不难，只是那种病毒，一直在干预你的神经权限。”
  “我知道。”
  “军方的病毒？”
  “是。”
  槙岛半跪在狡啮面前，脱掉他的外套，双手抚上狡啮的腹肌。
  “我要链接上你的神经，给我权限…”说着冰冷的双唇贴了上来，唇舌接触的瞬间，狡啮感觉舌尖一麻，只听见槙岛的声音在脑内放大了无数倍。
  “给我看，你的全部。”


  end.


created_time: 2023-08-09 15:47:45.822480
html_text:
  <p><a data-pswp-width="4096" data-pswp-height="3072" href="/upload/files/jpg/9a4d84e8f6d1f6326c090191c138d39b.jpg"><img data-magnify="gallery" data-src="/upload/files/jpg/9a4d84e8f6d1f6326c090191c138d39b.jpg"  src="/upload/files/jpg/9a4d84e8f6d1f6326c090191c138d39b.jpg"  data-href="/upload/files/jpg/9a4d84e8f6d1f6326c090191c138d39b.jpg" "/></a><a data-pswp-width="4096" data-pswp-height="3072" href="/upload/files/jpg/4c3748b443c30046bfaa83316b4978e9.jpg"><img data-magnify="gallery" data-src="/upload/files/jpg/4c3748b443c30046bfaa83316b4978e9.jpg"  src="/upload/files/jpg/4c3748b443c30046bfaa83316b4978e9.jpg"  data-href="/upload/files/jpg/4c3748b443c30046bfaa83316b4978e9.jpg" "/></a><a data-pswp-width="4096" data-pswp-height="3072" href="/upload/files/jpg/c56743f0aee401037cc0955c3c61ec70.jpg"><img data-magnify="gallery" data-src="/upload/files/jpg/c56743f0aee401037cc0955c3c61ec70.jpg"  src="/upload/files/jpg/c56743f0aee401037cc0955c3c61ec70.jpg"  data-href="/upload/files/jpg/c56743f0aee401037cc0955c3c61ec70.jpg" "/></a><a data-pswp-width="4096" data-pswp-height="3072" href="/upload/files/jpg/91468c917528647f060b8d491bf89461.jpg"><img data-magnify="gallery" data-src="/upload/files/jpg/91468c917528647f060b8d491bf89461.jpg"  src="/upload/files/jpg/91468c917528647f060b8d491bf89461.jpg"  data-href="/upload/files/jpg/91468c917528647f060b8d491bf89461.jpg" "/></a></p>


title: 致夏天，哦不，致垃圾，哦不，致...
intro: 写完之后爽了一点
created_time: 2023-08-10 08:52:57.259218
html_text:
  <p style="text-align: justify;"><span style="font-size: 19px;">窗外的蝉叫的跟它妈死掉了一样。</span></p><p style="text-align: justify;"><span style="font-size: 19px;">但作为一个讲究逻辑的人，我要对说出话的逻辑负责，所以我去百度上搜了：“蝉叫是伤心还是高兴”，很高兴我没有看到关于蝉叫的科学解释，那么在我这里，蝉之所以会叫就是因为它妈死了。</span></p><p style="text-align: justify;"><span style="font-size: 19px;">没错，这充满怨念的开头没有一丝一毫的矫揉造作，全是我此时的真情流露。</span></p><p style="text-align: justify;"><span style="font-size: 19px;">所以这不是一篇正能量的文章，也不符合社会主义核心价值观，只是一堆情绪垃圾。</span></p><p style="text-align: justify;"><span style="font-size: 19px;">在八月初的重庆，下午，晴天，蝉鸣，桑拿似的高温环境下，每待一秒都会让我的暴躁更甚一份。</span></p><p style="text-align: justify;"><span style="font-size: 19px;">作为一个毫无尊严的社畜，我发现了问题的严重性：因为在我以前我听到蝉叫我并不会觉得蝉会叫是因为它妈死了，虽然天也很热，但我会觉得它是想叫，如果是写作文可以描写成：“蝉在树上炫耀着它那优美的高音”，而不是会一下子想到它叫是因为它妈死了。</span></p><p style="text-align: justify;"><span style="font-size: 19px;">不要说我老是纠结蝉它妈死没死啥的，重点是我他妈怨气也太重了。</span></p><p style="text-align: justify;"><span style="font-size: 19px;">难道是因为天热吗？我想并不是，夏天总是很热，我活了20多年每个夏天都热。</span></p><p style="text-align: justify;"><span style="font-size: 19px;">我想是一种更隐秘，更严重的问题，就像是我刚晾衣服，发现两双不同款式的黑色袜子都各少了一只，而且还把它们都夹在了一起晒一样，当时觉得很操蛋，但是这一会，我他妈越想越操蛋。</span></p><p style="text-align: justify;"><span style="font-size: 19px;">一种习惯性的压抑，把人变成鬼了，都是上班害得！</span></p><p style="text-align: justify;"><span style="font-size: 19px;">上班越久病的就越重，我是个喜欢文字的人，然而现在灵感枯竭的很，我原来充沛的情感和倾诉欲就像挤牛奶一样从我身上被挤出去，而且不是攒了满满奶才挤，是有一点挤一点，一滴也不放过，太残暴啦！</span></p><p style="text-align: justify;"><span style="font-size: 19px;">难怪我攒了这么久的素材和设定，当我想下笔写的时候却像是女友刚脱完衣服就软了一样让人泄气。</span></p><p style="text-align: justify;"><span style="font-size: 19px;">但我不能吐槽工作，因为我不确定这篇文章，哦不，情绪垃圾会不会被老板看到，如果看到了，那么：</span></p><p style="text-align: justify;"><span style="font-size: 19px;">我爱工作。</span></p><p style="text-align: justify;"><span style="font-size: 19px;">一个发霉了的打工人不会对未来有什么美好的幻想了，只能回忆回忆从前找找感觉。如果回忆像是视频倒放，那我不能以N倍速回放，而是直接跳跃进度，因为有太多记忆是不堪回首了，这么讲感觉还蛮可怜的。</span></p><p style="text-align: justify;"><span style="font-size: 19px;">有些回忆片段还是挺值得细细品味的，不是某件快乐的事情那么肤浅，而是一种感觉，感受，一种对世界的好奇，对周遭事物</span></p><p style="text-align: justify;"><span style="font-size: 19px;">骚扰电话来的很是时候，看看看看！这就是令现代人烦躁，焦虑的真实写照，太多和你无关的事要和你扯上关系，你要不停的拒绝，有些你还不能拒绝。你买了扫地机器人将房间打扫的一尘不染；你把房间的垃圾都扔了；用洗面奶把脸上的油脂洗的干干净净；用洗发水把头发洗的清爽；撒了尿灌了肠；你以为都干净了，不！垃圾都在脑子里呢！资本创造的垃圾，和某种不能明说的洗脑垃圾一遍一遍的往你脑子里扔，让你莫名其妙的躁动，无法找到通往内心安宁的路。</span></p><p style="text-align: justify;"><span style="font-size: 19px;">好，接着回忆。</span></p><p style="text-align: justify;"><span style="font-size: 19px;">有些回忆片段还是挺值得细细品味的，不是某件快乐的事情那么肤浅，而是一种感觉，感受，一种对世界的好奇，对周遭事物</span></p><p style="text-align: justify;"><br></p><p style="text-align: justify;"><br></p><p style="text-align: justify;"><br></p><p style="text-align: justify;"><br></p><p style="text-align: justify;"><span style="font-size: 19px;">去你妈的。</span></p><p style="text-align: justify;"><span style="font-size: 19px;"><br></span></p>

markdown_text:
  窗外的蝉叫的跟它妈死掉了一样。
  但作为一个讲究逻辑的人，我要对说出话的逻辑负责，所以我去百度上搜了：“蝉叫是伤心还是高兴”，很高兴我没有看到关于蝉叫的科学解释，那么在我这里，蝉之所以会叫就是因为它妈死了。
  没错，这充满怨念的开头没有一丝一毫的矫揉造作，全是我此时的真情流露。
  所以这不是一篇正能量的文章，也不符合社会主义核心价值观，只是一堆情绪垃圾。
  在八月初的重庆，下午，晴天，蝉鸣，桑拿似的高温环境下，每待一秒都会让我的暴躁更甚一份。
  作为一个毫无尊严的社畜，我发现了问题的严重性：因为在我以前我听到蝉叫我并不会觉得蝉会叫是因为它妈死了，虽然天也很热，但我会觉得它是想叫，如果是写作文可以描写成：“蝉在树上炫耀着它那优美的高音”，而不是会一下子想到它叫是因为它妈死了。
  不要说我老是纠结蝉它妈死没死啥的，重点是我他妈怨气也太重了。
  难道是因为天热吗？我想并不是，夏天总是很热，我活了20多年每个夏天都热。
  我想是一种更隐秘，更严重的问题，就像是我刚晾衣服，发现两双不同款式的黑色袜子都各少了一只，而且还把它们都夹在了一起晒一样，当时觉得很操蛋，但是这一会，我他妈越想越操蛋。
  一种习惯性的压抑，把人变成鬼了，都是上班害得！
  上班越久病的就越重，我是个喜欢文字的人，然而现在灵感枯竭的很，我原来充沛的情感和倾诉欲就像挤牛奶一样从我身上被挤出去，而且不是攒了满满奶才挤，是有一点挤一点，一滴也不放过，太残暴啦！
  难怪我攒了这么久的素材和设定，当我想下笔写的时候却像是女友刚脱完衣服就软了一样让人泄气。
  但我不能吐槽工作，因为我不确定这篇文章，哦不，情绪垃圾会不会被老板看到，如果看到了，那么：
  我爱工作。
  一个发霉了的打工人不会对未来有什么美好的幻想了，只能回忆回忆从前找找感觉。如果回忆像是视频倒放，那我不能以N倍速回放，而是直接跳跃进度，因为有太多记忆是不堪回首了，这么讲感觉还蛮可怜的。
  有些回忆片段还是挺值得细细品味的，不是某件快乐的事情那么肤浅，而是一种感觉，感受，一种对世界的好奇，对周遭事物
  骚扰电话来的很是时候，看看看看！这就是令现代人烦躁，焦虑的真实写照，太多和你无关的事要和你扯上关系，你要不停的拒绝，有些你还不能拒绝。你买了扫地机器人将房间打扫的一尘不染；你把房间的垃圾都扔了；用洗面奶把脸上的油脂洗的干干净净；用洗发水把头发洗的清爽；撒了尿灌了肠；你以为都干净了，不！垃圾都在脑子里呢！资本创造的垃圾，和某种不能明说的洗脑垃圾一遍一遍的往你脑子里扔，让你莫名其妙的躁动，无法找到通往内心安宁的路。
  好，接着回忆。
  有些回忆片段还是挺值得细细品味的，不是某件快乐的事情那么肤浅，而是一种感觉，感受，一种对世界的好奇，对周遭事物




  去你妈的。


title: 升维
intro:
  老杨死在一个平静的夜晚。
  早上的时候他还在田地间劳作，闲暇之余他坐在田埂旁的老树桩上目眺远方，久久出神。
  直到清晨的第一缕阳光从远方漆黑的群山轮廓上照亮天地，他才回过神来。
  “该走了”。

created_time: 2023-08-13 14:20:37.504706
html_text:
  <h2 style="text-align: start;">死亡</h2><p style="text-align: justify;">老杨死在一个平静的夜晚。</p><p style="text-align: justify;">早上的时候他还在田地间劳作，闲暇之余他坐在田埂旁的老树桩上目眺远方，久久出神。</p><p style="text-align: justify;">直到清晨的第一缕阳光从远方漆黑的群山轮廓上照亮天地，他才回过神来。</p><p style="text-align: justify;">“该走了”。</p><p style="text-align: justify;">老杨干皱的嘴巴蠕动了几下，只发出了微弱的气喘声。</p><p style="text-align: justify;">生机勃勃的杂草被老杨手里的刮子削断，他没有想到已经去世两年的老伴，也没有想到他两个在城里工作的儿子和在上学的孙子。</p><p style="text-align: justify;">他想起了年轻时他去看瓜的那一段时光，每天晚上躺在干草搭起的简陋棚屋，他把头露出来看向夜空，漫天星辰存在了亿万年，亘古不变，它们像无数的神明在俯瞰众生，他第一次感觉到了自身的渺小，他第一次开始思考头顶上的东西：</p><p style="text-align: justify;">“这些星星都死不了滴蛮，乖乖”。</p><p style="text-align: justify;">老杨倒在了田间，小虫子不再惧怕这个曾经的巨人，开始在他的身上探索着食物的气息。</p><p style="text-align: justify;">在医院的急救病房内，老杨有过短暂的清醒，只不过每次呼吸就像是攀越了一座大山般吃力，他用眯缝着的眼睛撇了一下左右两边：</p><p style="text-align: justify;">“都没来”</p><p style="text-align: justify;">呼吸机开始发出不停歇的蜂鸣，病房开始忙碌起来。</p><h2 style="text-align: start;">升维</h2><p style="text-align: justify;">是上升还是下降？</p><p style="text-align: justify;">不知道，似乎是上升，也似乎是下降。</p><p style="text-align: justify;">它看见时间的尽头在向无限的时空延伸，病床上的老杨正在被最后一次电击，两个儿子先后冲进医院，大儿子开始来回踱步，小儿子在病床前低声哭泣。两个年轻的儿媳在一旁默默的看着，孙子和孙女哭着跟在他们的妈妈旁边。</p><p style="text-align: justify;">它并没有任何感觉，只觉得无趣，它开始回溯。</p><p style="text-align: justify;">世界开始倒退，它的儿子们从病房倒退出去，倒退到车里，倒退到城里的家里。</p><p style="text-align: justify;">它从病床倒退到救护车里，倒退到今早的田间。</p><p style="text-align: justify;">黑夜倒退到了夕阳，倒退到了正午，黎明，往复循环。</p><p style="text-align: justify;">它一直默默的观察着，不曾干预。</p><p style="text-align: justify;">它看到了去世多年的老伴，看到了两个儿子的诞生，看到了自己守瓜的夜，看到了自己的出生。</p><p style="text-align: justify;">还没到终点，继续回溯。</p><p style="text-align: justify;">它看完了五代十二纪元，一共四十五亿年。</p><p style="text-align: justify;">看到地球从生机勃勃到分崩离析，到一块千疮百孔的灰色巨岩，巨岩归于深空，地球归于虚无。</p><p style="text-align: justify;">唯有太阳还在炙烤着虚空。</p><p style="text-align: justify;">深空中传来了呼唤，它开始在太空中漂流，一种无形的引力在牵引着它寻找正确的方向，经过的超星系爆炸，吞噬恒星的黑洞，惊天动地的星系碰撞，这些都不曾让它停下。</p><p style="text-align: justify;">直到迎来光明，它到了。</p><p style="text-align: justify;">在这里它感到前所未有的幸福，快乐，它穿梭在过去，现在和未来。它和与他一起的能量体在浩瀚的时空中碰撞，它们就像是一群孩子在这里尽情的玩耍，捣乱。它们与一场可以吞没地球的大风暴共同舞蹈，能在和宇宙一样古老的‘沉思者’上小心翼翼地探索着宇宙的来源和历史，直到被‘沉思者’发现，将它们赶了出去。</p><p style="text-align: justify;">“滚出去”</p><h2 style="text-align: justify;">战争</h2><p>...待续</p>

markdown_text:
  死亡
  老杨死在一个平静的夜晚。
  早上的时候他还在田地间劳作，闲暇之余他坐在田埂旁的老树桩上目眺远方，久久出神。
  直到清晨的第一缕阳光从远方漆黑的群山轮廓上照亮天地，他才回过神来。
  “该走了”。
  老杨干皱的嘴巴蠕动了几下，只发出了微弱的气喘声。
  生机勃勃的杂草被老杨手里的刮子削断，他没有想到已经去世两年的老伴，也没有想到他两个在城里工作的儿子和在上学的孙子。
  他想起了年轻时他去看瓜的那一段时光，每天晚上躺在干草搭起的简陋棚屋，他把头露出来看向夜空，漫天星辰存在了亿万年，亘古不变，它们像无数的神明在俯瞰众生，他第一次感觉到了自身的渺小，他第一次开始思考头顶上的东西：
  “这些星星都死不了滴蛮，乖乖”。
  老杨倒在了田间，小虫子不再惧怕这个曾经的巨人，开始在他的身上探索着食物的气息。
  在医院的急救病房内，老杨有过短暂的清醒，只不过每次呼吸就像是攀越了一座大山般吃力，他用眯缝着的眼睛撇了一下左右两边：
  “都没来”
  呼吸机开始发出不停歇的蜂鸣，病房开始忙碌起来。
  升维
  是上升还是下降？
  不知道，似乎是上升，也似乎是下降。
  它看见时间的尽头在向无限的时空延伸，病床上的老杨正在被最后一次电击，两个儿子先后冲进医院，大儿子开始来回踱步，小儿子在病床前低声哭泣。两个年轻的儿媳在一旁默默的看着，孙子和孙女哭着跟在他们的妈妈旁边。
  它并没有任何感觉，只觉得无趣，它开始回溯。
  世界开始倒退，它的儿子们从病房倒退出去，倒退到车里，倒退到城里的家里。
  它从病床倒退到救护车里，倒退到今早的田间。
  黑夜倒退到了夕阳，倒退到了正午，黎明，往复循环。
  它一直默默的观察着，不曾干预。
  它看到了去世多年的老伴，看到了两个儿子的诞生，看到了自己守瓜的夜，看到了自己的出生。
  还没到终点，继续回溯。
  它看完了五代十二纪元，一共四十五亿年。
  看到地球从生机勃勃到分崩离析，到一块千疮百孔的灰色巨岩，巨岩归于深空，地球归于虚无。
  唯有太阳还在炙烤着虚空。
  深空中传来了呼唤，它开始在太空中漂流，一种无形的引力在牵引着它寻找正确的方向，经过的超星系爆炸，吞噬恒星的黑洞，惊天动地的星系碰撞，这些都不曾让它停下。
  直到迎来光明，它到了。
  在这里它感到前所未有的幸福，快乐，它穿梭在过去，现在和未来。它和与他一起的能量体在浩瀚的时空中碰撞，它们就像是一群孩子在这里尽情的玩耍，捣乱。它们与一场可以吞没地球的大风暴共同舞蹈，能在和宇宙一样古老的‘沉思者’上小心翼翼地探索着宇宙的来源和历史，直到被‘沉思者’发现，将它们赶了出去。
  “滚出去”
  战争
  ...待续


created_time: 2023-08-16 15:19:19.346393
html_text:
  <p><a data-pswp-width="1920" data-pswp-height="1080" href="/upload/files/png/b9b2ef965f2aef0945caa396f0f7b282.png"><img data-magnify="gallery" data-src="/upload/files/png/b9b2ef965f2aef0945caa396f0f7b282.png"  src="/upload/files/png/b9b2ef965f2aef0945caa396f0f7b282.png"  data-href="/upload/files/png/b9b2ef965f2aef0945caa396f0f7b282.png" "/></a><a data-pswp-width="1920" data-pswp-height="977" href="/upload/files/png/5bbadf7162491eb71770d03b8cce3a46.png"><img data-magnify="gallery" data-src="/upload/files/png/5bbadf7162491eb71770d03b8cce3a46.png"  src="/upload/files/png/5bbadf7162491eb71770d03b8cce3a46.png"  data-href="/upload/files/png/5bbadf7162491eb71770d03b8cce3a46.png" "/></a><a data-pswp-width="1920" data-pswp-height="1080" href="/upload/files/png/48592bbfb3da022f268f1c8225264102.png"><img data-magnify="gallery" data-src="/upload/files/png/48592bbfb3da022f268f1c8225264102.png"  src="/upload/files/png/48592bbfb3da022f268f1c8225264102.png"  data-href="/upload/files/png/48592bbfb3da022f268f1c8225264102.png" "/></a><a data-pswp-width="2279" data-pswp-height="1280" href="/upload/files/jpeg/250fb6c6a249e355e91e4d3d08b91a44.jpeg"><img data-magnify="gallery" data-src="/upload/files/jpeg/250fb6c6a249e355e91e4d3d08b91a44.jpeg"  src="/upload/files/jpeg/250fb6c6a249e355e91e4d3d08b91a44.jpeg"  data-href="/upload/files/jpeg/250fb6c6a249e355e91e4d3d08b91a44.jpeg" "/></a></p>


title: 沿途风景，躺平的好去处
created_time: 2023-08-21 02:07:08.482972
html_text:
  <p><a data-pswp-width="4032" data-pswp-height="3024" href="/upload/files/jpeg/ec06908b7f4b5a1d4e5cfea516318eb6.jpeg"><img data-magnify="gallery" data-src="/upload/files/jpeg/ec06908b7f4b5a1d4e5cfea516318eb6.jpeg"  src="/upload/files/jpeg/ec06908b7f4b5a1d4e5cfea516318eb6.jpeg"  data-href="/upload/files/jpeg/ec06908b7f4b5a1d4e5cfea516318eb6.jpeg" "/></a><a data-pswp-width="4032" data-pswp-height="3024" href="/upload/files/jpeg/d4422603ff1a342ff835fcb8a7f1a46e.jpeg"><img data-magnify="gallery" data-src="/upload/files/jpeg/d4422603ff1a342ff835fcb8a7f1a46e.jpeg"  src="/upload/files/jpeg/d4422603ff1a342ff835fcb8a7f1a46e.jpeg"  data-href="/upload/files/jpeg/d4422603ff1a342ff835fcb8a7f1a46e.jpeg" "/></a></p>


title: 草稿
created_time: 2023-08-22 08:27:19.979581
intro:
  ```bash
  route add -net 192.168.1.0 netmask 255.255.255.0 dev eth1
  route add -net 192.168.56.0 netmask 255.255.255.0 dev eth0

html_text:
  <p>```bash<br>route add -net 192.168.1.0 netmask 255.255.255.0 dev eth1<br>route add -net 192.168.56.0 netmask 255.255.255.0 dev eth0<br><br><br><br>route add -net 192.168.1.0 netmask 255.255.255.0 dev eth1<br>route add -net 192.168.0.0 netmask 255.255.255.0 dev eth0<br><br><br><br><br>route add -net 192.168.2.0 netmask 255.255.255.0 dev eth0<br>route del -net 192.168.2.0 netmask 255.255.255.0 dev eth0<br>route del -net 192.168.2.0 netmask 255.255.255.0 dev eth1<br><br>route add -net 192.168.0.0/24 netmask 255.255.255.0 dev eth0<br><br><br>sudo route add default gw 192.168.0.1<br><br><br>route del default gw 192.168.2.0 dev eth0<br><br>route del -net 192.168.2.0 netmask 255.255.255.0 dev eth1<br><br><br><br>sudo ip addr add 10.0.0.11/ dev eth1<br><br>systemctl stop isc-dhcp-server<br>systemctl restart isc-dhcp-server<br>systemctl status isc-dhcp-server <br><br>sudo route add default gw 192.168.0.1<br><br>route add -net 192.168.2.1/24 gw 192.168.0.0 eth1<br><br>route add -net 192.168.2.1/24 gw 192.168.0.0 eth1<br><br><br>route add -net 192.168.2.0/24 gw 192.168.0.0 eth1<br>route add -net 192.168.2.1/24 gw 192.168.0.9 eth1<br><br>route add default gw _gateway 0.0.0.0 eth0<br><br><br>default &nbsp; &nbsp; &nbsp; &nbsp; _gateway &nbsp; &nbsp; &nbsp; &nbsp;0.0.0.0 &nbsp; &nbsp; &nbsp; &nbsp; UG &nbsp; &nbsp; &nbsp; &nbsp;0 0 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;0 eth0<br><br>route add -net 192.168.20.0 netmask 255.255.255.0 gw 192.168.10.1<br><br><br>iptables -t nat -A POSTROUTING -o eth1 -j MASQUERADE<br><br><br>iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE<br>iptables -A INPUT -i eth1 -j ACCEPT<br>iptables -A INPUT -i eth0 -m state --state ESTABLISHED,RELATED -j ACCEPT<br>iptables -A INPUT -i eth2 -j ACCEPT<br>iptables -A INPUT -i eth0 -m state --state ESTABLISHED,RELATED -j ACCEPT<br>iptables -A OUTPUT -j ACCEPT<br><br><br>iptables -t nat -A POSTROUTING -o tun0 -j MASQUERADE<br>iptables -A INPUT -i tun0 -m state --state ESTABLISHED,RELATED -j ACCEPT<br><br><br><br>route add -net 192.168.31.0 netmask 255.255.255.0 gw 192.168.112.5<br><br><br><br><br>sudo vi /etc/netplan/01-installer-config.yaml<br><br><br><br><br><br>vim /etc/dhcp/dhcpd.conf<br><br>subnet 192.168.2.0 netmask 255.255.255.0 {<br> range 192.168.2.10 192.168.2.99;<br> option domain-name-servers 192.168.10.8,192.168.11.8;<br> option domain-name "splend";<br> option subnet-mask 255.255.255.0;<br> option routers 192.168.3.1;<br> option broadcast-address 192.168.3.255;<br> default-lease-time 600;<br> max-lease-time 7200;<br>}<br><br>option domain-name-servers 114.114.114.114,8.8.8.8,<br>option domain-name "splend";<br><br><br><br><br><br>subnet 192.168.2.0 netmask 255.255.255.0 {<br> &nbsp; range 192.168.2.100 192.168.2.200;<br> &nbsp; option domain-name-servers 114.114.114.114,8.8.8.8;<br> &nbsp; option domain-name "splend";<br> &nbsp; option routers 192.168.2.1;<br> &nbsp; option broadcast-address 192.168.2.255;<br> &nbsp; default-lease-time 600;<br> &nbsp; max-lease-time 7200;<br>}<br><br>subnet 192.168.3.0 netmask 255.255.255.0 {<br> &nbsp; range 192.168.3.100 192.168.3.200;<br> &nbsp; option domain-name-servers 114.114.114.114,8.8.8.8;<br> &nbsp; option domain-name "splend";<br> &nbsp; option routers 192.168.3.1;<br> &nbsp; option broadcast-address 192.168.3.255;<br> &nbsp; default-lease-time 600;<br> &nbsp; max-lease-time 7200;<br>}<br><br><br><br>iptables -A FORWARD -i eth1 -o tun0 -j ACCEPT<br>iptables -A FORWARD -i tun0 -o eth1 -m state --state ESTABLISHED,RELATED -j ACCEPT<br><br><br>iptables -A FORWARD -i eth2 -o tun0 -j ACCEPT<br>iptables -A FORWARD -i tun0 -o eth2 -m state --state ESTABLISHED,RELATED -j ACCEPT<br>iptables -t nat -A POSTROUTING -o tun0 -j MASQUERADE<br><br>apt install iptables-persistent<br><br><br>iptables-save &gt; /etc/iptables/rules.v4</p>

markdown_text:
  ```bash
  route add -net 192.168.1.0 netmask 255.255.255.0 dev eth1
  route add -net 192.168.56.0 netmask 255.255.255.0 dev eth0



  route add -net 192.168.1.0 netmask 255.255.255.0 dev eth1
  route add -net 192.168.0.0 netmask 255.255.255.0 dev eth0




  route add -net 192.168.2.0 netmask 255.255.255.0 dev eth0
  route del -net 192.168.2.0 netmask 255.255.255.0 dev eth0
  route del -net 192.168.2.0 netmask 255.255.255.0 dev eth1

  route add -net 192.168.0.0/24 netmask 255.255.255.0 dev eth0


  sudo route add default gw 192.168.0.1


  route del default gw 192.168.2.0 dev eth0

  route del -net 192.168.2.0 netmask 255.255.255.0 dev eth1



  sudo ip addr add 10.0.0.11/ dev eth1

  systemctl stop isc-dhcp-server
  systemctl restart isc-dhcp-server
  systemctl status isc-dhcp-server 

  sudo route add default gw 192.168.0.1

  route add -net 192.168.2.1/24 gw 192.168.0.0 eth1

  route add -net 192.168.2.1/24 gw 192.168.0.0 eth1


  route add -net 192.168.2.0/24 gw 192.168.0.0 eth1
  route add -net 192.168.2.1/24 gw 192.168.0.9 eth1

  route add default gw _gateway 0.0.0.0 eth0


  default         _gateway        0.0.0.0         UG        0 0          0 eth0

  route add -net 192.168.20.0 netmask 255.255.255.0 gw 192.168.10.1


  iptables -t nat -A POSTROUTING -o eth1 -j MASQUERADE


  iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
  iptables -A INPUT -i eth1 -j ACCEPT
  iptables -A INPUT -i eth0 -m state --state ESTABLISHED,RELATED -j ACCEPT
  iptables -A INPUT -i eth2 -j ACCEPT
  iptables -A INPUT -i eth0 -m state --state ESTABLISHED,RELATED -j ACCEPT
  iptables -A OUTPUT -j ACCEPT


  iptables -t nat -A POSTROUTING -o tun0 -j MASQUERADE
  iptables -A INPUT -i tun0 -m state --state ESTABLISHED,RELATED -j ACCEPT



  route add -net 192.168.31.0 netmask 255.255.255.0 gw 192.168.112.5




  sudo vi /etc/netplan/01-installer-config.yaml





  vim /etc/dhcp/dhcpd.conf

  subnet 192.168.2.0 netmask 255.255.255.0 {
   range 192.168.2.10 192.168.2.99;
   option domain-name-servers 192.168.10.8,192.168.11.8;
   option domain-name "splend";
   option subnet-mask 255.255.255.0;
   option routers 192.168.3.1;
   option broadcast-address 192.168.3.255;
   default-lease-time 600;
   max-lease-time 7200;
  }

  option domain-name-servers 114.114.114.114,8.8.8.8,
  option domain-name "splend";





  subnet 192.168.2.0 netmask 255.255.255.0 {
     range 192.168.2.100 192.168.2.200;
     option domain-name-servers 114.114.114.114,8.8.8.8;
     option domain-name "splend";
     option routers 192.168.2.1;
     option broadcast-address 192.168.2.255;
     default-lease-time 600;
     max-lease-time 7200;
  }

  subnet 192.168.3.0 netmask 255.255.255.0 {
     range 192.168.3.100 192.168.3.200;
     option domain-name-servers 114.114.114.114,8.8.8.8;
     option domain-name "splend";
     option routers 192.168.3.1;
     option broadcast-address 192.168.3.255;
     default-lease-time 600;
     max-lease-time 7200;
  }



  iptables -A FORWARD -i eth1 -o tun0 -j ACCEPT
  iptables -A FORWARD -i tun0 -o eth1 -m state --state ESTABLISHED,RELATED -j ACCEPT


  iptables -A FORWARD -i eth2 -o tun0 -j ACCEPT
  iptables -A FORWARD -i tun0 -o eth2 -m state --state ESTABLISHED,RELATED -j ACCEPT
  iptables -t nat -A POSTROUTING -o tun0 -j MASQUERADE

  apt install iptables-persistent


  iptables-save > /etc/iptables/rules.v4


